Wednesday, 23 December 2009

State of the Kadu Usability Project as of 12.2009

Hello,

This my first post on this blog, so I would like to say "Hello". ;) My name is Piotr Pełzowski, I'm working on Kadu project in my free time - mostly I work on usability and PR. Today I am going to report about current state of usability work done for upcoming version of Kadu Instant Messenger. I will present mockups that had been done by Season of Usability student, Mike Harmala with cooperation with the Kadu Usability Project Contributors.


Adding a New Contact in Kadu IM 0.6.6


Kadu Usability Project aims to make the current Kadu interface more user-friendly and easily accessible for users with different levels of proficiency as well as help the development team with the introduction of new features in the best way possible usability-wise. In short: KaduTeam is serious about building usable universal instant messenger.

As you probably know Kadu IM is an open-source IM client currently supporting only proprietary Gadu-Gadu protocol, Gadu-Gadu is the most popular IM network in Poland. In addition to GG network in next version of Kadu IM the XMPP/Jabber and Tlen networks are going to be officially supported. In fact three days ago Kadu 0.6.6 alpha0 has been released - this is the first development snapshot with XMPP support. As Rafał Malinowski stated:

Alpha0 releases in Kadu are soft-API freeze releases. That means that no one is allowed to make big API changes (small changes are still allowed). (...) Now we will focus on adjusting our GUI to mockups provided by Usability Team and on fixing remaining (less important) API parts - like Pending Messages, File Transfers or Status Changer (...)


So now it is time for GUI changes in Kadu, changes which are required and needed.

Ever since its creation in 2001 Kadu IM has been developed and designed primarily for Polish users of GG protocol. Now we hope to attract international user base with Kadu as a XMPP/Jabber client as well as make life easier for Polish users, many of whom use XMPP/Jabber on a daily basis (according to our user survey, that's the case for ~30% of Kadu users). Along with adding support for multiple protocols, we're planning to support simultaneous connections to more than one communication network, usage of multiple accounts of the same protocol and 'metacontacts'. All these enhancements require big changes, sometimes radical changes, in the interface of Kadu.

IM Networks drop-down list

Those changes are not results of UsabilityTeam whim ;) , our design is based on kadu user survey results (in polish), the survey which has been completed by 5035 participants provided us with vast amounts of useful data. Based on the data acquired from the developers and user survey personas for the Kadu IM has been built by Joanna Pierożek. In 11.2008 the actual work on the design of the multiprotocol user interface has been started. Draft mockups for most of the windows that needed to be introduced or redesigned were ready by the time Mike Harmala joined our team in July 2009 as a Season of Usability student. In the course of his work with us Mike has conducted a heuristic evaluation of the mockups we've previously prepared, based on Nielsen Heuristics, KDE4 Human Interface Guidelines - which we want to comply with - and other resources identifying a lot of issues that needed to be cleared up before the final version of the mockups could start to be implemented. The final contribution Mike has made to Kadu was a set of almost final, both functionally and visually polished mockups for most of the windows that would be introduced into Kadu as soon as it becomes a MultiIM. Those mockups were tweaked/polished a little bit by us and today we will present proposed final design to you.

Let's start with guideline that better explains some of the interactions in the new multiprotocol GUI:


Main mockups in png format:

Click to see big version of each mockup.

  1. Adding a New Contact
  2. Subscription Window (for XMPP-like networks)
  3. Contact Properties
  4. Your Accounts Window- Manage
  5. Your Accounts Window - Add/Create Account
  6. Menu and Context Menu
  7. Merging Contacts (so called "Metacontacts")
  8. Preferences (switching between Simple Mode and Advanced Mode changes ContactsList appearance as described in presentation)
  9. Configuration (First Run) Wizard
  10. Chat window

At this point we've almost finished full specification of all the windows that need to change or need to be introduced into Kadu 0.6.6 to make it a user-friendly multiprotocol Internet Messenger. We know the risks of radical changes to the design our users have enjoyed and gotten used to so far but we hope what we've come up with is good enough to both make our current users stay with Kadu and enjoy its enhanced functionality and improved usability and attract new users who either need a multiprotocol messenger or a user-friendly XMPP/Jabber client.

We still have few windows to design but those mockups should be consider as final GUI proposal for upcoming Kadu 0.6.6 version. We are eager to hear your feedback on our proposal. So if you have any comments feel free to comment!


Get Involved

Do you like our vision of usable multiprotocol messenger? Do you like our 'regionalization' concept visible in "Your Accounts Window - Add/Create Account", "Configuration Wizard" and "Adding a New Contact" window? Or maybe you like our efforts to integrate "branded" XMPP providers into GUI (which will probably lower the entry barriers for newcomers in XMPP world.)?

If additional you are interested or involved somehow in usability please consider joining Kadu Design Feedback mailinglist and became a member of UsabilityTeam. We have still many things to design, improve or fix. ;) Currently UsabilityTeam consists of two active contributors Joanna Pierożek and me. Previously the work on the Usability project has been conducted mostly in Polish. However during the summer we've switched almost entirely to English on the mailing list, in the documents and in the mock-ups we've produced. As far as the Usability project goes, we're ready to accommodate international contribution without making anyone feel intimidated. Hopefully, along with introducing XMPP/Jabber and other protocols useful for people from outside Poland into Kadu, regular contributors from other countries will indeed join our ranks.

