Advanced example of Model/View

History in a web browser has a number of challenges that make it a good demonstration of Qt's model/view. The history is used in the url completer, the history dialog, the history menu and lastly by WebKit itself for determining if you have been somewhere before. And just to add to the mix it need to be able to scale. A year of history could easily be 20,000+ entries. The History component of the demo browser is more advanced then the examples in Qt and shows off some more the cool aspects of model/view and it has been suggested to me that writing up a little description of how it works might be useful to some developers looking to use model/view in their own applications.

I have spent time working on item view and know it pretty well (I put together the Modeltest and ItemView Tips) so the history component of the demo browser was fun to write. The source code for all the classes can be found in demos/browser/history*.

The first class is the history manager who's data structure is optimized for history, not for dealing with item views. The class loads and stores the history, emits signals when there is a new entry or one is removed and that is about it. This class should be able to stand on its own if it was pulled out and used without itemview. I wanted to keep the memory usage down as much as possible. For 20K entries it was only using around 10MB of ram when I was done.

On top of the HistoryManager is the first model, a QAbstractTableModel (HistoryModel) who's only job is to wrap the history model. It doesn't do anything other then implementing functions such as rowCount(), flags(), and data(), matching up the itemview functionality with the corresponding functionality in the history model.

On top of that is a QAbstractProxyModel (HistoryFilterModel) who job it is to filter out duplicates. Looking at browsers I noticed that if you visit a link twice the browsers always hide everything, but the most recent visit. A hash is used to determine if a link has been visited along with the most recent location of the visit. This model has a convenience function that is used by WebKit to determine if a link has been visited.

On top of HistoryFilterModel is another QAbstractProxyModel (CompleterModel) that adds a new row for every entry stripping the front of the url so the completer can complete on "" or "". This model is used by the QCompleter in the url line edit.

For the history menu and the history dialog the browser wants to display a tree based structure where each top level node is a Date with history entries for that date as the children. So on top of HistoryFilterModel I wrote a new QAbstractProxyModel (HistoryTreeModel) that converted the table into a tree. Converting the table into a tree was a little tricky, but the modeltest was very useful for detecting problems.

Before putting the HistoryTreeModel into the dialog I wrapped it one last time with a QSortFilterProxyModel so that the user can do on the fly searching. The searching (really filtering) is very fast. This is put into the HistoryDialog that has a EditTreeView which is a normal QTreeView, but with added support for the delete key to remove one row from the model.

On top of the HistoryTreeModel there is another QAbstractProxyModel (HistoryMenuModel) that slightly modifies the tree moving the most recent history entries inside of the root rather the first date folder. To do the mapping I translate each node in the tree all the way up to the table and back again to convert to the source index. This class was just a big pain to code and took a solid day to do. Looking back adding a hack to the menu rather then writing this class seems like a reasonable option given the effort.

Both history and bookmarks show up in the top level menu so I wrote a subclass of QMenu (ModelMenu) which can take a QAbstractItemModel and on the fly the ModelMenu will populate it with actions from the model. This is pretty straight forward and works very well.

Overall the History classes show off how you can use item view to to write some pretty cool stuff that scales too. History's classes contains a wrapper, some classes that filter out items, some classes that add new entires, modify or even move items around and lastly some views the present the data in very different ways.

Using my inkscape skills (or lack of) here is a diagram of the classes to help make better sense of the relationships.


Blog Topics: