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

marble

  • sources
  • kde-4.14
  • kdeedu
  • marble
  • src
  • lib
  • marble
kdescendantsproxymodel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 2009 Stephen Kelly <steveire@gmail.com>
3  Copyright (C) 2010 Klarälvdalens Datakonsult AB,
4  a KDAB Group company, info@kdab.net,
5  author Stephen Kelly <stephen@kdab.com>
6 
7  This library is free software; you can redistribute it and/or modify it
8  under the terms of the GNU Library General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version.
11 
12  This library is distributed in the hope that it will be useful, but WITHOUT
13  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15  License for more details.
16 
17  You should have received a copy of the GNU Library General Public License
18  along with this library; see the file COPYING.LIB. If not, write to the
19  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  02110-1301, USA.
21 */
22 
23 #include "kdescendantsproxymodel.h"
24 
25 #include <QStringList>
26 #include <QTimer>
27 
28 #include "kbihash_p.h"
29 
30 typedef KHash2Map<QPersistentModelIndex, int> Mapping;
31 
32 class KDescendantsProxyModelPrivate
33 {
34  KDescendantsProxyModelPrivate(KDescendantsProxyModel * qq)
35  : q_ptr(qq),
36  m_rowCount(0),
37  m_ignoreNextLayoutAboutToBeChanged(false),
38  m_ignoreNextLayoutChanged(false),
39  m_relayouting(false),
40  m_displayAncestorData( false ),
41  m_ancestorSeparator( QLatin1String( " / " ) )
42  {
43  }
44 
45  Q_DECLARE_PUBLIC(KDescendantsProxyModel)
46  KDescendantsProxyModel * const q_ptr;
47 
48  mutable QVector<QPersistentModelIndex> m_pendingParents;
49 
50  void scheduleProcessPendingParents() const;
51  void processPendingParents();
52 
53  void synchronousMappingRefresh();
54 
55  void updateInternalIndexes(int start, int offset);
56 
57  void resetInternalData();
58 
59  void sourceRowsAboutToBeInserted(const QModelIndex &, int, int);
60  void sourceRowsInserted(const QModelIndex &, int, int);
61  void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int);
62  void sourceRowsRemoved(const QModelIndex &, int, int);
63  void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int);
64  void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int);
65  void sourceModelAboutToBeReset();
66  void sourceModelReset();
67  void sourceLayoutAboutToBeChanged();
68  void sourceLayoutChanged();
69  void sourceDataChanged(const QModelIndex &, const QModelIndex &);
70  void sourceModelDestroyed();
71 
72  Mapping m_mapping;
73  int m_rowCount;
74  QPair<int, int> m_removePair;
75  QPair<int, int> m_insertPair;
76 
77  bool m_ignoreNextLayoutAboutToBeChanged;
78  bool m_ignoreNextLayoutChanged;
79  bool m_relayouting;
80 
81  bool m_displayAncestorData;
82  QString m_ancestorSeparator;
83 
84  QList<QPersistentModelIndex> m_layoutChangePersistentIndexes;
85  QModelIndexList m_proxyIndexes;
86 };
87 
88 void KDescendantsProxyModelPrivate::resetInternalData()
89 {
90  m_rowCount = 0;
91  m_mapping.clear();
92  m_layoutChangePersistentIndexes.clear();
93  m_proxyIndexes.clear();
94 }
95 
96 void KDescendantsProxyModelPrivate::synchronousMappingRefresh()
97 {
98  m_rowCount = 0;
99  m_mapping.clear();
100  m_pendingParents.clear();
101 
102  m_pendingParents.append(QModelIndex());
103 
104  m_relayouting = true;
105  while (!m_pendingParents.isEmpty())
106  {
107  processPendingParents();
108  }
109  m_relayouting = false;
110 }
111 
112 void KDescendantsProxyModelPrivate::scheduleProcessPendingParents() const
113 {
114 // Q_Q(const KDescendantsProxyModel);
115  const_cast<KDescendantsProxyModelPrivate*>(this)->processPendingParents();
116 }
117 
118 void KDescendantsProxyModelPrivate::processPendingParents()
119 {
120  Q_Q(KDescendantsProxyModel);
121  const QVector<QPersistentModelIndex>::iterator begin = m_pendingParents.begin();
122  QVector<QPersistentModelIndex>::iterator it = begin;
123 
124  // Process chunkSize elements per invocation.
125 // static const int chunkSize = 30;
126 
127  const QVector<QPersistentModelIndex>::iterator end =
128  /* (m_pendingParents.size() > chunkSize) ? begin + chunkSize : */ m_pendingParents.end();
129 
130  QVector<QPersistentModelIndex> newPendingParents;
131 
132  while (it != end && it != m_pendingParents.end()) {
133  const QModelIndex sourceParent = *it;
134  if (!sourceParent.isValid() && m_rowCount > 0)
135  {
136  // It was removed from the source model before it was inserted.
137  it = m_pendingParents.erase(it);
138  continue;
139  }
140  const int rowCount = q->sourceModel()->rowCount(sourceParent);
141 
142  Q_ASSERT(rowCount > 0);
143  const QPersistentModelIndex sourceIndex = q->sourceModel()->index(rowCount - 1, 0, sourceParent);
144 
145  Q_ASSERT(sourceIndex.isValid());
146 
147  const QModelIndex proxyParent = q->mapFromSource(sourceParent);
148 
149  Q_ASSERT(sourceParent.isValid() == proxyParent.isValid());
150  const int proxyEndRow = proxyParent.row() + rowCount;
151  const int proxyStartRow = proxyEndRow - rowCount + 1;
152 
153  if (!m_relayouting)
154  q->beginInsertRows(QModelIndex(), proxyStartRow, proxyEndRow);
155 
156  updateInternalIndexes(proxyStartRow, rowCount);
157  m_mapping.insert(sourceIndex, proxyEndRow);
158  it = m_pendingParents.erase(it);
159  m_rowCount += rowCount;
160 
161  if (!m_relayouting)
162  q->endInsertRows();
163 
164  for (int sourceRow = 0; sourceRow < rowCount; ++sourceRow ) {
165  static const int column = 0;
166  const QModelIndex child = q->sourceModel()->index(sourceRow, column, sourceParent);
167  Q_ASSERT(child.isValid());
168 
169  if (q->sourceModel()->hasChildren(child))
170  {
171  Q_ASSERT(q->sourceModel()->rowCount(child) > 0);
172  newPendingParents.append(child);
173  }
174  }
175  }
176  m_pendingParents += newPendingParents;
177  if (!m_pendingParents.isEmpty())
178  processPendingParents();
179 // scheduleProcessPendingParents();
180 }
181 
182 void KDescendantsProxyModelPrivate::updateInternalIndexes(int start, int offset)
183 {
184  // TODO: Make KHash2Map support key updates and do this backwards.
185  QHash<int, QPersistentModelIndex> updates;
186  {
187  Mapping::right_iterator it = m_mapping.rightLowerBound(start);
188  const Mapping::right_iterator end = m_mapping.rightEnd();
189 
190  while (it != end)
191  {
192  updates.insert(it.key() + offset, *it);
193  ++it;
194  }
195  }
196 
197  {
198  QHash<int, QPersistentModelIndex>::const_iterator it = updates.constBegin();
199  const QHash<int, QPersistentModelIndex>::const_iterator end = updates.constEnd();
200 
201  for ( ; it != end; ++it)
202  {
203  m_mapping.insert(it.value(), it.key());
204  }
205  }
206 
207 }
208 
209 KDescendantsProxyModel::KDescendantsProxyModel(QObject *parent)
210  : QAbstractProxyModel(parent), d_ptr(new KDescendantsProxyModelPrivate(this))
211 {
212 }
213 
214 KDescendantsProxyModel::~KDescendantsProxyModel()
215 {
216  delete d_ptr;
217 }
218 
219 QModelIndexList KDescendantsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
220 {
221  return QAbstractProxyModel::match(start, role, value, hits, flags);
222 }
223 
224 void KDescendantsProxyModel::setDisplayAncestorData( bool display )
225 {
226  Q_D(KDescendantsProxyModel);
227  d->m_displayAncestorData = display;
228 }
229 
230 bool KDescendantsProxyModel::displayAncestorData() const
231 {
232  Q_D(const KDescendantsProxyModel );
233  return d->m_displayAncestorData;
234 }
235 
236 void KDescendantsProxyModel::setAncestorSeparator( const QString &separator )
237 {
238  Q_D(KDescendantsProxyModel);
239  d->m_ancestorSeparator = separator;
240 }
241 
242 QString KDescendantsProxyModel::ancestorSeparator() const
243 {
244  Q_D(const KDescendantsProxyModel );
245  return d->m_ancestorSeparator;
246 }
247 
248 
249 void KDescendantsProxyModel::setSourceModel(QAbstractItemModel *_sourceModel)
250 {
251  Q_D(const KDescendantsProxyModel );
252  beginResetModel();
253 
254  if (sourceModel()) {
255  disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
256  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
257  disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
258  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
259  disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
260  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
261  disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
262  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
263 // disconnect(sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
264 // this, SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
265 // disconnect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
266 // this, SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)));
267  disconnect(sourceModel(), SIGNAL(modelAboutToBeReset()),
268  this, SLOT(sourceModelAboutToBeReset()));
269  disconnect(sourceModel(), SIGNAL(modelReset()),
270  this, SLOT(sourceModelReset()));
271  disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
272  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
273  disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()),
274  this, SLOT(sourceLayoutAboutToBeChanged()));
275  disconnect(sourceModel(), SIGNAL(layoutChanged()),
276  this, SLOT(sourceLayoutChanged()));
277  disconnect(sourceModel(), SIGNAL(destroyed()),
278  this, SLOT(sourceModelDestroyed()));
279  }
280 
281  QAbstractProxyModel::setSourceModel(_sourceModel);
282  if ( sourceModel()->hasChildren() )
283  {
284  Q_ASSERT( sourceModel()->rowCount() > 0 );
285  const_cast<KDescendantsProxyModelPrivate*>(d)->synchronousMappingRefresh();
286  }
287 
288  if (_sourceModel) {
289  connect(_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
290  SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
291  connect(_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
292  SLOT(sourceRowsInserted(QModelIndex,int,int)));
293  connect(_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
294  SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
295  connect(_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
296  SLOT(sourceRowsRemoved(QModelIndex,int,int)));
297 // connect(_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
298 // SLOT(sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
299 // connect(_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
300 // SLOT(sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)));
301  connect(_sourceModel, SIGNAL(modelAboutToBeReset()),
302  SLOT(sourceModelAboutToBeReset()));
303  connect(_sourceModel, SIGNAL(modelReset()),
304  SLOT(sourceModelReset()));
305  connect(_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
306  SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
307  connect(_sourceModel, SIGNAL(layoutAboutToBeChanged()),
308  SLOT(sourceLayoutAboutToBeChanged()));
309  connect(_sourceModel, SIGNAL(layoutChanged()),
310  SLOT(sourceLayoutChanged()));
311  connect(_sourceModel, SIGNAL(destroyed()),
312  SLOT(sourceModelDestroyed()));
313  }
314 
315  endResetModel();
316 }
317 
318 QModelIndex KDescendantsProxyModel::parent(const QModelIndex &index) const
319 {
320  Q_UNUSED(index)
321  return QModelIndex();
322 }
323 
324 bool KDescendantsProxyModel::hasChildren(const QModelIndex &parent) const
325 {
326  Q_D(const KDescendantsProxyModel);
327  return !(d->m_mapping.isEmpty() || parent.isValid());
328 }
329 
330 int KDescendantsProxyModel::rowCount(const QModelIndex &parent) const
331 {
332  Q_D(const KDescendantsProxyModel);
333  if (d->m_pendingParents.contains(parent) || parent.isValid() || !sourceModel())
334  return 0;
335 
336  if (d->m_mapping.isEmpty() && sourceModel()->hasChildren())
337  {
338  Q_ASSERT(sourceModel()->rowCount() > 0);
339  const_cast<KDescendantsProxyModelPrivate*>(d)->synchronousMappingRefresh();
340  }
341  return d->m_rowCount;
342 }
343 
344 QModelIndex KDescendantsProxyModel::index(int row, int column, const QModelIndex &parent) const
345 {
346  if (parent.isValid())
347  return QModelIndex();
348 
349  if (!hasIndex(row, column, parent))
350  return QModelIndex();
351 
352  return createIndex(row, column);
353 }
354 
355 QModelIndex KDescendantsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
356 {
357  Q_D(const KDescendantsProxyModel);
358  if (d->m_mapping.isEmpty() || !proxyIndex.isValid() || !sourceModel())
359  return QModelIndex();
360 
361  const Mapping::right_const_iterator result = d->m_mapping.rightLowerBound(proxyIndex.row());
362  Q_ASSERT(result != d->m_mapping.rightEnd());
363 
364  const int proxyLastRow = result.key();
365  const QModelIndex sourceLastChild = result.value();
366  Q_ASSERT(sourceLastChild.isValid());
367 
368  // proxyLastRow is greater than proxyIndex.row().
369  // sourceLastChild is vertically below the result we're looking for
370  // and not necessarily in the correct parent.
371  // We travel up through its parent hierarchy until we are in the
372  // right parent, then return the correct sibling.
373 
374  // Source: Proxy: Row
375  // - A - A - 0
376  // - B - B - 1
377  // - C - C - 2
378  // - D - D - 3
379  // - - E - E - 4
380  // - - F - F - 5
381  // - - G - G - 6
382  // - - H - H - 7
383  // - - I - I - 8
384  // - - - J - J - 9
385  // - - - K - K - 10
386  // - - - L - L - 11
387  // - - M - M - 12
388  // - - N - N - 13
389  // - O - O - 14
390 
391  // Note that L, N and O are lastChildIndexes, and therefore have a mapping. If we
392  // are trying to map G from the proxy to the source, We at this point have an iterator
393  // pointing to (L -> 11). The proxy row of G is 6. (proxyIndex.row() == 6). We seek the
394  // sourceIndex which is vertically above L by the distance proxyLastRow - proxyIndex.row().
395  // In this case the verticalDistance is 5.
396 
397  int verticalDistance = proxyLastRow - proxyIndex.row();
398 
399  // We traverse the ancestors of L, until we can index the desired row in the source.
400 
401  QModelIndex ancestor = sourceLastChild;
402  while (ancestor.isValid())
403  {
404  const int ancestorRow = ancestor.row();
405  if (verticalDistance <= ancestorRow)
406  {
407  return ancestor.sibling(ancestorRow - verticalDistance, proxyIndex.column());
408  }
409  verticalDistance -= (ancestorRow + 1);
410  ancestor = ancestor.parent();
411  }
412  Q_ASSERT(!"Didn't find target row.");
413  return QModelIndex();
414 }
415 
416 QModelIndex KDescendantsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
417 {
418  Q_D(const KDescendantsProxyModel);
419 
420  if (!sourceModel())
421  return QModelIndex();
422 
423  if (d->m_mapping.isEmpty())
424  return QModelIndex();
425 
426  {
427  // TODO: Consider a parent Mapping to speed this up.
428 
429  Mapping::right_const_iterator it = d->m_mapping.rightConstBegin();
430  const Mapping::right_const_iterator end = d->m_mapping.rightConstEnd();
431  const QModelIndex sourceParent = sourceIndex.parent();
432  Mapping::right_const_iterator result = end;
433 
434  for ( ; it != end; ++it )
435  {
436  QModelIndex index = it.value();
437  bool found_block = false;
438  while (index.isValid())
439  {
440  const QModelIndex ancestor = index.parent();
441  if (ancestor == sourceParent && index.row() >= sourceIndex.row())
442  {
443  found_block = true;
444  if (result == end || it.key() < result.key())
445  {
446  result = it;
447  break; // Leave the while loop. index is still valid.
448  }
449  }
450  index = ancestor;
451  }
452  if (found_block && !index.isValid())
453  // Looked through the ascendants of it.key() without finding sourceParent.
454  // That means we've already got the result we need.
455  break;
456  }
457  Q_ASSERT(result != end);
458  const QModelIndex sourceLastChild = result.value();
459  int proxyRow = result.key();
460  QModelIndex index = sourceLastChild;
461  while (index.isValid())
462  {
463  const QModelIndex ancestor = index.parent();
464  if (ancestor == sourceParent)
465  {
466  return createIndex(proxyRow - (index.row() - sourceIndex.row()), sourceIndex.column());
467  }
468  proxyRow -= (index.row() + 1);
469  index = ancestor;
470  }
471  Q_ASSERT(!"Didn't find valid proxy mapping.");
472  return QModelIndex();
473  }
474 
475 }
476 
477 int KDescendantsProxyModel::columnCount(const QModelIndex &parent) const
478 {
479  if (parent.isValid() /* || rowCount(parent) == 0 */ || !sourceModel())
480  return 0;
481 
482  return sourceModel()->columnCount();
483 }
484 
485 QVariant KDescendantsProxyModel::data(const QModelIndex &index, int role) const
486 {
487  Q_D(const KDescendantsProxyModel );
488 
489  if (!sourceModel())
490  return QVariant();
491 
492  if (!index.isValid())
493  return sourceModel()->data(index, role);
494 
495  QModelIndex sourceIndex = mapToSource( index );
496 
497  if ((d->m_displayAncestorData) && ( role == Qt::DisplayRole ) )
498  {
499  if (!sourceIndex.isValid())
500  {
501  return QVariant();
502  }
503  QString displayData = sourceIndex.data().toString();
504  sourceIndex = sourceIndex.parent();
505  while (sourceIndex.isValid())
506  {
507  displayData.prepend(d->m_ancestorSeparator);
508  displayData.prepend(sourceIndex.data().toString());
509  sourceIndex = sourceIndex.parent();
510  }
511  return displayData;
512  } else {
513  return sourceIndex.data(role);
514  }
515 }
516 
517 QVariant KDescendantsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
518 {
519  if (!sourceModel() || columnCount() <= section)
520  return QVariant();
521 
522  return QAbstractProxyModel::headerData(section, orientation, role);
523 }
524 
525 Qt::ItemFlags KDescendantsProxyModel::flags(const QModelIndex &index) const
526 {
527  if (!index.isValid() || !sourceModel())
528  return QAbstractProxyModel::flags(index);
529 
530  const QModelIndex srcIndex = mapToSource(index);
531  Q_ASSERT(srcIndex.isValid());
532  return sourceModel()->flags(srcIndex);
533 }
534 
535 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
536 {
537  Q_Q(KDescendantsProxyModel);
538 
539  if (!q->sourceModel()->hasChildren(parent))
540  {
541  Q_ASSERT(q->sourceModel()->rowCount(parent) == 0);
542  // parent was not a parent before.
543  return;
544  }
545 
546  int proxyStart = -1;
547 
548  const int rowCount = q->sourceModel()->rowCount(parent);
549 
550  if (rowCount > start)
551  {
552  const QModelIndex belowStart = q->sourceModel()->index(start, 0, parent);
553  proxyStart = q->mapFromSource(belowStart).row();
554  } else if (rowCount == 0)
555  {
556  proxyStart = q->mapFromSource(parent).row() + 1;
557  } else {
558  Q_ASSERT(rowCount == start);
559  static const int column = 0;
560  QModelIndex idx = q->sourceModel()->index(rowCount - 1, column, parent);
561  while (q->sourceModel()->hasChildren(idx))
562  {
563  Q_ASSERT(q->sourceModel()->rowCount(idx) > 0);
564  idx = q->sourceModel()->index(q->sourceModel()->rowCount(idx) - 1, column, idx);
565  }
566  // The last item in the list is getting a sibling below it.
567  proxyStart = q->mapFromSource(idx).row() + 1;
568  }
569  const int proxyEnd = proxyStart + (end - start);
570 
571  m_insertPair = qMakePair(proxyStart, proxyEnd);
572  q->beginInsertRows(QModelIndex(), proxyStart, proxyEnd);
573 }
574 
575 void KDescendantsProxyModelPrivate::sourceRowsInserted(const QModelIndex &parent, int start, int end)
576 {
577  Q_Q(KDescendantsProxyModel);
578 
579  const QModelIndex sourceStart = q->sourceModel()->index(start, 0, parent);
580  Q_ASSERT(sourceStart.isValid());
581 
582  const int rowCount = q->sourceModel()->rowCount(parent);
583  Q_ASSERT(rowCount > 0);
584 
585  const int difference = end - start + 1;
586 
587  if (rowCount == difference)
588  {
589  // @p parent was not a parent before.
590  m_pendingParents.append(parent);
591  scheduleProcessPendingParents();
592  return;
593  }
594 
595  const int proxyStart = m_insertPair.first;
596 
597  Q_ASSERT(proxyStart >= 0);
598 
599  updateInternalIndexes(proxyStart, difference);
600 
601  if (rowCount - 1 == end)
602  {
603  // The previously last row (the mapped one) is no longer the last.
604  // For example,
605 
606  // - A - A 0
607  // - - B - B 1
608  // - - C - C 2
609  // - - - D - D 3
610  // - - - E -> - E 4
611  // - - F - F 5
612  // - - G -> - G 6
613  // - H - H 7
614  // - I -> - I 8
615 
616  // As last children, E, F and G have mappings.
617  // Consider that 'J' is appended to the children of 'C', below 'E'.
618 
619  // - A - A 0
620  // - - B - B 1
621  // - - C - C 2
622  // - - - D - D 3
623  // - - - E -> - E 4
624  // - - - J - ??? 5
625  // - - F - F 6
626  // - - G -> - G 7
627  // - H - H 8
628  // - I -> - I 9
629 
630  // The updateInternalIndexes call above will have updated the F and G mappings correctly because proxyStart is 5.
631  // That means that E -> 4 was not affected by the updateInternalIndexes call.
632  // Now the mapping for E -> 4 needs to be updated so that it's a mapping for J -> 5.
633 
634  Q_ASSERT(!m_mapping.isEmpty());
635  static const int column = 0;
636  const QModelIndex oldIndex = q->sourceModel()->index(rowCount - 1 - difference, column, parent);
637  Q_ASSERT(m_mapping.leftContains(oldIndex));
638 
639  const QModelIndex newIndex = q->sourceModel()->index(rowCount - 1, column, parent);
640 
641  QModelIndex indexAbove = oldIndex;
642 
643  if (start > 0) {
644  // If we have something like this:
645  //
646  // - A
647  // - - B
648  // - - C
649  //
650  // and we then insert D as a sibling of A below it, we need to remove the mapping for A,
651  // and the row number used for D must take into account the descendants of A.
652 
653  while (q->sourceModel()->hasChildren(indexAbove)) {
654  Q_ASSERT(q->sourceModel()->rowCount(indexAbove) > 0);
655  indexAbove = q->sourceModel()->index(q->sourceModel()->rowCount(indexAbove) - 1, column, indexAbove);
656  }
657  Q_ASSERT(q->sourceModel()->rowCount(indexAbove) == 0);
658  }
659 
660  Q_ASSERT(m_mapping.leftContains(indexAbove));
661 
662  const int newProxyRow = m_mapping.leftToRight(indexAbove) + difference;
663 
664  // oldIndex is E in the source. proxyRow is 4.
665  m_mapping.removeLeft(oldIndex);
666 
667  // newIndex is J. (proxyRow + difference) is 5.
668  m_mapping.insert(newIndex, newProxyRow);
669  }
670 
671  for (int row = start; row <= end; ++row)
672  {
673  static const int column = 0;
674  const QModelIndex idx = q->sourceModel()->index(row, column, parent);
675  Q_ASSERT(idx.isValid());
676  if (q->sourceModel()->hasChildren(idx))
677  {
678  Q_ASSERT(q->sourceModel()->rowCount(idx) > 0);
679  m_pendingParents.append(idx);
680  }
681  }
682 
683  m_rowCount += difference;
684 
685  q->endInsertRows();
686  scheduleProcessPendingParents();
687 }
688 
689 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
690 {
691  Q_Q(KDescendantsProxyModel);
692 
693  const int proxyStart = q->mapFromSource(q->sourceModel()->index(start, 0, parent)).row();
694 
695  static const int column = 0;
696  QModelIndex idx = q->sourceModel()->index(end, column, parent);
697  while (q->sourceModel()->hasChildren(idx))
698  {
699  Q_ASSERT(q->sourceModel()->rowCount(idx) > 0);
700  idx = q->sourceModel()->index(q->sourceModel()->rowCount(idx) - 1, column, idx);
701  }
702  const int proxyEnd = q->mapFromSource(idx).row();
703 
704  m_removePair = qMakePair(proxyStart, proxyEnd);
705 
706  q->beginRemoveRows(QModelIndex(), proxyStart, proxyEnd);
707 }
708 
709 static QModelIndex getFirstDeepest(QAbstractItemModel *model, const QModelIndex &parent, int *count) {
710  static const int column = 0;
711  Q_ASSERT(model->hasChildren(parent));
712  Q_ASSERT(model->rowCount(parent) > 0);
713  for (int row = 0; row < model->rowCount(parent); ++row) {
714  (*count)++;
715  const QModelIndex child = model->index(row, column, parent);
716  Q_ASSERT(child.isValid());
717  if (model->hasChildren(child))
718  return getFirstDeepest(model, child, count);
719  }
720  return model->index(model->rowCount(parent) - 1, column, parent);
721 }
722 
723 void KDescendantsProxyModelPrivate::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
724 {
725  Q_Q(KDescendantsProxyModel);
726  Q_UNUSED(end)
727 
728  const int rowCount = q->sourceModel()->rowCount(parent);
729 
730 
731  const int proxyStart = m_removePair.first;
732  const int proxyEnd = m_removePair.second;
733 
734  const int difference = proxyEnd - proxyStart + 1;
735  {
736  Mapping::right_iterator it = m_mapping.rightLowerBound(proxyStart);
737  const Mapping::right_iterator endIt = m_mapping.rightUpperBound(proxyEnd);
738 
739  if (endIt != m_mapping.rightEnd())
740  while (it != endIt)
741  it = m_mapping.eraseRight(it);
742  else
743  while (it != m_mapping.rightUpperBound(proxyEnd))
744  it = m_mapping.eraseRight(it);
745  }
746 
747  m_removePair = qMakePair(-1, -1);
748  m_rowCount -= difference;
749  Q_ASSERT(m_rowCount >= 0);
750 
751  updateInternalIndexes(proxyStart, -1 * difference);
752 
753  if (rowCount != start || rowCount == 0) {
754  q->endRemoveRows();
755  return;
756  }
757 
758  static const int column = 0;
759  const QModelIndex newEnd = q->sourceModel()->index(rowCount - 1, column, parent);
760  Q_ASSERT(newEnd.isValid());
761 
762  if (m_mapping.isEmpty()) {
763  m_mapping.insert(newEnd, newEnd.row());
764  q->endRemoveRows();
765  return;
766  }
767  if (q->sourceModel()->hasChildren(newEnd)) {
768  int count = 0;
769  const QModelIndex firstDeepest = getFirstDeepest(q->sourceModel(), newEnd, &count);
770  Q_ASSERT(firstDeepest.isValid());
771  const int firstDeepestProxy = m_mapping.leftToRight(firstDeepest);
772 
773  m_mapping.insert(newEnd, firstDeepestProxy - count);
774  q->endRemoveRows();
775  return;
776  }
777  Mapping::right_iterator lowerBound = m_mapping.rightLowerBound(proxyStart);
778  if (lowerBound == m_mapping.rightEnd()) {
779  int proxyRow = (lowerBound - 1).key();
780 
781  for (int row = newEnd.row(); row >= 0; --row ) {
782  const QModelIndex newEndSibling = q->sourceModel()->index(row, column, parent);
783  if (!q->sourceModel()->hasChildren(newEndSibling)) {
784  ++proxyRow;
785  } else {
786  break;
787  }
788  }
789  m_mapping.insert(newEnd, proxyRow);
790  q->endRemoveRows();
791  return;
792  } else if (lowerBound == m_mapping.rightBegin()) {
793  int proxyRow = rowCount - 1;
794  QModelIndex trackedParent = parent;
795  while (trackedParent.isValid()) {
796  proxyRow += (trackedParent.row() + 1);
797  trackedParent = trackedParent.parent();
798  }
799  m_mapping.insert(newEnd, proxyRow);
800  q->endRemoveRows();
801  return;
802  }
803  const Mapping::right_iterator boundAbove = lowerBound - 1;
804 
805  QVector<QModelIndex> targetParents;
806  targetParents.push_back(parent);
807  {
808  QModelIndex target = parent;
809  int count = 0;
810  while (target.isValid()) {
811  if (target == boundAbove.value()) {
812  m_mapping.insert(newEnd, count + boundAbove.key() + newEnd.row() + 1);
813  q->endRemoveRows();
814  return;
815  }
816  count += (target.row() + 1);
817  target = target.parent();
818  if (target.isValid())
819  targetParents.push_back(target);
820  }
821  }
822 
823  QModelIndex boundParent = boundAbove.value().parent();
824  QModelIndex prevParent = boundParent;
825  Q_ASSERT(boundParent.isValid());
826  while (boundParent.isValid()) {
827  prevParent = boundParent;
828  boundParent = boundParent.parent();
829 
830  if (targetParents.contains(prevParent))
831  break;
832 
833  if (!m_mapping.leftContains(prevParent))
834  break;
835 
836  if (m_mapping.leftToRight(prevParent) > boundAbove.key())
837  break;
838  }
839 
840  QModelIndex trackedParent = parent;
841 
842  int proxyRow = boundAbove.key();
843 
844  Q_ASSERT(prevParent.isValid());
845  proxyRow -= prevParent.row();
846  while (trackedParent != boundParent) {
847  proxyRow += (trackedParent.row() + 1);
848  trackedParent = trackedParent.parent();
849  }
850  m_mapping.insert(newEnd, proxyRow + newEnd.row());
851  q->endRemoveRows();
852 }
853 
854 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destStart)
855 {
856  Q_UNUSED(srcParent)
857  Q_UNUSED(srcStart)
858  Q_UNUSED(srcEnd)
859  Q_UNUSED(destParent)
860  Q_UNUSED(destStart)
861  Q_Q(KDescendantsProxyModel);
862  q->beginResetModel();
863 }
864 
865 void KDescendantsProxyModelPrivate::sourceRowsMoved(const QModelIndex &srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int destStart)
866 {
867  Q_UNUSED(srcParent)
868  Q_UNUSED(srcStart)
869  Q_UNUSED(srcEnd)
870  Q_UNUSED(destParent)
871  Q_UNUSED(destStart)
872  Q_Q(KDescendantsProxyModel);
873  resetInternalData();
874  q->endResetModel();
875 }
876 
877 void KDescendantsProxyModelPrivate::sourceModelAboutToBeReset()
878 {
879  Q_Q(KDescendantsProxyModel);
880  q->beginResetModel();
881 }
882 
883 void KDescendantsProxyModelPrivate::sourceModelReset()
884 {
885  Q_Q(KDescendantsProxyModel);
886  resetInternalData();
887  if (q->sourceModel()->hasChildren())
888  {
889  Q_ASSERT(q->sourceModel()->rowCount() > 0);
890  m_pendingParents.append(QModelIndex());
891  scheduleProcessPendingParents();
892  }
893  q->endResetModel();
894 }
895 
896 void KDescendantsProxyModelPrivate::sourceLayoutAboutToBeChanged()
897 {
898  Q_Q(KDescendantsProxyModel);
899 
900  if (m_ignoreNextLayoutChanged) {
901  m_ignoreNextLayoutChanged = false;
902  return;
903  }
904 
905  if (m_mapping.isEmpty())
906  return;
907 
908  QPersistentModelIndex srcPersistentIndex;
909  foreach(const QPersistentModelIndex &proxyPersistentIndex, q->persistentIndexList()) {
910  m_proxyIndexes << proxyPersistentIndex;
911  Q_ASSERT(proxyPersistentIndex.isValid());
912  srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
913  Q_ASSERT(srcPersistentIndex.isValid());
914  m_layoutChangePersistentIndexes << srcPersistentIndex;
915  }
916 
917  q->layoutAboutToBeChanged();
918 }
919 
920 void KDescendantsProxyModelPrivate::sourceLayoutChanged()
921 {
922  Q_Q(KDescendantsProxyModel);
923 
924  if (m_ignoreNextLayoutAboutToBeChanged) {
925  m_ignoreNextLayoutAboutToBeChanged = false;
926  return;
927  }
928 
929  if (m_mapping.isEmpty())
930  return;
931 
932  m_rowCount = 0;
933 
934  synchronousMappingRefresh();
935 
936  for (int i = 0; i < m_proxyIndexes.size(); ++i) {
937  q->changePersistentIndex(m_proxyIndexes.at(i), q->mapFromSource(m_layoutChangePersistentIndexes.at(i)));
938  }
939 
940  m_layoutChangePersistentIndexes.clear();
941  m_proxyIndexes.clear();
942 
943  q->layoutChanged();
944 }
945 
946 void KDescendantsProxyModelPrivate::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
947 {
948  Q_Q(KDescendantsProxyModel);
949  Q_ASSERT(topLeft.model() == q->sourceModel());
950  Q_ASSERT(bottomRight.model() == q->sourceModel());
951 
952  const int topRow = topLeft.row();
953  const int bottomRow = bottomRight.row();
954 
955  if (m_mapping.isEmpty() && q->sourceModel()->hasChildren())
956  {
957  Q_ASSERT(q->sourceModel()->rowCount() > 0);
958  synchronousMappingRefresh();
959  }
960 
961  for(int i = topRow; i <= bottomRow; ++i)
962  {
963  const QModelIndex sourceTopLeft = q->sourceModel()->index(i, topLeft.column(), topLeft.parent());
964  Q_ASSERT(sourceTopLeft.isValid());
965  const QModelIndex proxyTopLeft = q->mapFromSource(sourceTopLeft);
966  // TODO. If an index does not have any descendants, then we can emit in blocks of rows.
967  // As it is we emit once for each row.
968  const QModelIndex sourceBottomRight = q->sourceModel()->index(i, bottomRight.column(), bottomRight.parent());
969  const QModelIndex proxyBottomRight = q->mapFromSource(sourceBottomRight);
970  Q_ASSERT(proxyTopLeft.isValid());
971  Q_ASSERT(proxyBottomRight.isValid());
972  emit q->dataChanged(proxyTopLeft, proxyBottomRight);
973  }
974 }
975 
976 void KDescendantsProxyModelPrivate::sourceModelDestroyed()
977 {
978 // Q_Q(KDescendantsProxyModel);
979  resetInternalData();
980 // q->endResetModel();
981 }
982 
983 QMimeData* KDescendantsProxyModel::mimeData( const QModelIndexList & indexes ) const
984 {
985  if (!sourceModel())
986  return QAbstractProxyModel::mimeData(indexes);
987  Q_ASSERT(sourceModel());
988  QModelIndexList sourceIndexes;
989  foreach(const QModelIndex& index, indexes)
990  sourceIndexes << mapToSource(index);
991  return sourceModel()->mimeData(sourceIndexes);
992 }
993 
994 QStringList KDescendantsProxyModel::mimeTypes() const
995 {
996  if (!sourceModel())
997  return QAbstractProxyModel::mimeTypes();
998  Q_ASSERT(sourceModel());
999  return sourceModel()->mimeTypes();
1000 }
1001 
1002 Qt::DropActions KDescendantsProxyModel::supportedDropActions() const
1003 {
1004  if (!sourceModel())
1005  return QAbstractProxyModel::supportedDropActions();
1006  return sourceModel()->supportedDropActions();
1007 }
1008 
1009 #include "kdescendantsproxymodel.moc"
QAbstractItemModel::hasIndex
bool hasIndex(int row, int column, const QModelIndex &parent) const
QList::clear
void clear()
QModelIndex
QAbstractItemModel::rowCount
virtual int rowCount(const QModelIndex &parent) const =0
QHash::insert
iterator insert(const Key &key, const T &value)
QAbstractItemModel::index
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const =0
KDescendantsProxyModel::index
virtual QModelIndex index(int, int, const QModelIndex &parent=QModelIndex()) const
Definition: kdescendantsproxymodel.cpp:344
QAbstractItemModel::layoutChanged
void layoutChanged()
KDescendantsProxyModel
Proxy Model for restructuring a Tree into a list.
Definition: kdescendantsproxymodel.h:69
QVector::append
void append(const T &value)
QVector::begin
iterator begin()
QAbstractProxyModel
QAbstractProxyModel::supportedDropActions
virtual Qt::DropActions supportedDropActions() const
QString::prepend
QString & prepend(QChar ch)
KDescendantsProxyModel::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
Definition: kdescendantsproxymodel.cpp:983
QVector::erase
iterator erase(iterator begin, iterator end)
Qt::MatchFlags
typedef MatchFlags
QAbstractItemModel::modelAboutToBeReset
void modelAboutToBeReset()
QAbstractItemModel::modelReset
void modelReset()
QAbstractProxyModel::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
QAbstractItemModel::rowsAboutToBeRemoved
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
QObject::disconnect
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QMimeData
QAbstractItemModel::mimeTypes
virtual QStringList mimeTypes() const
KDescendantsProxyModel::mapToSource
QModelIndex mapToSource(const QModelIndex &proxyIndex) const
Definition: kdescendantsproxymodel.cpp:355
QAbstractItemModel::beginResetModel
void beginResetModel()
QAbstractItemModel::match
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, QFlags< Qt::MatchFlag > flags) const
QAbstractItemModel::layoutAboutToBeChanged
void layoutAboutToBeChanged()
QModelIndex::isValid
bool isValid() const
KDescendantsProxyModel::setAncestorSeparator
void setAncestorSeparator(const QString &separator)
Sets the ancestor separator used between data of ancestors.
Definition: kdescendantsproxymodel.cpp:236
KBiAssociativeContainer< QHash< QPersistentModelIndex, int >, QMap< int, QPersistentModelIndex > >::right_const_iterator
QMap< int, QPersistentModelIndex >::const_iterator right_const_iterator
Definition: kbihash_p.h:125
KBiAssociativeContainer< QHash< QPersistentModelIndex, int >, QMap< int, QPersistentModelIndex > >::right_iterator
_iterator< QMap< int, QPersistentModelIndex > > right_iterator
Definition: kbihash_p.h:124
QPersistentModelIndex::isValid
bool isValid() const
QVector::contains
bool contains(const T &value) const
QAbstractItemModel::rowsAboutToBeInserted
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
QHash::constEnd
const_iterator constEnd() const
QAbstractItemModel::dataChanged
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
KDescendantsProxyModel::hasChildren
virtual bool hasChildren(const QModelIndex &parent=QModelIndex()) const
Definition: kdescendantsproxymodel.cpp:324
QHash
QObject
KDescendantsProxyModel::mapFromSource
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const
Definition: kdescendantsproxymodel.cpp:416
QModelIndex::row
int row() const
KDescendantsProxyModel::ancestorSeparator
QString ancestorSeparator() const
Separator used between data of ancestors.
Definition: kdescendantsproxymodel.cpp:242
QAbstractItemModel::supportedDropActions
virtual Qt::DropActions supportedDropActions() const
QAbstractProxyModel::setSourceModel
virtual void setSourceModel(QAbstractItemModel *sourceModel)
getFirstDeepest
static QModelIndex getFirstDeepest(QAbstractItemModel *model, const QModelIndex &parent, int *count)
Definition: kdescendantsproxymodel.cpp:709
QAbstractItemModel::data
virtual QVariant data(const QModelIndex &index, int role) const =0
KDescendantsProxyModel::KDescendantsProxyModel
KDescendantsProxyModel(QObject *parent=0)
Creates a new descendant entities proxy model.
Definition: kdescendantsproxymodel.cpp:209
QString
QList< QPersistentModelIndex >
kdescendantsproxymodel.h
QAbstractItemModel::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
QModelIndex::parent
QModelIndex parent() const
QAbstractItemModel::rowsRemoved
void rowsRemoved(const QModelIndex &parent, int start, int end)
QStringList
KDescendantsProxyModel::data
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Definition: kdescendantsproxymodel.cpp:485
QPair< int, int >
QAbstractItemModel::createIndex
QModelIndex createIndex(int row, int column, void *ptr) const
KDescendantsProxyModel::supportedDropActions
virtual Qt::DropActions supportedDropActions() const
Definition: kdescendantsproxymodel.cpp:1002
KHash2Map< QPersistentModelIndex, int >
QHash::const_iterator
QAbstractProxyModel::mimeTypes
virtual QStringList mimeTypes() const
QHash::constBegin
const_iterator constBegin() const
QPersistentModelIndex
QAbstractProxyModel::sourceModel
QAbstractItemModel * sourceModel() const
QModelIndex::model
const QAbstractItemModel * model() const
KDescendantsProxyModel::columnCount
virtual int columnCount(const QModelIndex &index=QModelIndex()) const
Definition: kdescendantsproxymodel.cpp:477
QVector< QPersistentModelIndex >
QModelIndex::data
QVariant data(int role) const
QLatin1String
Qt::DropActions
typedef DropActions
KDescendantsProxyModel::setSourceModel
virtual void setSourceModel(QAbstractItemModel *model)
Sets the source model of the proxy.
Definition: kdescendantsproxymodel.cpp:249
QAbstractItemModel::hasChildren
virtual bool hasChildren(const QModelIndex &parent) const
QAbstractProxyModel::headerData
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
QModelIndex::sibling
QModelIndex sibling(int row, int column) const
QAbstractProxyModel::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
KDescendantsProxyModel::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
Definition: kdescendantsproxymodel.cpp:525
Mapping
KHash2Map< QPersistentModelIndex, int > Mapping
Definition: kdescendantsproxymodel.cpp:30
QAbstractItemModel::columnCount
virtual int columnCount(const QModelIndex &parent) const =0
QModelIndex::column
int column() const
KDescendantsProxyModel::setDisplayAncestorData
void setDisplayAncestorData(bool display)
Set whether to show ancestor data in the model.
Definition: kdescendantsproxymodel.cpp:224
QAbstractItemModel
KDescendantsProxyModel::rowCount
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
Definition: kdescendantsproxymodel.cpp:330
QVector::push_back
void push_back(const T &value)
kbihash_p.h
KDescendantsProxyModel::~KDescendantsProxyModel
virtual ~KDescendantsProxyModel()
Destroys the descendant entities proxy model.
Definition: kdescendantsproxymodel.cpp:214
QAbstractItemModel::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
QAbstractItemModel::rowsInserted
void rowsInserted(const QModelIndex &parent, int start, int end)
QAbstractItemModel::endResetModel
void endResetModel()
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::parent
QObject * parent() const
KDescendantsProxyModel::mimeTypes
virtual QStringList mimeTypes() const
Definition: kdescendantsproxymodel.cpp:994
QVariant::toString
QString toString() const
QVector::end
iterator end()
KDescendantsProxyModel::displayAncestorData
bool displayAncestorData() const
Whether ancestor data will be displayed.
Definition: kdescendantsproxymodel.cpp:230
KDescendantsProxyModel::headerData
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
Definition: kdescendantsproxymodel.cpp:517
QObject::destroyed
void destroyed(QObject *obj)
KDescendantsProxyModel::match
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits=1, Qt::MatchFlags flags=Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const
Reimplemented to match all descendants.
Definition: kdescendantsproxymodel.cpp:219
QVariant
Qt::ItemFlags
typedef ItemFlags
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:13:40 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

marble

Skip menu "marble"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdeedu API Reference

Skip menu "kdeedu API Reference"
  • Analitza
  •     lib
  • kalgebra
  • kalzium
  •   libscience
  • kanagram
  • kig
  •   lib
  • klettres
  • marble
  • parley
  • rocs
  •   App
  •   RocsCore
  •   VisualEditor
  •   stepcore

Search



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