Thursday, 29 January 2009

New developer

Yesterday Uzi got a SVN account. Now he is an official Kadu developer ;)

He is working with Juzef on making Jabber/XMPP and Tlen protocols first-class citizens in Kadu - expect impressive results in next few months ;)

Monday, 26 January 2009

How much does it need to change to make sure everything remains as before (or the ContactsListWidget story)

Old widget used to display list of contacts in main window and in chat widget (used with conferences) was named UserBox. This class survived the transition to Qt4 only because Q3Support module. In 0.6.6 we want to remove dependency on Q3Support, and to add more functionality there (like support for meta-contacts, avatars, trees).

So we've started to writing it from scratch (almost).

The first thing to do: extract usefull classes from userbox.h/userbox.cpp to its own files.

Result:
  • AbstractToolTip
  • ToolTipClassManager
  • ContactsListWidgetMenuManager

The last one was part of UserBox class, but I think it is better to have it separated.

Second thing to do: write new widget.

Result:

I can say that this whole Model/View thing totally kicks ass. Most of the work is done by Qt: we don't have to care about sorting (with its performance when many contacts change status in one time) - we only provide compare function and signals triggered when something changes on ContactsManager.

Also the refreshing code in old UserBox was not very easy to follow - there was methods like refresh(), refreshLater(), refreshAll(), refreshAllLater() and complicated structures that allowed to avoid refreshing list if not neccessary. I'm glad we could remove all that code and just belive that Qt will do it better than us ;)