Monday, 21 December 2009

Kadu 0.6.6 alpha0

Kadu 0.6.6 alpha0 was released.

Warning: This version will eat your children. Do not use if you are not sure what you are doing. Configuration file format was changed (configuration file too), history format was changed. Reverting back to older versions is be possible, but with loss of all new history and configuration changes. If you can, use new Kadu from new user or with CONFIG_DIR set to kadu_alpha. We will not provide any official packages for this version.

Alpha0 releases in Kadu are soft-API freeze releases. That means that no one is allowed to make big API changes (small changes are still allowed). Stability in general is very good, history works, chats works, file transfers - we don't really know yet ;)

Now we will focus on adjusting our GUI to mockups provided by Usability Team and on fixing remaining (less important) API parts - like Pending Messages, File Transfers or Status Chagner. Many modules needs to be adjusted to new API too.

And of course: stability, stability and stability ;)

ChangeLog available at gitorious.
Source package available at kadu.net (2MB).

Good lock!

Wednesday, 2 December 2009

Storage Layer

Code I've started writing almost year ago (new configuration API) have grown to whole new layer of code. Now it is called Kadu Storage API and this is the first thing that will get documentation (by Doxygen) for new release.

This API has some really nice features:

Details

Some classes, like Contact or Chat, have data that depends on some plugins (like XMPP/Jabber contact data, or Gadu-Gadu contact data, or IRC chatroom data, or ...). We want to give user view to this data even if plugin is not loaded/unavailable for some reason. So as much data as possible were extracted to generic classes like Contact, Chat, or Account (see how much extra data Gadu-Gadu needs for Account).This data can be loaded from Storage at any moment and be displayed to user and manipulated.
But that data cannot be used to do anything useful: you can't talk with someone on XMPP account if Jabber plugin is not loaded (but you can view this chat history). So when the plugin is loaded every object that depends on it loads extra data by using specific Details class. This class uses the same StoragePoint as main class so it can load extra data from the same configuration node. After extra data is loaded, the object can be used (to connect to server, make some chat and so on).

Shared classes

Shared classes bring some Java-like API to Kadu. It is just a trick to hide that given class is really a pointer. Just a good usage of QExplicitlySharedDataPointer and QSharedData. But with lazy-loading and StorableObject support ;)

Generic managers

Kadu has now two generic (template-based) managers: Manager and SimpleManager. The first one has built-in support for classes that use Details (like DetailsHolder subclasses) - it only exposes object without Details loaded when code explicitly asks for that. Just look for ChatManager implementation to see how little code is now needed to implement manager that fully supports loading/storing and many more features using these generics:

chat-manager.h
chat-manager.cpp

Simple constructors and intuitive behavior of StorableObject

StorableObject class (and all subclasses) has now only one constructor (instead of 4). Passing parent objects, node names and so on in constructors was replaced with nice virtual functions.

This class has now State field. It can has three values: StateLoaded, StateNotLoaded, StateNew. Code of load method is executed only if the state is StateNotLoaded. By default object is treated as "new" (so no need to load, but only default data is available). To make object "not loaded" (so it will be eventually "loaded") you need to set up its StoragePoint using setStorage method.

All that simple changes give more natural and intuitive feel of how this object behave. It also allows us to remove some code, because these new default are what we need in most of our code.

What is next?

After Doxygen documentation for Storage is written, I'll focus myself on little higher layers: Buddy Storage, Chat Storage, anything Storage to make sure everything is nice and clean ;)

Tuesday, 10 November 2009

API unification

I am working on last bits of new Kadu API. Works that needs to be done can be summarized in 3 points:

  1. move all important classes to base/shared/details model
  2. extract as much as possible from base/shared/details model to common base classes
  3. remove Buddy::contact(Account) method (or remove unnecessary uses of it)
Base/Shared/Details model

This is the improved structure of our main classes (Buddy, Contact, Identity, Avatar...). Base class (named simply Contact) is responsible to give access to object data. Every copy of such object contains pointer to the same Shared class (like ContactShared) contains data, can read and store it (it is always UuidStorableObject). Thanks to that we don't need to worry about working on copy of object instead of original one (the only class that knows about Shared class is the Base class).

Details classes allowed us to switch from IS-A to HAS-A relation (now GaduContact does not inherits Contact, just GaduContactDetails inherits ContactDetails and ContactShared object contains one of ContactDetails objects). This allows us to load all accounts/buddies and contacts at once and only populate details fields when protocol module is loaded. So situation where some object exists only in configuration and not in running program is not possible now. You can read history of such contact now ;)

A lot of code for these classes is duplicated: it needs extraction to template classes, and it will be done after Contact is ported to this new model.

BTW: I've renamed Contact to Buddy and ContactAccountData to Contact, so the names do make sense now ;) It was very problematically but I think that it was worth the effort.

Removing Buddy::contact(Account) method

This method assumes that a Buddy can have only one Contact for one Account. We don't like this assumption (someone can have a friend with 10 Jabber accounts, so why limit connections to only one?). After this function is removed (or its usage reduced as much as possible) this will be possible. We also need to remove functions with Account/Buddy pairs and replace that with Contact. It will also make moving Contacts between Buddies simpler (and merging and splitting Buddies too).

0.6.6 alpha0 - soft API freeze (or I will not mess so much anymore)

These are the last three things I want to do before soft API freeze. Be prepared to testing first alpha release ;)

Sunday, 11 October 2009

Power of proxy models

Our Usability Team created mockup of new "Add Buddy" window. It contains combobox for selecting group new contact will belong to. It looks like that:

  • - Select group -
  • Group 1
  • Group 2
  • Group 3
  • Create new group...
We have model for groups, but using it for that combobox would only show 3 groups, without additional items at top and at bottom. So I had to use proxy model.

Proxy models are models that you can stack (many of them) on another model (like our group model) to filter them, sort them or process data in any way. For "Add buddy" window there are 2 proxy models on top on group model:
  • QSortFilterProxyModel
  • ActionsProxyModel
First proxy model sorts groups by name, the second one adds items at top and at bottom of the list (it even make it italics). Code for stacking proxy models looks like this:

GroupsModel *groupComboModel =
new GroupsModel(GroupCombo);
  QSortFilterProxyModel *sortModel =
new QSortFilterProxyModel(GroupCombo);
  sortModel->setSourceModel(groupComboModel);
ActionsProxyModel *groupsProxyModel =
new ActionsProxyModel(groupsModelBeforeActions,
  groupsModelAfterActions, GroupCombo);
  groupsProxyModel->setSourceModel(sortModel);
GroupCombo->setModel(groupsProxyModel);


The most important part is that you can reuse these proxy models everywhere you want and you don't have to manually update this combobox when new groups are created ;) Models do everything for you.

Tuesday, 22 September 2009

How not to use signals and slots

I wanted sound module to work for brand-new git/master Kadu with Phonon backend. So I've looked at code of sound and phonon_sound modules and I've discovered a big misuse of signal-slot mechanism.

Please look at sound.h file from 0.6.5 branch. The interesting part is signals: from SoundManager class:
signals:
  void openDeviceImpl(SoundDeviceType type,
       int sample_rate, int channels,
       SoundDevice* device);
  void closeDeviceImpl(SoundDevice device);
  void playSampleImpl(SoundDevice device,
       const int16_t* data, int length,
       bool *result);
  void recordSampleImpl(SoundDevice device,
       int16_t* data, int length, bool *result);

How this works? A module such as phonon_module creates new object that connects to these signals. Then a signal is emited and a slot is executed. Everything works, but this creates a lot of problems:
  • many object can conencts to these slots so sound may be played many times by different backend
  • so we need to ensure that only one backend is loaded, this requires additional code in module-managing classes
  • object can just ignore some signals and do not implement slots and/or connect them
  • results are returned using pointers in last parameters of these signals
This kind of problem requires another solution, that is still very simple and nice to implement:
  • create abstract class, like SoundPlayer, that has methods like SoundDevice *openDevice() and so on
  • create object SoundPlayer *Player in SoundManager
  • use this object instead of emitting signals
  • make SoundPlayer parent class of backend classes instead of connecting signals
Thats all today ;)

Monday, 14 September 2009

New history window

Juzef has been working on new history window (and SQL history backend) for some time. So I've finally give it a try and after some tweaks I will show you effects of his work (this window still needs lot of work from our UI team, but it works as it should):


On the left side there is list of all chats (single ones and conferences - future modules like irc_protocol would be able to add new types of chats). You can use edit box above this tree view to filter chats by name, contacts, contacts' ids and whatever you want.

On the right side you have list of dates. You can select any one of them and you will get list of messages from that date. Above that is edit box for searching words (it will filter chats and dates lists too) and for selecting date range (it will filter chats list also).

What needs to be done:
* usability (this window does not have any)
* highlighting of found words (needs backport from 0.6.5.3)

Monday, 7 September 2009

New channels

Three new channels for KaduTeam-world communication were open today:
I hope you will enjoy reading our microblogs ;)

Saturday, 5 September 2009

New history feature

Yesterday I've added a feature known from Skype - user can display messages from yesterday/last 7 days/last 30 days in chat window. I needed only one hour to implement it, thanks to Juzef's SQL history and improving Kadu API ;)

Screenshot:


I know, the icon is the same as "Show history" one. I'll ask our art team to make a new one :)

Wednesday, 2 September 2009

Another git repository

White Eagle has set up a new git repository for Kadu: on gitorious.org. It is way better that the one I've set: it has all our SVN tags, branches and externals (libiris). He has spent a lot of time to do that ;)

So now repositorium on Github is just a mirror of master branch and other git branches (not including old svn-ones). Feel free to ask for access to any of them.

Wednesday, 26 August 2009

Avatars

Kadu now supports avatars on contacts list and chat window with Adium styles (now only for GaduGadu accounts). Implementation was done by me, Juzef and White Eagle (team work ;) ). Thanks to Qt model-view framework and to Kadu protocols' service framework this was very easy to do. Now we only need to optimize it a bit (don't ask server every time for avatar data) and to use original aspect-ratio of images.

Below is screenshot with contacts from my list that have avatars and two contacts without one, so you can see the temporary default-avatar image too.


And the chat window:


Next feature that will be implemented by me is choosed by community on our forum - support for "Show messages from today/last day/last week" in chat window (similar to what is in Skype 4).

Wednesday, 19 August 2009

Contacts tree view

With one of last revision Kadu got a new feature - tree view for contacts. Now you can expand (with right-arrow button) any contact to see all connection to it (like Gadu-Gadu and Jabber ones). In near future double-clicking on any item will open chat window using choosen protocol and account, and every item will have its own context menu... Now just a simple screenshot:

My number is shown (btw: if you want to talk to me, use Private Messages on our forum or comments on this blog).

If someone has a screenshot with contact that has more than one subcontact - please send it to me.

Edit: screenshot from White Eagle

Thursday, 13 August 2009

Another manager

GaduGadu protocol supports delivery acknowledges - you can be sure that message was received. Kadu implemented it in two ways (you can choose one in Configuration):
* ignore it
* block chat window until ack is received

The first one sucks, because message could be lost in deep network and you will never know, second one sucks, because you sometimes have to wait for more than 5 second until you could write another message.

On 2007 mishaaq wrote a patch that would display a status icon next to each message. So you have information about delivery and chat window is not blocked. This patch was not applied (don't remember why) and is not available now (url is invalid now). This functionality is so much needed - I decided to write simple version of it from scratch.

As usual for me I started with looking again at the whole messages code and I've found that we need to make MessagesManager, remove PendingMessagesManager (or change it into MessagesManager), make Message a StorableObject and QSharedData and then everything will work great and look great and will be very hackable ;)

In near future 50% of Kadu classes will be managers. I hope it is a right way - it still looks so.

Wednesday, 5 August 2009

Back in Poznań

I'm now back from my two weeks of sailing on Mazury's lakes. My thesis is almost-approved by my mentor and amount of my free time will be greater than zero in next week. Be prepared for next big changes for Kadu 0.6.6 ;)

Saturday, 4 July 2009

Git repository

Kadu has now an unofficial (read only) Git repository: git://github.com/vogel/kadu.git (http://github.com/vogel/kadu/). For write access please create an account on Github and ask me on our forum then. It is connected with our SVN by my local repo created by git-svn, so I hope I'll be able to synchronize these two.

I am curious how it will works and how the Kadu Team will like it. From the tutorial on github it seems like git is superior to SVN in every possible case. The future will show the truth ;)

Monday, 29 June 2009

Changing status for multiple accounts

We now supports multiple accounts. Each account can have its own status and description. We need a way to allow user to change these easily. Our usability team created two modes for kadu: advanced and simple one. In advanced mode each account will have its own status button and status menu, each identity will have own button and menu (accounts can be joined in identities like Work and Private).

Development team has to put these ideas into code. In Kadu 0.6.5 each status change goes through StatusChangerManager singleton, that stores current user-set status and computes real status (modules can change what user set). StatusButton and StatusMenu uses this singleton.

The new code will be a little more complicated. StatusContainer interface (empty class) is introduced. It contains methods to set status, get current status and signal statusChanged. This interface will be implemented by two classes: Account (already done) and Identity (this class does not exists yet). Singleton StatusContainerManager will be created, as well as StatusContainerAwareObject class. Each StatusMenu object will get an StatusContainer object as contructor parameter, the samy apply for StatusButton. New widget, StatusButtons, is an StatusContainerAwareObject so it can adjust itself to new accounts/identities. It is just a horizontal box that contains multiple StatusButton widgets.

I thinks that this architecture will cover all needed functionality. Any other ideas?

Tuesday, 23 June 2009

Status updates

Kadu is now slowly progressing to 0.6.6:
  • I'm moving files and classes around, so as few files as possible will remaing in kadu-core directory;
  • after this is done, I'll finish Your accounts window and start to enhance ContactsListWidget to support visualizing of metacontacts
  • our Season of Usability mentor and student connected (at least) with us, so we will have more GUI mockups to code;
  • I've switched locally to git (with git-svn as gateway to our official repository). I hope we will be able to move official repo to git after team members' exams, so everyone would be able to work on own branch(es), and I hope that this will also speed up our development a bit.

Wednesday, 10 June 2009

Password protection

I did not have much time to work on Kadu last time (thesis work), but I would like to present a small feature I've just implemented. Now you can use "Your accounts" window to uncheck "Remember password" field. When you'll try to login to this account a nice window will appear to get the password from you:


You don't need to worry again about someone stealing your identity ;)

Tuesday, 19 May 2009

New Accounts window

Our Usability team worked hard for bringing some nice windows for users. The first one I've implemented (almost) is "Your accounts" (White Eagle is working on "Add buddy" window now).

Maybe some screenshots now:


This is the window in "New account mode" (the only one already implemented). It lacks edit/delete functions, but anyone really needs them?

Lets select "Gadu-Gadu" from protocols combobox. What do you get?


You can enter things like "Account name", "Gadu-Gadu number" and "Password". Below is something called "Account description" - not yet implemented too. If you don't have an Gadu-Gadu account go select "I don't have a Gadu-Gadu number" and you'll get this window:

You need to provide e-mail address and a password (two times). And the "captcha" has to be solved (it is Gadu-Gadu's thing, not our invention). Click "Register" and with some luck you'll be granted with new account!

Next click "Add this account" and you'll get this:


Voila.

Implementing this window required some strange code:
  1. CreateAccountWidget was added, each ProtocolFactory now has to provide function that create one of these window (like GaduAccountCreateWidget, that is visible above),
  2. Creating FirstEmpty model class that adds an item before any other items in a model (like "Select a network to add" in example above); I think there is a better way to do that, but I haven't found one.
We (with the whole Kadu Usability Team) hopes that our users will love these windows. If you have any suggestions please put them in comments or on Kadu forum (we have special subforum for English users).

Wednesday, 29 April 2009

Broken server

Out server is broken now ;( It means no access to our web page, Wiki, Forum, Mantis, even SVN... What is worse: we cannot reach admins of the machine... Maybe it is time to think about mirroring on SourceForge or on Google Code? And to move development from SVN to git?

And of course this had to happen when we got Season of Usability slot... Sigh.

Update: server is online again! It was only broken net connection, so no backup restore needed ;)

Monday, 27 April 2009

Season of Usability

I have a good news from our Usability Team:

Kadu accepted as a Season of Usability project

Season of Usability is a series of sponsored student projects to encourage students of usability, user-interface design, and interaction design to get involved with Free/Libre/Open-Source Software (FLOSS) projects. Inspired by Google's Summer of Code, OpenUsability offers sponsored student projects. The Season of Usability 2009, sponsored by Google and Canonical, will be supporting 10 students to work on 10 open source projects during the June 1 - August 31 2009 season.

Kadu has been accepted as one of the projects. If you want to apply and work on our Usability Project) find out the details at the Season of Usability 2009 website.

Joanna "akai" Pierożek

Sunday, 19 April 2009

Status updates

  • Code a lot more stable that before ;) Almost everyday-usable (if you do not count modules...)
  • Gadu descriptions now properly decoded
  • Work on "Your accounts" window was started - it will allow users to configure their accounts (add, edit, delete). Mockups are available at our wiki page.
  • New class added: ContactSet (based on QSet). It will replace ContactList in many places.
  • Managers are now lazy-loading.
  • ConfigurationManager class added - it stores list of classes, that wants to save themselves before aplication exits (like AccountManager)
  • Many signals in ContactManager added - to allow watching for contact data changes
  • ChatWidgetManager will be added, replacing old ChatManager (that was renamed recently to ChatManagerOld [sic]).

Wednesday, 1 April 2009

0.6.6 in middle of the way

Kadu 0.6.6 is now half done. I've decided to stop adding new features and reorganizing/rewriting code and to focus on stabilization and functionality that was lost in the development process after 0.6.5 (excluding modules, only core counts). So everyone is encouraged to get the last SVN snapshot and test it. I would like to have a stable version in a week or two, before the next part of API will go under review (the less important part).

We've done a summary of changes (done so far) on our forum. Here it is translated:
  • Gadu protocol moved to gadu_protocol module;
  • kadu-core directory splitted, files moved to subdirectories;
  • splitted big classes (Kadu -> KaduWindow, Core, KaduWindowActions, StatusMenu, StatusButton);
  • notify and window_notify modules moved to core;
  • static objects reimplemented as singletons (e.g. AccountsManager::instance());
  • a lot of new API to support multiple protocols: Account, Contact, AccountsManager, ContactsManager, ProtocolFactory, ContactAccountDat and many more;
  • UserListElement/UserListElements replaced by Contact/ContactList and probably ContactSet in nearest future;
  • services extracted from Protocol class, every implementor can just implement a subset of them - like Chat of FileTransfer;
  • Qt3Support removed, contacts list is MVC now;
  • Chat concept introduced (chat with one contact, GG conferences, IRC chatroom, XMPP chatroom - everything is a Chat), contacts list will be transfered into chat list in fututre;
  • simpler classes to support configuration storing (StorableObject, UuidStorableObject);
  • Adium chat styles support (with possibility to add more styles engines);
  • better X11 integration (by ultra);
  • idle module for supporting autoaway and similar modules (no more /proc reading);
  • better, splited menu (3 submenus);
  • extended group management (adding, removing, changing name and so on);
  • tlen and jabber modules added (Juzef, uzi);
  • a lot more.
And it's just half of the things that needs to be done...

Proof-read by Stiep

Monday, 23 March 2009

Notifications

For the last few days I've been splitting the big Kadu god-class. It has so much code, that it's enough to make five other classes (Core, KaduWindow, KaduWindowActions, StatusMenu, StatusButton) and I think I could split it even more! Core (with Core::instance()) manages starting/stopping the application and setting/getting the current status. KaduWindow only implements gui of the main window... It is now closer to one-class one-purpose principle.

The next task is to move notify and notify_window code from modules to core. There are many places in kadu_core that could use notify capabilities and I don't see any reason to forbid that (and it will allow us to remove the MessageBox class).

I think our notify system is quite powerful, it supports many back-ends (like showing windows, playing sound, showing hints...) and it allows placing actions inside notifications. The only thing it is lacking is showing a value, e.g. a progress bar for file transfers. If we add it, it could be possible to integrate our notify system with KDE or GNOME notifications.

A little bit about our system: it consists of two base classes, i.e. Notification (for a messages being displayed to the user) and Notifier (for backends), and the NotificationManager class (for managing the two former).

Notification

If you want to create a message for the user, you should use the Notification class (or one of its descenders). Each notification has a name (e.g.: "newChat", "incomingFile", "connectionError") that identifies an event (notifications are configurable by name). Other properties are: title (used in WindowNotifier), text (content displayed to user, e.g. "New message from XYZ"), details (less important data, e.g. content of message sent to us), list of contacts and of course an icon.

Many notifications depend on the currently used account (e.g. "connectionError"). For those notifications the AccountNotification class should be used. Of course you could extend any of these classes to add new information, but not every notifier will be aware of them.

The Notification class has a concept of 'callbacks' - it's a list of actions that a user can perform (e.g.: 'accept' and 'ignore' on the 'incomingFile' notification). The only way to create a notification with a callback is to create a new class with slots for each action and call addCallback(tr("Action description"), SLOT(...)). You can also call setDefaultCallback(int timeout, SLOT(...)) so the user won't have to decide by herself or himself. If you would want to receive only the default callbacks 'accepted' and 'discard' (e.g. generated by left and right-clicking on hint), then just overwrite the callbackAccept and callbackDiscard virtual methods.

Also, notifications can live for some time. Every notifier can 'acquire' notification, so it won't be deleted until every 'acquirement' is canceled with 'release'. Sound notifiers do not 'acquire', but window and hint do. As long as the Notification is alive, it can be changed, but as I can see now, no signal is emitted. I consider it a flaw (look how the hints module appends new messages to hint...).

The last thing to know about notifications is that they need to be registered (so users can edit settings for each of them individually). It is done by NotificationManager::registerEvent and you can unregister it (for example if your module gets unloaded) by NotificationManager::unregisterEvent.

Notifiers

For implementing a new notifier, you should create descendant from the Notifier class with an overridden method notify(Notification *) (and register it with NotificationManager::registerNotifier). If your notifier will support callbacks, re-implement CallbackCapacity callbackCapacity() too (and return CallbackSupported there). You will also need to use notification->acquire() if it will display/update the notification content/icon anywhere. Of course after the notification has hidden use notification->release() to free memory.

If notifier can be configured, you should also re-implement NotifierConfigurationWidget *createConfigurationWidget(QWidget *) - it should return a new instance of the new class descending from NotifierConfigurationWidget. The most important function to re-implement is switchToEvent(const QString &). It should save the state of the currently edited event and display the state of the new one - given its method arguments.

In the end, saveNotifyConfiguration should store all changes made by the user in the configuration file.

Proof-read by Stiep

Wednesday, 18 March 2009

GSOC updates

Google rejected us ;(
How sad. So we will have to do some of our projects without their support.
Maybe next year... When we will have XMPP support and Kadu will be more recognizable in the world ;)

Status updates

No big changes recently in Kadu code (lack of time as usuall), but there are some things I would like to share:
  • we've got new developer - Juzef (http://juzef.idl.pl/) got full read-write access to Kadu svn repository, congratulations ;)
  • we've applied for Google Summer Of Code 2009 (our submission is still not verified), there is list of our ideas: http://www.kadu.net/w/GSOC2009 - of course anyone can start working on it, even if Google does not want us ;(
  • big Kadu class will be splited into KaduWindow, Core, CoreActions and maybe some others; mixing GUI logic with main class of Kadu is not a good idea (something like God Object antipattern)
  • Chat concept appeared, it will allows to have Gadu-Gadu conferences on contact list and not only that ;)

Wednesday, 11 March 2009

Signals, slots and ignoring OOP principies

Refactoring of gadu_protocol module is now in cleanup mode. There are still some missing things, like DCC not supporting NAT connections, DCC configuration is not moved to Account settings page and so on...

I had some thoughts that I've got while I did the refactoring: about signals and slots overuse in gadu_protocol code. This Qt extension of C++ is supposed to help programmers with connecting events that happens on one object with actions that another object should perform when an event is triggered. The overuse occurs when the connection is always one-to-one and we know that no another object ever will try to connect to these slots or signals. Just like in gadu_protocol - classes like GaduProtocol, GaduProtocolSocketNotifiers, DccManager, DccSocketNotifiers were communicating with each other using this, but I think it is not the optimal solution. It needs connecting these objects (this is a lot of code to write!) and signals-slots invoking is really very slow (I know it is not a concern because of small frequency of these calls in protocol classes, but I still does not like this solution).

I've got into conclusion that these classes are really an one big class that is just split for convenience. So I've removed signal-slots where it was possible and made these classes each ones friends. They can now access private members of each other ;) In the result, it is easier now to understand how this whole group works together. We did not lost any of flexibility, because there was not any either (you cannot just extend a closed communication protocol like this).

This is not very good solution from view of OOP (no accessors, just friend classes), but it is the best one for my simple mind ;)

Monday, 2 March 2009

Sending files

Kadu trunk can now send and receive files. Dcc6 and Dcc7 are supported. NAT to NAT Dcc7 connections will not work (libgadu issue). I've not checked Dcc6 callback connections (public-ip to NAT). If anyone has Gadu Gadu 6.x and is behind NAT - please contact with me on Kadu forum, so we can test this too.

What is left? Notify-based questions (accept file/reject file) and merging new file transfers with old ones (like sending rest of the same file in another session).

Saturday, 28 February 2009

Receiving files

Gadu protocol can now receive files (again) - in Dcc6 mode ;) It now uses FileTransferService to do that. Now it needs support for incoming transfers from Gadu Gadu 7 and newer and support for outcoming transfers; file_transfer modue will get all functionality of old dcc module (like asking for accepting incoming connection) and everybody will be happy now.

I've also found (and fixed) some fundamental mistakes in whole SocketNotifiers code. I hope now file transfers will be more reliable (eg: will work 100% of the time).

Monday, 23 February 2009

New (alternative) configuration API

When I was updating Contacts API, then Accounts API and some more Kadu APIs ;) I've realized that current configuration API needs too much code to use. You basically have to use QXml to do anything more complicated that write one simple value (config_file.writeEntry() is not always good enough).

Contacts, Accounts, File Transfers are complicated structures that needs simple API to save/restore (some kind of serialization). This API is now provided by StorableObject class (I think I'll rename it to Persistent or PersistentObject in API-review time). Class that need to be stored (persistent) must inherit from StorableObject. It only requires reimplementation of one method: virtual StoragePoint * createStoragePoint() const, where StoragePoint class contains pointer to XmlConfigFile and QDomElement that is root node for storing this object's configuration.

For example, ContactData class implements it as:
StoragePoint *parent = ContactManager::instance()->storage();
if (!parent)
return 0;

QDomElement contactNode = parent->storage()->getUuidNode(parent->point(),
"Contact", Uuid.toString());
return new StoragePoint(parent->storage(), contactNode);
So we get new node under main ContactManager configuration tagged with the contact's Uuid. The storage() method is inherited and implemented in StorableObject class - it calls createStoragePoint when needed.

Implementation of createStoragePoint in ContactManager:
QDomElement contactsNewNode = xml_config_file->getNode("ContactsNew");
return new StoragePoint(xml_config_file, contactsNewNode);
It creates ContactsNew node under root node of main configuration file.

StorableObject supports external setting of StoragePoint - see setStorage() method.

When we have all of that nice stuff, we can store object. For example, FileTransfer storage looks like that:
void FileTransfer::storeConfiguration()
{
if (!isValidStorage())
return;

storeValue("Account", CurrentAccount->uuid().toString());
storeValue("Peer", Peer.uuid().toString());
storeValue("LocalFileName", LocalFileName);
storeValue("RemoteFileName", RemoteFileName);
storeValue("TransferType", TypeSend == TransferType ? "Send" : "Receive");
storeValue("FileSize", (qulonglong)FileSize);
storeValue("TransferredSize", (qulonglong)TransferredSize);
}
Condition !isValidStorage() check if storage point exist, if not, it creates it (with all the needed hierarchy). It if fails, it returns false - we cannot do anything here about it. After that we store all values needed to recreate object in future. storeValue() method of StorableObject class writes data to subnodes of StoragePoint of this object (BTW: this method is not completed yet, it does not store objects Uuid, and I think that should be done be StorableObject or StorableObjectWithUuid class).

Loading object is possible when setStorage() method was called from somewhere (object does not know anything about its StoragePoint before loading). Then we could do:
void FileTransfer::loadConfiguration()
{
if (!isValidStorage())
return;

CurrentAccount = AccountManager::instance()->byUuid(QUuid(loadValue<QString>("Account")));
Peer = ContactManager::instance()->byUuid(QUuid(loadValue<QString>("Peer")));
LocalFileName = loadValue<QString>("LocalFileName");
RemoteFileName = loadValue<QString>("RemoteFileName");
TransferType = ("Send" == loadValue<QString>("TransferType")) ? TypeSend : TypeReceive;
FileSize = loadValue<qulonglong>("FileSize");
TransferredSize = loadValue<qulonglong>("TransferredSize");
}
Everyting is as simple as possible.

And who sets the StoragePoint? FileTransferManager:
StoragePoint *fileTransferStoragePoint =
new StoragePoint(configurationStorage, fileTransferElement);
FileTransfer *fileTransfer =
FileTransfer::loadFromStorage(fileTransferStoragePoint);

The static method loadFromStorage does some magic to determine which Account this FileTransfer belongs to. Then it creates empty FileTransfer for this Account and calls something like this (for Gadu):
GaduFileTransfer *gft = new GaduFileTransfer(Protocol->account());
gft->setStorage(fileTransferStoragePoint);
gft->loadConfiguration();
Done.

Friday, 20 February 2009

Dcc Porting

I'm in hell now. I've got working ChatService, ChatImagerService, PersonalInfoService, ContactListService and almost-working SerachService... But the FileTransferService need so much porting of DCC module, that I'm starting to cry... The worst part is that I've rewritten it before.

The good news is that White Eagle is writing Adium chat styles support for Kadu ;)

Thursday, 12 February 2009

ULEs removed

I've finally managed to remove last uses of UserListElement, UserListElements, UserGroup, UserList in Kadu (not including modules, but it is work for post-alpha releases).

Uzi is working on new ContactDataWindow (for editing contacts). After that we can restore contact management functionality - it is hard to use IM without that :(

I'll work on removing UserStatus and renaming Status to Presence. After that - rest of Gadu-Gadu protocol refactorisations (FileTransferService, VoiceService, SearchService...).

Protocol services

Some more thoughts about refactorisation of Protocol and GaduProtocol classes. When I was extracting contact list export-import functionality from GaduProtocol, I've got an excellent idea about how-to-split the whole big class into smaller ones. It is very simple and widelly used in other projects (so why did't I thought about it before?).

We will have services: ContactListService, ChatService, FileTransferService, VoiceService and many others. Each of these will be an abstract class (call it an interface). Protocol class will implement methods like ChatService * chatService() - it will return 0 (no service implementation) by default. GaduProtocol for example will return GaduChatService object created in GaduProtocol's contructor.

Advantages? Smaller classes, possibility to query protocols for capabilities (if chatService() == 0 we cannot chat by this protocol, fileTransferService() == 0 - we cannot send/receive files).

Tuesday, 10 February 2009

Sometimes a function can became a class...

...and it happens after the function became too big (or complicated). Like account-management functions for GaduProtocol (registerAccount, unregisterAccount, remindPassword, changePassword). Each of these functions used getToken function and a Mode field of GaduProtocol object, so the getToken could choose right function to call after its work was done (it works asynchronically). And each of these has own slot!

Why I don't like this solution? Without true reason it adds a new state field to GaduProtocol class - Mode (Register, Unregister, RemindPassword, ChangePassword). It makes GaduProtocol do things that are not really involved with normal IM communication - account management. It uses copy-and-pasted code, becouse these methods are nearly identical.

So I've decided to promote each of this funtion to a class of its own. With, of course, one base class: GaduServerConnector (ok, the name suck, it will be changed when I found a better one). This class does most of the work: it connects to server, fetches token (captcha) image, asks user for token value (using external object for that, so method of asking can be changed) and calls abstract method performAction(tokenId, tokenValue). Classes like GaduServerRegisterAccount (this name sucks too) only implement performAction method (and should call finished(bool), so GaduServerConnector can emit a signal for higher level code).

For example (there is only one right now) GaduServerRegisterAccount implements performAction using gg_register3 libgadu function. It wraps the connection in GaduPubdirSocketNotifiers and connects to its signal: done(bool, gg_http *). So after registration if over, it receives that signal and sets its Uin field. The caller can then read GaduServerConnector::Result field (so it knows if it was successfully) and the GaduPubdirSocketNotifiers::Uin (so it knows id of the newly registered account). Voila.

It makes GaduProtocol smaller and the flow of Register/Unregister/RemindPassword/ChangePassword a lot simples (I hope so...).

BTW: it still don't work, it segfaults every time. But it is just a technical problem, not design one.

UPDATE: I've fixed the crash. Registration does not work because (as I think) we are no longer compatible with GG protocol. I'll fix that later.

Is there somebody who wants to promote last 3 functions to objects?

UPDATE 2: I've found error with registration - it was just sending 2x password instead of password and e-mail ;)

Tuesday, 3 February 2009

Time for GaduProtocol

Unfortunately it is impossible to do full ULE to ContactList transition without making some changes in Gadu-Gadu protocol implementation. Code responsible for exporting/importing list of contacts depends on old classes. So I had a look into gadu_protocol module and...

And I started refactoring it a bit. I know I've done it about year ago, but now I see that it was not well done: files and classes are too large. Some method also. Yesterday three classes were moved to their own files. Next step require removing all references to old UserStatus (and GaduUserStatus classes) and replacing it with the brand-new Status class.

But what was wrong with UserStatus? It does tasks that only Protocol class should have: it has slots, signals (for informing it was changed). Both protocol classes and Kadu main class conencted to it. The code flow:
  • Kadu: protocol->writeableStatus().setStatus(newStatus)
  • Status: emit goOnline("description")
  • Protocol: slot iWantGoOnline("description")
  • Protocol: communication with server
  • Protocol: CurrentStatus->setStatus(newStatus)
  • CurrentStatus emit goOnline("description")
  • Kadu: wentOnline("description")
Many unnecessary slots/signals. The right way to do it:
  • Kadu: protocol->setStatus(newStatus)
  • Protocol: communication with server
  • Protocol: emit statusChanged(newStatus)
We need the statusChanged signal, because the status setting can be asynchronous.

So the Status class is now very lightweight and does just what it should do - stores 2 pieces of data. Protocol and Kadu classes lost lot of signals and slots. And logic is much simpler ;)

After I sort out the Status class I'll look if splitting GaduProtocol into smaller classes (like GaduConnection) makes sense - I just like small classes ;)

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).