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