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