For each ContactsListWidget in Kadu there is exactly one ContactsListWidgetDelegate object that paints items on list. In Kadu 0.6.6 for each item on each UserBox there was one KaduListBoxPixmap. So I hope our memory ussage is a little bit lower that before (I'll do some measurements after we have 0.6.6 out). Painting code is almost the same - the only difference is description handling. Qt4 provides QTextDocument that we now use for that - Kadu 0.6.5 had custom (and long) code to compute description height and to paint it wrapped. It is another thing that Qt is doing for us now.

But it is not done yet:
  • sorting does not works as good as it should (random contacts are not-sorted)
  • filtering not yet implemented
  • support for only one column (maybe it will be possible to have more columns, i don't know yet)
  • drag&drop not yet implemented
  • background images not yet implemented
So if you have some time, please go to Kadu forum and ask how you can help ;)

Tuesday, 20 January 2009

Aware objects

"Aware objects" were created because of my laziness. In Kadu exists lot of objects that should change their behaviour/look when the configuration changes. The change invokes ConfigurationWindow::updateConfig() which (before introduction of ConfigurationAwareObject class) emited a signal.

Each object that wanted to be informed about it had to connect to this signal. It needed a lot of code: slot implementation, connection in constructor, disconnection in destructor. To free myself from writing this I've created ConfigurationAwareObject class. It has very simple interface:
class KADUAPI ConfigurationAwareObject
{
  static QList<ConfigurationAwareObject *> objects;

  static void registerObject(ConfigurationAwareObject *object);
  static void unregisterObject(ConfigurationAwareObject *object);

public:
  static void notifyAll();

  ConfigurationAwareObject();
  virtual ~ConfigurationAwareObject();

  virtual void configurationUpdated() = 0;

};


The objects field contains list of all objects of this class. Its gets updated each time the ConfigurationAwareObject contructor or destructor is called.

Static method notifyAll (called from ConfigurationWindow::updateConfig()) iterates over all objects and calls configurationUpdated on them.

So if you need to create a class that reacts to changes in configuration just make it inherit from ConfigurationAwareObject and reimplement virtual method configurationUpdated(). No need to connect/disconnect signals and slots.

In 0.6.6 AccountsAwareObject class was added with 2 abstract virtual methods: accountRegistered(Account *) and accountUnregistered(Account *) and two normal methods: triggerAllAccountsRegistered and triggerAllAccountsUnregistered (these two just call virtual methods with all currently active account).

So if you want information about accounts changes - make a class inherit from AccountsAwareObject and reimplement virtual methods. Also add call to triggerAllAccountsRegistered in constructor and triggerAllAccountsUnregistered in destructor - so it will be aware of accounts that existed before the object was created.

You could ask why triggerAllAccountsRegistered and triggerAllAccountsUnregistered are not called in AccountsAwareObject's constructor/destructor. It is because AccountsAwareObject's constructor will be called before your class constructor, and call accountRegistered(Account *) on object that can be not ready for this.

I hope that in near future we will create more aware objects in Kadu and remove many unnecessary signal/slots connections.

Monday, 19 January 2009

Tlen & Jabber support

First screenshots of Tlen contacts in Kadu (Jabber is supported too, but we don't yet have icons for that):

http://www.kadu.net/forum/viewtopic.php?p=88749#p88749

Thanks for Juzef and uzi for the hard work!

Thursday, 8 January 2009

Status and description

I had a GPU failure on my laptop, so I was unable to do any work for Kadu for 2 weeks. Now as the ULE to Contact porting is in progress, I'll present how status/description changes are done in Kadu internals.

The problem

Changing status seems to be an easy task - create an object with enum (Online, Busy, Invisible, Offline) and QString fields, modify values and pass it to protocol-handler object. But at some time people started writing plugins that were modifying statuses in many ways:
  • amarok module - could add information about currently played song to description (before or after user-supplied one)
  • filedesc module - was reading lines from file, one by one, and setting it as user description
  • autoway - change status to offline/invisible/busy after some time of inactivity, description could also be modified as in amarok
  • ...
User could load and use any status-changing modules at one time.

This is how amarok module worked:
  • when amarok started to play song, current user status was stored
  • user description was changed by adding song information
  • when song stopped, old (stored) status was restored
And autoaway:
  • when user was inactive for given period of time, current user status was stored
  • user description and status was changed
  • when user activated, user status was restored
If you have some experience with multithreaded programming, you'll see the possible problem right away. If user used amarok and autoaway modules and the steps were executed in following order:
  • (amarok) 1 - amarok stores "user status"
  • (amarok) 2 - amarok changes status to "amarok status"
  • (autoaway) 1 - autoaway stores "amarok status"
  • (autoaway) 2 - autoaway changes status to "autoaway status"
  • (amarok) 3 - amarok restores "user status
  • (autoaway) 3 - autoaway restores "amarok status"
The status of user will not be set to "user status" - so the behavior is plain wrong.

The resolution

To resolve this problem I've created two classes: StatusChanger and StatusChangerManager. These two operates on old UserStatus class (that will be replaced by new, much simpler, Status - but this is another story).

StatusChanger is abstract class with method virtual void changeStatus(UserStatus &status). This method can do anything with status - append, prepend something to description, replace some tags with any content, even change the status to Offline (which means logout from Gadu Gadu network).

StatusChangerManager allows for registration and unregistration of StatusChangers. StatusChangers are joined in chain, ordered by their priorities. The first StatusChanger in chain is always UserStatusChanger, which is controlled by user. When user changes status the UserStatusChanger::userStatusSet(UserStatus) is invoked, which emits signal statusChanged. This signal is received by StatusChangerManager, and every StatusChanger's changeStatus is invoked:

UserStatus status;
for (int i = 0; i < statusChangers.count(); i++)
 statusChangers.at(i)->changeStatus(status);

After that statusChanged(Userstatus) signal of StatusChangerManager is invoked, and resulting status is sent to server.

Every StatusChanger can emit statusChanged slot and trigger status rebuild in chain.

Resolved
Now amarok module is replaced by mediaplayer (much more general module). All status-changing modules uses new classes. There is no problem like before, because all modules provides only 'operations' that changes copy of user status (so it is always remembered). After removing or changing one of 'operations' status is rebuild starting from the one that user set.

SplitStatusChanger

Kadu has class SplitStatusChanger (in status_changer.cpp). It allows for setting statuses with description of any length for Gadu Gadu. If it is too long for protocol to handle it - it is splited in number of parts. Each part is displayed for specified period of time.

If you want to test it - please find place in code suitable for inserting it in chain, also remove the description limit from status dialog (look in misc.cpp).