• Skip to content
  • Skip to link menu
  • KDE API Reference
  • KDE Home
  • Contact Us
 

Nepomuk-Core

  • KTp
  • Models
abstract-grouping-proxy-model.cpp
Go to the documentation of this file.
1 /*
2  * Turns a list model into a tree allowing nodes to be in multiple groups
3  *
4  * Copyright (C) 2012 David Edmundson <kde@davidedmundson.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "abstract-grouping-proxy-model.h"
22 
23 #include <QSet>
24 #include <QTimer>
25 
26 #include <KDebug>
27 
28 
29 class KTp::AbstractGroupingProxyModel::Private
30 {
31 public:
32  QAbstractItemModel *source;
33 
34  //keep a cache of what groups an item belongs to
35  QHash<QPersistentModelIndex, QSet<QString> > groupCache;
36 
37  //item -> groups
38  QMultiHash<QPersistentModelIndex, ProxyNode*> proxyMap;
39  QHash<QString, GroupNode*> groupMap;
40 };
41 
42 class ProxyNode : public QStandardItem
43 {
44 public:
45  ProxyNode(const QPersistentModelIndex &sourceIndex);
46  QVariant data(int role) const;
47  void changed(); //expose protected method in QStandardItem
48  QString group() const;
49 private:
50  const QPersistentModelIndex m_sourceIndex;
51 };
52 
53 class GroupNode : public QStandardItem {
54 public:
55  GroupNode(const QString &groupId);
56  QString group() const;
57  virtual QVariant data(int role) const;
58  bool forced() const;
59  void changed(); //expose protected method in QStandardItem
60  void setForced(bool forced);
61 private:
62  const QString m_groupId;
63  bool m_forced;
64 };
65 
66 
67 ProxyNode::ProxyNode(const QPersistentModelIndex &sourceIndex):
68  QStandardItem(),
69  m_sourceIndex(sourceIndex)
70 {
71 }
72 
73 QVariant ProxyNode::data(int role) const
74 {
75  return m_sourceIndex.data(role);
76 }
77 
78 void ProxyNode::changed()
79 {
80  QStandardItem::emitDataChanged();
81 }
82 
83 QString ProxyNode::group() const
84 {
85  //FIXME is this a hack?
86  GroupNode *groupNode = static_cast<GroupNode*>(parent());
87  if (groupNode) {
88  return groupNode->group();
89  }
90  return QString();
91 }
92 
93 
94 
95 GroupNode::GroupNode(const QString &groupId):
96  QStandardItem(),
97  m_groupId(groupId),
98  m_forced(false)
99 {
100 }
101 
102 QString GroupNode::group() const
103 {
104  return m_groupId;
105 }
106 
107 QVariant GroupNode::data(int role) const
108 {
109  KTp::AbstractGroupingProxyModel *proxyModel = qobject_cast<KTp::AbstractGroupingProxyModel*>(model());
110  Q_ASSERT(proxyModel);
111  return proxyModel->dataForGroup(m_groupId, role);
112 }
113 
114 void GroupNode::setForced(bool forced)
115 {
116  m_forced = forced;
117 }
118 
119 bool GroupNode::forced() const
120 {
121  return m_forced;
122 }
123 
124 void GroupNode::changed()
125 {
126  QStandardItem::emitDataChanged();
127 }
128 
129 
130 KTp::AbstractGroupingProxyModel::AbstractGroupingProxyModel(QAbstractItemModel *source):
131  QStandardItemModel(source),
132  d(new KTp::AbstractGroupingProxyModel::Private())
133 {
134  d->source = source;
135  setRoleNames(source->roleNames());
136 
137  //we have to process all existing rows in the model, but must never call a virtual method from a constructor as it will crash.
138  //we use a single shot timer to get round this
139  QTimer::singleShot(0, this, SLOT(onLoad()));
140 }
141 
142 KTp::AbstractGroupingProxyModel::~AbstractGroupingProxyModel()
143 {
144  delete d;
145 }
146 
147 
148 void KTp::AbstractGroupingProxyModel::forceGroup(const QString &group)
149 {
150  GroupNode* groupNode = itemForGroup(group);
151  groupNode->setForced(true);
152 }
153 
154 void KTp::AbstractGroupingProxyModel::unforceGroup(const QString &group)
155 {
156  GroupNode* groupNode = d->groupMap[group];
157  if (!groupNode) {
158  return;
159  }
160 
161  //mark that this group can be removed when it's empty
162  groupNode->setForced(false);
163 
164  //if group is already empty remove it
165  if (groupNode->rowCount() == 0) {
166  takeRow(groupNode->row());
167  d->groupMap.remove(groupNode->group());
168  }
169 }
170 
171 void KTp::AbstractGroupingProxyModel::groupChanged(const QString &group)
172 {
173  GroupNode *node = d->groupMap[group];
174  if (node) {
175  node->changed();
176  }
177 }
178 
179 
180 /* Called when source items inserts a row
181  *
182  * For each new row, create a proxyNode.
183  * If it's at the top level add it to the relevant group nodes.
184  * Otherwise add it to the relevant proxy nodes in our model
185  */
186 
187 void KTp::AbstractGroupingProxyModel::onRowsInserted(const QModelIndex &sourceParent, int start, int end)
188 {
189  //if top level in root model
190  if (!sourceParent.parent().isValid()) {
191  for (int i = start; i <= end; i++) {
192  QModelIndex index = d->source->index(i, 0, sourceParent);
193  Q_FOREACH(const QString &group, groupsForIndex(index)) {
194  addProxyNode(index, itemForGroup(group));
195  }
196  }
197  } else {
198  for (int i = start; i <= end; i++) {
199  QModelIndex index = d->source->index(i, 0, sourceParent);
200  QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
201  while (it != d->proxyMap.end() && it.key() == index) {
202  addProxyNode(index, it.value());
203  it++;
204  }
205  }
206  }
207 }
208 
209 void KTp::AbstractGroupingProxyModel::addProxyNode(const QModelIndex &sourceIndex, QStandardItem *parent)
210 {
211  ProxyNode *proxyNode = new ProxyNode(sourceIndex);
212  d->proxyMap.insertMulti(sourceIndex, proxyNode);
213  parent->appendRow(proxyNode);
214 }
215 
216 void KTp::AbstractGroupingProxyModel::removeProxyNodes(const QModelIndex &sourceIndex, const QList<ProxyNode *> &removedItems)
217 {
218  Q_FOREACH(ProxyNode *proxy, removedItems) {
219  QStandardItem *parentItem = proxy->parent();
220  parentItem->removeRow(proxy->row());
221  d->proxyMap.remove(sourceIndex, proxy);
222 
223  //if the parent item to this proxy node is now empty, and is a top level item
224  if (parentItem->rowCount() == 0 && parentItem->parent() == 0 ) {
225  GroupNode* groupNode = dynamic_cast<GroupNode*>(parentItem);
226 
227  //do not delete forced groups
228  if (groupNode->forced() == false) {
229  takeRow(groupNode->row());
230  d->groupMap.remove(groupNode->group());
231  }
232  }
233  }
234 }
235 
236 /*
237  * Called when a row is remove from the source model model
238  * Find all existing proxy models and delete thems
239 */
240 void KTp::AbstractGroupingProxyModel::onRowsRemoved(const QModelIndex &sourceParent, int start, int end)
241 {
242  for (int i = start; i<=end; i++) {
243  QPersistentModelIndex index = d->source->index(i, 0, sourceParent);
244  QList<ProxyNode *> itemsToRemove;
245 
246  QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
247  while (it != d->proxyMap.end() && it.key() == index) {
248  kDebug() << "removing row" << index.data();
249  itemsToRemove.append(it.value());
250  ++it;
251  }
252  d->groupCache.remove(index);
253  removeProxyNodes(index, itemsToRemove);
254  }
255 }
256 
257 /*
258  * Called when source model changes data
259  * If it's the top level item in the source model detect if the groups have changed, if so update as appropriately
260  * Find all proxy nodes, and make dataChanged() get emitted
261  */
262 
263 void KTp::AbstractGroupingProxyModel::onDataChanged(const QModelIndex &sourceTopLeft, const QModelIndex &sourceBottomRight)
264 {
265  for (int i = sourceTopLeft.row(); i <= sourceBottomRight.row(); i++) {
266  QPersistentModelIndex index = d->source->index(i, 0, sourceTopLeft.parent());
267 
268  //if top level item
269  if (!sourceTopLeft.parent().isValid()) {
270  //groupsSet has changed...update as appropriate
271  QSet<QString> itemGroups = groupsForIndex(d->source->index(i, 0, sourceTopLeft.parent()));
272  if (d->groupCache[index] != itemGroups) {
273  d->groupCache[index] = itemGroups;
274 
275  //loop through existing proxy nodes, and check each one is still valid.
276  QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
277  QList<ProxyNode*> removedItems;
278  while (it != d->proxyMap.end() && it.key() == index) {
279  // if proxy's group is still in the item's groups.
280  if (itemGroups.contains(it.value()->group())) {
281  itemGroups.remove(it.value()->group());
282  } else {
283  //remove the proxy item
284  //cache to list and remove once outside the const_iterator
285  removedItems.append(it.value());
286 
287  kDebug() << "removing " << index.data().toString() << " from group " << it.value()->group();
288  }
289  ++it;
290  }
291 
292  removeProxyNodes(index, removedItems);
293 
294  //remaining items in itemGroups are now the new groups
295  Q_FOREACH(const QString &group, itemGroups) {
296  ProxyNode *proxyNode = new ProxyNode(index);
297  d->proxyMap.insertMulti(index, proxyNode);
298  itemForGroup(group)->appendRow(proxyNode);
299 
300  kDebug() << "adding " << index.data().toString() << " to group " << group;
301  }
302  }
303  }
304 
305  //mark all proxy nodes as changed
306  QHash<QPersistentModelIndex, ProxyNode*>::const_iterator it = d->proxyMap.find(index);
307  while (it != d->proxyMap.end() && it.key() == index) {
308  it.value()->changed();
309  ++it;
310  }
311  }
312 }
313 
314 
315 void KTp::AbstractGroupingProxyModel::onLoad()
316 {
317  if (d->source->rowCount() > 0) {
318  onRowsInserted(QModelIndex(), 0, d->source->rowCount()-1);
319  }
320  connect(d->source, SIGNAL(modelReset()), SLOT(onModelReset()));
321  connect(d->source, SIGNAL(rowsInserted(QModelIndex, int,int)), SLOT(onRowsInserted(QModelIndex,int,int)));
322  connect(d->source, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(onRowsRemoved(QModelIndex,int,int)));
323  connect(d->source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(onDataChanged(QModelIndex,QModelIndex)));
324 }
325 
326 /* Called when source model gets reset
327  * Delete all local caches/maps and wipe the current QStandardItemModel
328  */
329 
330 void KTp::AbstractGroupingProxyModel::onModelReset()
331 {
332  clear();
333  d->groupCache.clear();
334  d->proxyMap.clear();
335  d->groupMap.clear();
336  kDebug() << "reset";
337 
338  if (d->source->rowCount() > 0) {
339  onRowsInserted(QModelIndex(), 0, d->source->rowCount()-1);
340  }
341 }
342 
343 GroupNode* KTp::AbstractGroupingProxyModel::itemForGroup(const QString &group)
344 {
345  if (d->groupMap.contains(group)) {
346  return d->groupMap[group];
347  } else {
348  GroupNode* item = new GroupNode(group);
349  appendRow(item);
350  d->groupMap[group] = item;
351  return item;
352  }
353 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Fri Mar 22 2013 10:58:52 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

ktp-common-internals API Reference

Skip menu "ktp-common-internals API Reference"
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal