LeechCraft  0.6.70-10870-g558588d6ec
Modular cross-platform feature rich live environment.
flattofoldersproxymodel.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * LeechCraft - modular cross-platform feature rich internet client.
3  * Copyright (C) 2006-2014 Georg Rudoy
4  *
5  * Boost Software License - Version 1.0 - August 17th, 2003
6  *
7  * Permission is hereby granted, free of charge, to any person or organization
8  * obtaining a copy of the software and accompanying documentation covered by
9  * this license (the "Software") to use, reproduce, display, distribute,
10  * execute, and transmit the Software, and to prepare derivative works of the
11  * Software, and to permit third-parties to whom the Software is furnished to
12  * do so, all subject to the following:
13  *
14  * The copyright notices in the Software and this entire statement, including
15  * the above license grant, this restriction and the following disclaimer,
16  * must be included in all copies of the Software, in whole or in part, and
17  * all derivative works of the Software, unless such copies or derivative
18  * works are solely in the form of machine-executable object code generated by
19  * a source language processor.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  **********************************************************************/
29 
31 #include <QSet>
32 #include <QMimeData>
33 #include <QItemSelectionRange>
34 #include <interfaces/iinfo.h>
36 
37 namespace LeechCraft
38 {
39  struct FlatTreeItem
40  {
43 
44  enum class Type
45  {
46  Root,
47  Folder,
48  Item
49  };
50 
52 
53  QPersistentModelIndex Index_;
54  QString Tag_;
55 
56  int Row () const
57  {
58  if (Parent_)
59  {
60  const auto& c = Parent_->C_;
61  for (int i = 0, size = c.size (); i < size; ++i)
62  if (c.at (i).get () == this)
63  return i;
64  }
65  return 0;
66  }
67  };
68 
69  FlatTreeItem* ToFlat (const QModelIndex& idx)
70  {
71  return static_cast<FlatTreeItem*> (idx.internalPointer ());
72  }
73 
74  namespace Util
75  {
77  : QAbstractItemModel { parent }
78  , Root_ { std::make_shared<FlatTreeItem> () }
79  {
80  Root_->Type_ = FlatTreeItem::Type::Root;
81  }
82 
84  {
85  beginResetModel ();
86  TM_ = tm;
87  endResetModel ();
88  }
89 
90  int FlatToFoldersProxyModel::columnCount (const QModelIndex&) const
91  {
92  return SourceModel_ ?
93  SourceModel_->columnCount (QModelIndex ()) :
94  0;
95  }
96 
97  QVariant FlatToFoldersProxyModel::data (const QModelIndex& index, int role) const
98  {
99  FlatTreeItem *fti = ToFlat (index);
100  if (fti->Type_ == FlatTreeItem::Type::Item)
101  {
102  QModelIndex source = fti->Index_;
103  return source.sibling (source.row (), index.column ()).data (role);
104  }
105  else if (fti->Type_ == FlatTreeItem::Type::Folder &&
106  index.column () == 0)
107  {
108  if (role == Qt::DisplayRole)
109  {
110  if (fti->Tag_.isEmpty ())
111  return tr ("untagged");
112 
113  QString ut = TM_->GetTag (fti->Tag_);
114  if (ut.isEmpty ())
115  return tr ("<unknown tag>");
116  else
117  return ut;
118  }
119  else if (role == RoleTags)
120  return fti->Tag_;
121  else
122  return QVariant ();
123  }
124  else
125  return QVariant ();
126  }
127 
128  QVariant FlatToFoldersProxyModel::headerData (int section,
129  Qt::Orientation orient, int role) const
130  {
131  if (SourceModel_)
132  return SourceModel_->headerData (section, orient, role);
133  else
134  return QVariant ();
135  }
136 
137  Qt::ItemFlags FlatToFoldersProxyModel::flags (const QModelIndex& index) const
138  {
139  auto fti = ToFlat (index);
140  if (fti && fti->Type_ == FlatTreeItem::Type::Item)
141  return fti->Index_.flags ();
142  else
143  return Qt::ItemIsSelectable |
144  Qt::ItemIsEnabled |
145  Qt::ItemIsDragEnabled |
146  Qt::ItemIsDropEnabled;
147  }
148 
149  QModelIndex FlatToFoldersProxyModel::index (int row, int column,
150  const QModelIndex& parent) const
151  {
152  if (!hasIndex (row, column, parent))
153  return QModelIndex ();
154 
155  FlatTreeItem *fti = 0;
156  if (parent.isValid ())
157  fti = ToFlat (parent);
158  else
159  fti = Root_.get ();
160 
161  if (fti->Type_ == FlatTreeItem::Type::Item)
162  return QModelIndex ();
163  else
164  return createIndex (row, column, fti->C_.at (row).get ());
165  }
166 
167  QModelIndex FlatToFoldersProxyModel::parent (const QModelIndex& index) const
168  {
169  FlatTreeItem *fti = 0;
170  if (index.isValid ())
171  fti = ToFlat (index);
172  else
173  fti = Root_.get ();
174 
176  parent = fti->Parent_;
177 
178  if (parent &&
180  return createIndex (parent->Row (), 0, parent.get ());
181  else
182  return QModelIndex ();
183  }
184 
185  int FlatToFoldersProxyModel::rowCount (const QModelIndex& index) const
186  {
187  if (index.isValid ())
188  return ToFlat (index)->C_.size ();
189  else
190  return Root_->C_.size ();
191  }
192 
194  {
195  return SourceModel_ ?
196  SourceModel_->supportedDropActions () :
197  QAbstractItemModel::supportedDropActions ();
198  }
199 
201  {
202  return SourceModel_ ?
203  SourceModel_->mimeTypes () :
204  QAbstractItemModel::mimeTypes ();
205  }
206 
207  QMimeData* FlatToFoldersProxyModel::mimeData (const QModelIndexList& indexes) const
208  {
209  if (!SourceModel_)
210  return QAbstractItemModel::mimeData (indexes);
211 
212  QModelIndexList sourceIdxs;
213  for (const auto& index : indexes)
214  {
215  auto item = static_cast<FlatTreeItem*> (index.internalPointer ());
216  switch (item->Type_)
217  {
219  sourceIdxs << MapToSource (index);
220  break;
222  for (const auto& subItem : item->C_)
223  sourceIdxs << subItem->Index_;
224  break;
225  default:
226  break;
227  }
228  }
229 
230  return SourceModel_->mimeData (sourceIdxs);
231  }
232 
233  bool FlatToFoldersProxyModel::dropMimeData (const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex& parent)
234  {
235  if (!SourceModel_)
236  return false;
237 
238  QMimeData modified;
239  for (const auto& format : data->formats ())
240  modified.setData (format, data->data (format));
241 
242  if (auto ptr = static_cast<FlatTreeItem*> (parent.internalPointer ()))
243  {
244  switch (ptr->Type_)
245  {
248  modified.setData ("x-leechcraft/tag", ptr->Tag_.toLatin1 ());
249  break;
250  default:
251  break;
252  }
253  }
254 
255  return SourceModel_->dropMimeData (&modified, action, -1, -1, QModelIndex ());
256  }
257 
258  void FlatToFoldersProxyModel::SetSourceModel (QAbstractItemModel *model)
259  {
260  if (SourceModel_)
261  disconnect (SourceModel_,
262  0,
263  this,
264  0);
265 
266  SourceModel_ = model;
267 
268  if (model)
269  {
270  // We don't support changing columns (yet) so don't connect
271  // to columns* signals.
272  connect (model,
273  SIGNAL (headerDataChanged (Qt::Orientation, int, int)),
274  this,
275  SIGNAL (headerDataChanged (Qt::Orientation, int, int)));
276  connect (model,
277  SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
278  this,
279  SLOT (handleDataChanged (const QModelIndex&, const QModelIndex&)));
280  connect (model,
281  SIGNAL (layoutAboutToBeChanged ()),
282  this,
283  SIGNAL (layoutAboutToBeChanged ()));
284  connect (model,
285  SIGNAL (layoutChanged ()),
286  this,
287  SIGNAL (layoutChanged ()));
288  connect (model,
289  SIGNAL (modelReset ()),
290  this,
291  SLOT (handleModelReset ()));
292  connect (model,
293  SIGNAL (rowsInserted (const QModelIndex&,
294  int, int)),
295  this,
296  SLOT (handleRowsInserted (const QModelIndex&,
297  int, int)));
298  connect (model,
299  SIGNAL (rowsAboutToBeRemoved (const QModelIndex&,
300  int, int)),
301  this,
302  SLOT (handleRowsAboutToBeRemoved (const QModelIndex&,
303  int, int)));
304  }
305 
306  handleModelReset ();
307  }
308 
309  QAbstractItemModel* FlatToFoldersProxyModel::GetSourceModel () const
310  {
311  return SourceModel_;
312  }
313 
314  QModelIndex FlatToFoldersProxyModel::MapToSource (const QModelIndex& proxy) const
315  {
316  if (!GetSourceModel ())
317  return {};
318 
319  if (!proxy.isValid ())
320  return {};
321 
322  const auto item = ToFlat (proxy);
323 
324  if (item->Type_ != FlatTreeItem::Type::Item)
325  return {};
326 
327  return item->Index_;
328  }
329 
331  {
332  auto tags = source.data (RoleTags).toStringList ();
333  if (tags.isEmpty ())
334  tags << QString ();
335 
336  QList<QModelIndex> result;
337  for (const auto& tag : tags)
338  {
339  const auto& folder = FindFolder (tag);
340  if (!folder)
341  {
342  qWarning () << Q_FUNC_INFO
343  << "could not find folder for tag"
344  << tag
345  << GetSourceModel ();
346  continue;
347  }
348 
349  const auto& folderIdx = index (folder->Row (), 0, {});
350 
351  for (int i = 0; i < folder->C_.size (); ++i)
352  {
353  const auto& child = folder->C_.at (i);
354  if (child->Index_ != source)
355  continue;
356 
357  result << index (i, 0, folderIdx);
358  break;
359  }
360  }
361  return result;
362  }
363 
364  FlatTreeItem_ptr FlatToFoldersProxyModel::FindFolder (const QString& tag) const
365  {
366  for (const auto& item : Root_->C_)
367  if (item->Tag_ == tag)
368  return item;
369 
370  return {};
371  }
372 
373  FlatTreeItem_ptr FlatToFoldersProxyModel::GetFolder (const QString& tag)
374  {
375  auto& c = Root_->C_;
376  for (const auto& item : c)
377  if (item->Tag_ == tag)
378  return item;
379 
380  const auto& item = std::make_shared<FlatTreeItem> ();
381  item->Type_ = FlatTreeItem::Type::Folder;
382  item->Tag_ = tag;
383  item->Parent_ = Root_;
384 
385  int size = c.size ();
386  beginInsertRows (QModelIndex (), size, size);
387  c.append (item);
388  endInsertRows ();
389 
390  return item;
391  }
392 
393  void FlatToFoldersProxyModel::HandleRowInserted (int i)
394  {
395  QModelIndex idx = SourceModel_->index (i, 0);
396 
397  QStringList tags = idx.data (RoleTags).toStringList ();
398 
399  if (tags.isEmpty ())
400  tags << QString ();
401 
402  QPersistentModelIndex pidx (idx);
403 
404  Q_FOREACH (QString tag, tags)
405  AddForTag (tag, pidx);
406  }
407 
408  void FlatToFoldersProxyModel::HandleRowRemoved (int i)
409  {
410  QAbstractItemModel *model = SourceModel_;
411  QModelIndex idx = model->index (i, 0);
412 
413  QStringList tags = idx.data (RoleTags).toStringList ();
414 
415  if (tags.isEmpty ())
416  tags << QString ();
417 
418  QPersistentModelIndex pidx (idx);
419 
420  Q_FOREACH (QString tag, tags)
421  RemoveFromTag (tag, pidx);
422  }
423 
424  void FlatToFoldersProxyModel::AddForTag (const QString& tag,
425  const QPersistentModelIndex& pidx)
426  {
427  FlatTreeItem_ptr folder = GetFolder (tag);
428 
429  const auto& item = std::make_shared<FlatTreeItem> ();
430  item->Type_ = FlatTreeItem::Type::Item;
431  item->Index_ = pidx;
432  item->Parent_ = folder;
433  item->Tag_ = tag;
434 
435  int size = folder->C_.size ();
436  QModelIndex iidx = index (Root_->C_.indexOf (folder), 0);
437  beginInsertRows (iidx, size, size);
438  folder->C_.append (item);
439  Items_.insert (pidx, item);
440  endInsertRows ();
441  }
442 
443  void FlatToFoldersProxyModel::RemoveFromTag (const QString& tag,
444  const QPersistentModelIndex& pidx)
445  {
446  const auto& folder = GetFolder (tag);
447  auto& c = folder->C_;
448  int findex = Root_->C_.indexOf (folder);
449  for (int i = 0, size = c.size ();
450  i < size; ++i)
451  {
452  if (c.at (i)->Index_ != pidx)
453  continue;
454 
455  beginRemoveRows (index (findex, 0), i, i);
456  Items_.remove (pidx, c.at (i));
457  c.removeAt (i);
458  endRemoveRows ();
459  break;
460  }
461 
462  if (c.isEmpty ())
463  {
464  beginRemoveRows (QModelIndex (), findex, findex);
465  Root_->C_.removeAt (findex);
466  endRemoveRows ();
467  }
468  }
469 
470  void FlatToFoldersProxyModel::HandleChanged (const QModelIndex& idx)
471  {
472  QSet<QString> newTags = QSet<QString>::fromList (idx.data (RoleTags).toStringList ());
473  if (newTags.isEmpty ())
474  newTags << QString ();
475 
476  QPersistentModelIndex pidx (idx);
477  QList<FlatTreeItem_ptr> items = Items_.values (pidx);
478 
479  QSet<QString> oldTags;
480  Q_FOREACH (FlatTreeItem_ptr item, items)
481  oldTags << item->Tag_;
482 
483  QSet<QString> added = QSet<QString> (newTags).subtract (oldTags);
484  QSet<QString> removed = QSet<QString> (oldTags).subtract (newTags);
485  QSet<QString> changed = QSet<QString> (newTags).intersect (oldTags);
486 
487  Q_FOREACH (QString ch, changed)
488  {
489  FlatTreeItem_ptr folder = GetFolder (ch);
490 
491  QList<FlatTreeItem_ptr>& c = folder->C_;
492  int findex = Root_->C_.indexOf (folder);
493  QModelIndex fmi = index (findex, 0);
494  for (int i = 0, size = c.size ();
495  i < size; ++i)
496  {
497  if (c.at (i)->Index_ != pidx)
498  continue;
499 
500  emit dataChanged (index (i, 0, fmi),
501  index (i, columnCount () - 1, fmi));
502  break;
503  }
504  }
505 
506  Q_FOREACH (QString rem, removed)
507  RemoveFromTag (rem, pidx);
508 
509  Q_FOREACH (QString add, added)
510  AddForTag (add, pidx);
511  }
512 
513  void FlatToFoldersProxyModel::handleDataChanged (const QModelIndex& topLeft,
514  const QModelIndex& bottomRight)
515  {
516  QItemSelectionRange range (topLeft.sibling (topLeft.row (), 0),
517  bottomRight.sibling (bottomRight.row (), 0));
518  QModelIndexList indexes = range.indexes ();
519  for (int i = 0, size = indexes.size ();
520  i < size; ++i)
521  HandleChanged (indexes.at (i));
522  }
523 
524  void FlatToFoldersProxyModel::handleModelReset ()
525  {
526  if (const int size = Root_->C_.size ())
527  {
528  beginRemoveRows (QModelIndex (), 0, size - 1);
529  Root_->C_.clear ();
530  Items_.clear ();
531  endRemoveRows ();
532  }
533 
534  if (SourceModel_)
535  {
536  for (int i = 0, size = SourceModel_->rowCount ();
537  i < size; ++i)
538  HandleRowInserted (i);
539  }
540  }
541 
542  void FlatToFoldersProxyModel::handleRowsInserted (const QModelIndex&,
543  int start, int end)
544  {
545  for (int i = start; i <= end; ++i)
546  HandleRowInserted (i);
547  }
548 
549  void FlatToFoldersProxyModel::handleRowsAboutToBeRemoved (const QModelIndex&,
550  int start, int end)
551  {
552  for (int i = start; i <= end; ++i)
553  HandleRowRemoved (i);
554  }
555  };
556 };
557 
QMimeData * mimeData(const QModelIndexList &indexes) const override
QList< QModelIndex > MapFromSource(const QModelIndex &) const
virtual QString GetTag(tag_id id) const =0
Returns the tag with the given id.
QVariant headerData(int, Qt::Orientation, int) const override
std::shared_ptr< FlatTreeItem > FlatTreeItem_ptr
QModelIndex parent(const QModelIndex &) const override
QList< FlatTreeItem_ptr > C_
Tags manager&#39;s interface.
Definition: itagsmanager.h:43
QModelIndex MapToSource(const QModelIndex &) const
QVariant data(const QModelIndex &, int=Qt::DisplayRole) const override
FlatTreeItem * ToFlat(const QModelIndex &idx)
int columnCount(const QModelIndex &={}) const override
Qt::ItemFlags flags(const QModelIndex &) const override
Qt::DropActions supportedDropActions() const override
int rowCount(const QModelIndex &={}) const override
QModelIndex index(int, int, const QModelIndex &={}) const override
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override