LeechCraft  0.6.70-13729-g7046a9d2a7
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>
35 #include <util/sll/prelude.h>
36 #include <interfaces/iinfo.h>
38 
39 namespace LC
40 {
41  struct FlatTreeItem
42  {
45 
46  enum class Type
47  {
48  Root,
49  Folder,
50  Item
51  };
52 
54 
55  QPersistentModelIndex Index_;
56  QString Tag_;
57 
58  int Row () const
59  {
60  if (Parent_)
61  {
62  const auto& c = Parent_->C_;
63  for (int i = 0, size = c.size (); i < size; ++i)
64  if (c.at (i).get () == this)
65  return i;
66  }
67  return 0;
68  }
69  };
70 
71  FlatTreeItem* ToFlat (const QModelIndex& idx)
72  {
73  return static_cast<FlatTreeItem*> (idx.internalPointer ());
74  }
75 
76  namespace Util
77  {
79  : QAbstractItemModel { parent }
80  , TM_ { itm }
81  , Root_ { std::make_shared<FlatTreeItem> () }
82  {
83  Root_->Type_ = FlatTreeItem::Type::Root;
84  }
85 
86  int FlatToFoldersProxyModel::columnCount (const QModelIndex&) const
87  {
88  return SourceModel_ ?
89  SourceModel_->columnCount (QModelIndex ()) :
90  0;
91  }
92 
93  QVariant FlatToFoldersProxyModel::data (const QModelIndex& index, int role) const
94  {
95  FlatTreeItem *fti = ToFlat (index);
96  if (fti->Type_ == FlatTreeItem::Type::Item)
97  {
98  QModelIndex source = fti->Index_;
99  return source.sibling (source.row (), index.column ()).data (role);
100  }
101  else if (fti->Type_ == FlatTreeItem::Type::Folder &&
102  index.column () == 0)
103  {
104  if (role == Qt::DisplayRole)
105  {
106  if (fti->Tag_.isEmpty ())
107  return tr ("untagged");
108 
109  QString ut = TM_->GetTag (fti->Tag_);
110  if (ut.isEmpty ())
111  return tr ("<unknown tag>");
112  else
113  return ut;
114  }
115  else if (role == RoleTags)
116  return fti->Tag_;
117  else
118  return QVariant ();
119  }
120  else
121  return QVariant ();
122  }
123 
124  QVariant FlatToFoldersProxyModel::headerData (int section,
125  Qt::Orientation orient, int role) const
126  {
127  if (SourceModel_)
128  return SourceModel_->headerData (section, orient, role);
129  else
130  return QVariant ();
131  }
132 
133  Qt::ItemFlags FlatToFoldersProxyModel::flags (const QModelIndex& index) const
134  {
135  auto fti = ToFlat (index);
136  if (fti && fti->Type_ == FlatTreeItem::Type::Item)
137  return fti->Index_.flags ();
138  else
139  return Qt::ItemIsSelectable |
140  Qt::ItemIsEnabled |
141  Qt::ItemIsDragEnabled |
142  Qt::ItemIsDropEnabled;
143  }
144 
145  QModelIndex FlatToFoldersProxyModel::index (int row, int column,
146  const QModelIndex& parent) const
147  {
148  if (!hasIndex (row, column, parent))
149  return QModelIndex ();
150 
151  FlatTreeItem *fti = 0;
152  if (parent.isValid ())
153  fti = ToFlat (parent);
154  else
155  fti = Root_.get ();
156 
157  if (fti->Type_ == FlatTreeItem::Type::Item)
158  return QModelIndex ();
159  else
160  return createIndex (row, column, fti->C_.at (row).get ());
161  }
162 
163  QModelIndex FlatToFoldersProxyModel::parent (const QModelIndex& index) const
164  {
165  FlatTreeItem *fti = 0;
166  if (index.isValid ())
167  fti = ToFlat (index);
168  else
169  fti = Root_.get ();
170 
172  parent = fti->Parent_;
173 
174  if (parent &&
176  return createIndex (parent->Row (), 0, parent.get ());
177  else
178  return QModelIndex ();
179  }
180 
181  int FlatToFoldersProxyModel::rowCount (const QModelIndex& index) const
182  {
183  if (index.isValid ())
184  return ToFlat (index)->C_.size ();
185  else
186  return Root_->C_.size ();
187  }
188 
190  {
191  return SourceModel_ ?
192  SourceModel_->supportedDropActions () :
193  QAbstractItemModel::supportedDropActions ();
194  }
195 
197  {
198  return SourceModel_ ?
199  SourceModel_->mimeTypes () :
200  QAbstractItemModel::mimeTypes ();
201  }
202 
203  QMimeData* FlatToFoldersProxyModel::mimeData (const QModelIndexList& indexes) const
204  {
205  if (!SourceModel_)
206  return QAbstractItemModel::mimeData (indexes);
207 
208  QModelIndexList sourceIdxs;
209  for (const auto& index : indexes)
210  {
211  auto item = static_cast<FlatTreeItem*> (index.internalPointer ());
212  switch (item->Type_)
213  {
215  sourceIdxs << MapToSource (index);
216  break;
218  for (const auto& subItem : item->C_)
219  sourceIdxs << subItem->Index_;
220  break;
221  default:
222  break;
223  }
224  }
225 
226  return SourceModel_->mimeData (sourceIdxs);
227  }
228 
229  bool FlatToFoldersProxyModel::dropMimeData (const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex& parent)
230  {
231  if (!SourceModel_)
232  return false;
233 
234  QMimeData modified;
235  for (const auto& format : data->formats ())
236  modified.setData (format, data->data (format));
237 
238  if (auto ptr = static_cast<FlatTreeItem*> (parent.internalPointer ()))
239  {
240  switch (ptr->Type_)
241  {
244  modified.setData ("x-leechcraft/tag", ptr->Tag_.toLatin1 ());
245  break;
246  default:
247  break;
248  }
249  }
250 
251  return SourceModel_->dropMimeData (&modified, action, -1, -1, QModelIndex ());
252  }
253 
254  void FlatToFoldersProxyModel::SetSourceModel (QAbstractItemModel *model)
255  {
256  if (SourceModel_)
257  disconnect (SourceModel_,
258  0,
259  this,
260  0);
261 
262  SourceModel_ = model;
263 
264  if (model)
265  {
266  // We don't support changing columns (yet) so don't connect
267  // to columns* signals.
268  connect (model,
269  SIGNAL (headerDataChanged (Qt::Orientation, int, int)),
270  this,
271  SIGNAL (headerDataChanged (Qt::Orientation, int, int)));
272  connect (model,
273  SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
274  this,
275  SLOT (handleDataChanged (const QModelIndex&, const QModelIndex&)));
276  connect (model,
277  SIGNAL (layoutAboutToBeChanged ()),
278  this,
279  SIGNAL (layoutAboutToBeChanged ()));
280  connect (model,
281  SIGNAL (layoutChanged ()),
282  this,
283  SIGNAL (layoutChanged ()));
284  connect (model,
285  SIGNAL (modelReset ()),
286  this,
287  SLOT (handleModelReset ()));
288  connect (model,
289  SIGNAL (rowsInserted (const QModelIndex&,
290  int, int)),
291  this,
292  SLOT (handleRowsInserted (const QModelIndex&,
293  int, int)));
294  connect (model,
295  SIGNAL (rowsAboutToBeRemoved (const QModelIndex&,
296  int, int)),
297  this,
298  SLOT (handleRowsAboutToBeRemoved (const QModelIndex&,
299  int, int)));
300  }
301 
302  handleModelReset ();
303  }
304 
305  QAbstractItemModel* FlatToFoldersProxyModel::GetSourceModel () const
306  {
307  return SourceModel_;
308  }
309 
310  QModelIndex FlatToFoldersProxyModel::MapToSource (const QModelIndex& proxy) const
311  {
312  if (!GetSourceModel ())
313  return {};
314 
315  if (!proxy.isValid ())
316  return {};
317 
318  const auto item = ToFlat (proxy);
319 
320  if (item->Type_ != FlatTreeItem::Type::Item)
321  return {};
322 
323  return item->Index_;
324  }
325 
327  {
328  auto tags = source.data (RoleTags).toStringList ();
329  if (tags.isEmpty ())
330  tags << QString ();
331 
332  QList<QModelIndex> result;
333  for (const auto& tag : tags)
334  {
335  const auto& folder = FindFolder (tag);
336  if (!folder)
337  {
338  qWarning () << Q_FUNC_INFO
339  << "could not find folder for tag"
340  << tag
341  << GetSourceModel ();
342  continue;
343  }
344 
345  const auto& folderIdx = index (folder->Row (), 0, {});
346 
347  for (int i = 0; i < folder->C_.size (); ++i)
348  {
349  const auto& child = folder->C_.at (i);
350  if (child->Index_ != source)
351  continue;
352 
353  result << index (i, 0, folderIdx);
354  break;
355  }
356  }
357  return result;
358  }
359 
360  FlatTreeItem_ptr FlatToFoldersProxyModel::FindFolder (const QString& tag) const
361  {
362  for (const auto& item : Root_->C_)
363  if (item->Tag_ == tag)
364  return item;
365 
366  return {};
367  }
368 
369  FlatTreeItem_ptr FlatToFoldersProxyModel::GetFolder (const QString& tag)
370  {
371  auto& c = Root_->C_;
372  for (const auto& item : c)
373  if (item->Tag_ == tag)
374  return item;
375 
376  const auto& item = std::make_shared<FlatTreeItem> ();
377  item->Type_ = FlatTreeItem::Type::Folder;
378  item->Tag_ = tag;
379  item->Parent_ = Root_;
380 
381  int size = c.size ();
382  beginInsertRows (QModelIndex (), size, size);
383  c.append (item);
384  endInsertRows ();
385 
386  return item;
387  }
388 
389  void FlatToFoldersProxyModel::HandleRowInserted (int i)
390  {
391  QModelIndex idx = SourceModel_->index (i, 0);
392 
393  QStringList tags = idx.data (RoleTags).toStringList ();
394 
395  if (tags.isEmpty ())
396  tags << QString ();
397 
398  QPersistentModelIndex pidx (idx);
399 
400  for (auto tag : tags)
401  AddForTag (tag, pidx);
402  }
403 
404  void FlatToFoldersProxyModel::HandleRowRemoved (int i)
405  {
406  QAbstractItemModel *model = SourceModel_;
407  QModelIndex idx = model->index (i, 0);
408 
409  QStringList tags = idx.data (RoleTags).toStringList ();
410 
411  if (tags.isEmpty ())
412  tags << QString ();
413 
414  QPersistentModelIndex pidx (idx);
415 
416  for (const auto& tag : tags)
417  RemoveFromTag (tag, pidx);
418  }
419 
420  void FlatToFoldersProxyModel::AddForTag (const QString& tag,
421  const QPersistentModelIndex& pidx)
422  {
423  FlatTreeItem_ptr folder = GetFolder (tag);
424 
425  const auto& item = std::make_shared<FlatTreeItem> ();
426  item->Type_ = FlatTreeItem::Type::Item;
427  item->Index_ = pidx;
428  item->Parent_ = folder;
429  item->Tag_ = tag;
430 
431  int size = folder->C_.size ();
432  QModelIndex iidx = index (Root_->C_.indexOf (folder), 0);
433  beginInsertRows (iidx, size, size);
434  folder->C_.append (item);
435  Items_.insert (pidx, item);
436  endInsertRows ();
437  }
438 
439  void FlatToFoldersProxyModel::RemoveFromTag (const QString& tag,
440  const QPersistentModelIndex& pidx)
441  {
442  const auto& folder = GetFolder (tag);
443  auto& c = folder->C_;
444  int findex = Root_->C_.indexOf (folder);
445  for (int i = 0, size = c.size ();
446  i < size; ++i)
447  {
448  if (c.at (i)->Index_ != pidx)
449  continue;
450 
451  beginRemoveRows (index (findex, 0), i, i);
452  Items_.remove (pidx, c.at (i));
453  c.removeAt (i);
454  endRemoveRows ();
455  break;
456  }
457 
458  if (c.isEmpty ())
459  {
460  beginRemoveRows (QModelIndex (), findex, findex);
461  Root_->C_.removeAt (findex);
462  endRemoveRows ();
463  }
464  }
465 
466  void FlatToFoldersProxyModel::HandleChanged (const QModelIndex& idx)
467  {
468  auto newTags = Util::AsSet (idx.data (RoleTags).toStringList ());
469  if (newTags.isEmpty ())
470  newTags << QString {};
471 
472  QPersistentModelIndex pidx (idx);
473 
474  const auto& oldTags = Util::MapAs<QSet> (Items_.values (pidx), [] (const auto& item) { return item->Tag_; });
475 
476  const auto added = QSet<QString> (newTags).subtract (oldTags);
477  const auto removed = QSet<QString> (oldTags).subtract (newTags);
478  const auto changed = QSet<QString> (newTags).intersect (oldTags);
479 
480  for (const auto& ch : changed)
481  {
482  FlatTreeItem_ptr folder = GetFolder (ch);
483 
484  QList<FlatTreeItem_ptr>& c = folder->C_;
485  int findex = Root_->C_.indexOf (folder);
486  QModelIndex fmi = index (findex, 0);
487  for (int i = 0, size = c.size ();
488  i < size; ++i)
489  {
490  if (c.at (i)->Index_ != pidx)
491  continue;
492 
493  emit dataChanged (index (i, 0, fmi),
494  index (i, columnCount () - 1, fmi));
495  break;
496  }
497  }
498 
499  for (const auto& rem : removed)
500  RemoveFromTag (rem, pidx);
501 
502  for (const auto& add : added)
503  AddForTag (add, pidx);
504  }
505 
506  void FlatToFoldersProxyModel::handleDataChanged (const QModelIndex& topLeft,
507  const QModelIndex& bottomRight)
508  {
509  QItemSelectionRange range (topLeft.sibling (topLeft.row (), 0),
510  bottomRight.sibling (bottomRight.row (), 0));
511  QModelIndexList indexes = range.indexes ();
512  for (int i = 0, size = indexes.size ();
513  i < size; ++i)
514  HandleChanged (indexes.at (i));
515  }
516 
517  void FlatToFoldersProxyModel::handleModelReset ()
518  {
519  if (const int size = Root_->C_.size ())
520  {
521  beginRemoveRows (QModelIndex (), 0, size - 1);
522  Root_->C_.clear ();
523  Items_.clear ();
524  endRemoveRows ();
525  }
526 
527  if (SourceModel_)
528  {
529  for (int i = 0, size = SourceModel_->rowCount ();
530  i < size; ++i)
531  HandleRowInserted (i);
532  }
533  }
534 
535  void FlatToFoldersProxyModel::handleRowsInserted (const QModelIndex&,
536  int start, int end)
537  {
538  for (int i = start; i <= end; ++i)
539  HandleRowInserted (i);
540  }
541 
542  void FlatToFoldersProxyModel::handleRowsAboutToBeRemoved (const QModelIndex&,
543  int start, int end)
544  {
545  for (int i = start; i <= end; ++i)
546  HandleRowRemoved (i);
547  }
548  };
549 };
550 
QVariant headerData(int, Qt::Orientation, int) const override
virtual QString GetTag(tag_id id) const =0
Returns the tag with the given id.
std::shared_ptr< FlatTreeItem > FlatTreeItem_ptr
QStringList mimeTypes() const override
QAbstractItemModel * GetSourceModel() const
QModelIndex parent(const QModelIndex &) const override
QModelIndex index(int, int, const QModelIndex &={}) const override
auto AsSet(const T &cont)
Tags manager&#39;s interface.
Definition: itagsmanager.h:43
QList< FlatTreeItem_ptr > C_
FlatTreeItem * ToFlat(const QModelIndex &idx)
QPersistentModelIndex Index_
QVariant data(const QModelIndex &, int=Qt::DisplayRole) const override
FlatToFoldersProxyModel(const ITagsManager *, QObject *=nullptr)
int columnCount(const QModelIndex &={}) const override
QModelIndex MapToSource(const QModelIndex &) const
Qt::ItemFlags flags(const QModelIndex &) const override
Qt::DropActions supportedDropActions() const override
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
int rowCount(const QModelIndex &={}) const override
QMimeData * mimeData(const QModelIndexList &indexes) const override
Definition: constants.h:35
QList< QModelIndex > MapFromSource(const QModelIndex &) const