Wednesday, 29 August 2012

Merged Proxy Model

Kadu IM blog is now part of Qt Planet. This is good time to start sharing some of our solutions with community.

Merged Proxy Model

This is the thing I'm most proud of - a model that can merge any number of other models into one. In Kadu we use it in for:
  • display actions above/below items in QComboBox widgdets
  • display list of Chat and Buddy objects in our roster widget
The second usage forces us to use a complicated structure or models and proxy model, as we have one Chat instance for every Buddy in addition to other instances. So before merging these models we apply filter proxy models that removes one-buddy chats from it. Then we apply more filtering/sorting proxy models on merged model just to be nice for our users.

Features

This proxy model not support everything - for example we haven't tested how it works with multiple columns or submodels with different number of columns. It also does not allows to plug in a model under a subnode of other model - all models are on top level.

It supports following features:
  • updating list of submodels on demand
  • inserting and removing rows (no support for moving)
  • emiting all row and data based signals properly
  • proper mapping to source and from source indexes
  • copying mime daya from submodels
So it is pretty usefull already.

Implementation

I must say that is was very hard piece of work. MergedProxyModel has about 450 lines of code and we have KaduMergedProxyModel on top of it (to implement reverse mapping from values to multiple indexes).

Two basic concepts are used: boundaries list that stores begin and end row index of all submodels in merged model. This value is updated every time a row is inserted or removed. Second one is mapping that maps QModelIndex instances from source to proxy model.

Code

If you want more details or the code please look at our git repository: Kadu on Gitorious. This file is most important: merged-proxy-model.h. Feel free to download and change it. And please send us any update that will make this code better.

7 comments:

  1. I would like to use code. but cant its GPL

    please make it LGPL

    ReplyDelete
    Replies
    1. Hello. I've not seen that comment before.
      Please send me e-mail to vogel@kadu.im, I'll check what I can do with making it LGPL (must check real authors of code, pretty sure it was only me, maybe +one person).

      Delete
  2. I'm also interested ... Any updates regrading making it LGPL?

    ReplyDelete
    Replies
    1. Hello. I've not seen that comment before.
      Please send me e-mail to vogel@kadu.im, I'll check what I can do with making it LGPL (must check real authors of code, pretty sure it was only me, maybe +one person).

      Delete
    2. Found all authors - it is now LGPL.

      Delete
  3. Found a crash.. I fixed it using dirty hack. I don't why it happens.


    QModelIndex MergedProxyModel::mapFirstLevelToSource(const QModelIndex &proxyIndex) const
    {
    if (!proxyIndex.isValid()) // invalid maps to invalid
    return QModelIndex();

    Q_ASSERT(this == proxyIndex.model());

    ModelMap::const_iterator i = Boundaries.constBegin();
    ModelMap::const_iterator end = Boundaries.constEnd();

    int row = proxyIndex.row();
    while (i != end)
    {
    if (row >= i.value().first && row <= i.value().second)
    {
    if(!i.key())
    {
    // i dn't know why it happens and when it happens. Its just never comes...just rare and happens once or twice
    qDebug() << "crash here so if there is no key return/... why this hap";
    return QModelIndex();
    }
    return i.key()->index(row - i.value().first, proxyIndex.column());
    }
    i++;
    }

    Q_ASSERT(false); // we should never be here
    return QModelIndex();
    }

    ReplyDelete