So GSoC ended already, as most of you already know. I haven’t been blogging as much as I would like, and I didn’t achieve to finish on time everything I wanted, but that’s not a defeat, only a delay – I will continue working on this bookmarks system until it can get merged into trunk. And then I’ll fix incomintheg related bug reports =).
So the state of the art is: we’ve got an akonadi resource for konqueror bookmarks which stores the bookmarks in nepomuk. We’ve got a bookmarks organizer, and we’ve got a bookmarks menu integrated in konqueror. The new location bar is however not finished yet, but that will be fixed within days.
A lot has changed since last report. I’ll try to explain here what’s the base structure of the new classes and what I’ve been doing, mainly related to the location bar, and why I followed that design decisions.
The current konqueror location bar uses a KCompletion class for autocompletion, which is mainly handled by KLineEdit. But how does it work? Let’s use a simple example. Imagine you are developing kmail’s new email dialog. For the line in which the user enters the destination email address, you could use a KLineEdit, but in order to easy the task of the user, you could use autocompletion like this:
KLineEdit* destinationLineEdit = new KLineEdit(this);
You could of course get the list of items to add to the completion object from the address list too ;-). If you were developing instead the location bar for dolphin, you would like to have directories listed while the user is typing. Instead of adding and removing items manually yourself to the completion each time the user types, you can use a KUrlCompletion object that does that automatically.
But for konqueror location bar we have a problem: it needs to be able to use kurlcompletion for completing directories, but it also needs to complete from bookmarks and history. We need to work with multiple completion objects at once even though KLineEdit and similar classes can work only with one. Also, we need to do more complex completion A normal KCompletion object contains a list of strings and matches what you type with those, but for having an amazing location bar we need more power: if I type “work” and I have a bookmark tagged “work”, I want it to show up in the completion list even if its URL doesn’t contain the word “work” at all. And that’s only the beginning. I want to be able to set an order of the completed items depending on the relevance and type of the items, and more..
Enter Qt Model-views
I need to confess that I love Qt model-views classes. QAbstractItemModel, QAbstractItemView, QAbstractProxyModel, QSortFilterProxyModel, QTreeView.. They provide an standard convinient and flexible way to manage and display almost any kind of collections. Even collections of completed items. Actually, the completed items popup is in reality a QListWidget, which displays a model associated with the completion object..
But that is an internal model which KLineEdit doesn’t let me change. So the question that follows is.. why not use directly my own custom item models instead of a KCompletion object? And that’s what I did, even if it required a lot of work to be done.
First of all, I tried to outline in paper the master plan. What classes needed to be created for everything to work fine. Then I wrote step by step what needed to be done before what. Then I started following those steps one by one and it worked!
Places all over the.. place
The plan was that the location bar autocompletes places. A place could be:
- a history entry from history entries model
- a bookmark from bookmarks model
- an url from a kurlcompletion model
I had already a bookmarks model and konqueror already has an history entries model, but I had no kurlcompletion model. I looked at the code of the KUrlCompletion class I decided that I didn’t want to rewrite it.. so I simply created a KCompletionModel which acts as a proxy and converts a KCompletion object in a QAbstractItemModel. So to create a url completion model, I do:
KUrlCompletion* urlCompletion = new KUrlCompletion();
KCompletionModel* urlCompletionModel = new KCompletionModel();
To do a completion, I connect the urlCompletion to the textChanged(QString) signal from the lineedit. And the completion objects reflects the changes which in turn instantly appear in the model. It’s not the best solution but hey it works.
Also, another problem was that the completion object of KLineEdit would be replaced by a model, not three. But now as you see I have again multiple completion objects, so what was the solution? creating an aggregated model created out of multiple models. It works like this:
KAggregatedModel* aggregatedModel = new KAggregatedModel(this);
Now, those who know how an item model works will probably have a lot of questions about that. For example one could be.. Do all those models need to share the same columns? The answer is no. The aggregated model I created is quite simplistic in the way it works:
- It assumes the source models have only one level of childrens.
- It only shows one column, which is the default display column (sourceIndex.data(role)).
- It shows the list of items as a list, showing first the items from the first item model, then the ones from the second, etc.
So far we have an aggregated model with completed urls, unfiltered bookmarks and unfiltered history entries. That’s yet not complete from being the amazing completer model. What we need to do is the amazing filtering and sorting. That’s done by the final completion model, the master of places..
It’s called PlacesProxyModel and inherits QSortFilterProxyModel. It takes a QAbstractIteModel and takes the Konqueror::PlaceUrlRole for each index, gets the URL, and obtains the corresponding Konqueror::Place for it. Then, knowing already all the available information for that place, tries to match it against the query the user entered in the lineedit and sets its relevance for sorting purposes. It also filters out duplicated entries. Quite an achievement, but how does all that work again?
First off, all previous models (url completion, bookmarks and history entries models) report their items to a Places Manager which keeps track of them and contains a Place for each relevant url. So for example, if the user has bookmark for http://www.kde.org and it’s been visited yesterday, there’s a place in the places manager with the information from both the bookmark and the history entry.
All those models also support obtaining the url related to each item by using the Konqueror::PlaceUrlRole. The aggregated model proxies calls to retrieve data from any role, including that. So in the end the information can be retrieved by the places proxy model. Then an algorithm that takes into account the number of visits, the last visit, if the user-written string matched the place’s url, title or tags, etc sets the relevance of the items in the proxy model.
The road to the location bar
The new location bar inherits from the modified KLineEdit which uses a QAbstractItemModel for completion, which I have named KLineEditView. An amazing location bar needs to be able to show an star that can be clicked to add/remove a bookmark, and the autocompletion needs to show for each place shown if it’s bookmarked, its tags, etc. Contextual information.
State of the art
What has been done? All the previous things I have mentioned are working. They can always improve, but the code is already there. The location bar widget is the last thing I started to write so it’s not finished: it has already plugins support so that new icons/sub-widgets can be shown inside the location bar. Now I need to write the plugin to let the user bookmark the current location, and I also need to write the CompletionPlaceDelegate to have a properly eye-candied completion list. And I’ll do it shortly.
Future and end
Unfortunately, a lot of things need to be done before this new bookmarks system ends up in konqueror trunk. Revamping such an integral part of Konqi is not a simple task. We also want to be sure that when the replacements comes into play the user doesn’t have to suffer it but to enjoy it instead, so I need to find fix and all the regressions. I need to write documentation and test cases too. I honestly don’t really know when the job will be done but I know I’ll continue working on it.
I want to thank specially my mentor David Faure for the support, for giving me a thumbs up even if I didn’t finish on time everything I wrote in my gsoc propossal. You rock david!
This is post has been too large I admit, so I think I’ll stop here :D. If you read everything yay you’ve got too much free time so go and do something more useful!