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