LeechCraft 0.6.70-16373-g319c272718
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 {
27 Root,
28 Folder,
29 Item
30 };
31
33
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
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
63 {
64 return SourceModel_ ?
65 SourceModel_->columnCount ({}) :
66 0;
67 }
68
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
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);
113 return fti->Index_.flags ();
114
115 return Qt::ItemIsSelectable |
116 Qt::ItemIsEnabled |
117 Qt::ItemIsDragEnabled |
118 Qt::ItemIsDropEnabled;
119 }
120
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
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
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
161 {
162 if (!SourceModel_)
163 return QAbstractItemModel::mimeData (indexes);
164
166 for (const auto& index : indexes)
167 {
168 auto item = static_cast<FlatTreeItem*> (index.internalPointer ());
169 switch (item->Type_)
170 {
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
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
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
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
284 for (const auto& tag : tags)
285 {
286 const auto& folder = FindFolder (tag);
287 if (!folder)
288 {
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 FlatTreeItem_ptr FlatToFoldersProxyModel::FindFolder (const QString& tag) const
312 {
313 for (const auto& item : Root_->C_)
314 if (item->Tag_ == tag)
315 return item;
316
317 return {};
318 }
319
320 FlatTreeItem_ptr FlatToFoldersProxyModel::GetFolder (const QString& tag)
321 {
322 auto& c = Root_->C_;
323 for (const auto& item : c)
324 if (item->Tag_ == tag)
325 return item;
326
327 const auto& item = std::make_shared<FlatTreeItem> ();
329 item->Tag_ = tag;
330 item->Parent_ = Root_;
331
332 int size = c.size ();
333 beginInsertRows (QModelIndex (), size, size);
334 c.append (item);
335 endInsertRows ();
336
337 return item;
338 }
339
340 const FlatTreeItem& FlatToFoldersProxyModel::ToFlatOrRoot (const QModelIndex& idx) const
341 {
342 return idx.isValid () ? *ToFlat (idx) : *Root_;
343 }
344
345 void FlatToFoldersProxyModel::HandleRowInserted (int i)
346 {
347 const auto& idx = SourceModel_->index (i, 0);
348
349 auto tags = idx.data (RoleTags).toStringList ();
350
351 if (tags.isEmpty ())
352 tags << QString ();
353
355
356 for (const auto& tag : qAsConst (tags))
357 AddForTag (tag, pidx);
358 }
359
360 void FlatToFoldersProxyModel::HandleRowRemoved (int i)
361 {
362 QAbstractItemModel *model = SourceModel_;
363 QModelIndex idx = model->index (i, 0);
364
365 QStringList tags = idx.data (RoleTags).toStringList ();
366
367 if (tags.isEmpty ())
368 tags << QString ();
369
371
372 for (const auto& tag : tags)
373 RemoveFromTag (tag, pidx);
374 }
375
376 void FlatToFoldersProxyModel::AddForTag (const QString& tag,
378 {
379 FlatTreeItem_ptr folder = GetFolder (tag);
380
381 const auto& item = std::make_shared<FlatTreeItem> ();
383 item->Index_ = pidx;
384 item->Parent_ = folder;
385 item->Tag_ = tag;
386
387 int size = folder->C_.size ();
388 QModelIndex iidx = index (Root_->C_.indexOf (folder), 0);
389 beginInsertRows (iidx, size, size);
390 folder->C_.append (item);
391 Items_.insert (pidx, item);
392 endInsertRows ();
393 }
394
395 void FlatToFoldersProxyModel::RemoveFromTag (const QString& tag,
397 {
398 const auto& folder = GetFolder (tag);
399 auto& c = folder->C_;
400 int findex = Root_->C_.indexOf (folder);
401 for (int i = 0, size = c.size ();
402 i < size; ++i)
403 {
404 if (c.at (i)->Index_ != pidx)
405 continue;
406
407 beginRemoveRows (index (findex, 0), i, i);
408 Items_.remove (pidx, c.at (i));
409 c.removeAt (i);
410 endRemoveRows ();
411 break;
412 }
413
414 if (c.isEmpty ())
415 {
417 Root_->C_.removeAt (findex);
418 endRemoveRows ();
419 }
420 }
421
422 void FlatToFoldersProxyModel::HandleChanged (const QModelIndex& idx)
423 {
424 auto newTags = Util::AsSet (idx.data (RoleTags).toStringList ());
425 if (newTags.isEmpty ())
426 newTags << QString {};
427
429
430 const auto& oldTags = Util::MapAs<QSet> (Items_.values (pidx), [] (const auto& item) { return item->Tag_; });
431
432 const auto added = QSet<QString> (newTags).subtract (oldTags);
433 const auto removed = QSet<QString> (oldTags).subtract (newTags);
434 const auto changed = QSet<QString> (newTags).intersect (oldTags);
435
436 for (const auto& ch : changed)
437 {
438 FlatTreeItem_ptr folder = GetFolder (ch);
439
441 int findex = Root_->C_.indexOf (folder);
443 for (int i = 0, size = c.size ();
444 i < size; ++i)
445 {
446 if (c.at (i)->Index_ != pidx)
447 continue;
448
449 emit dataChanged (index (i, 0, fmi),
450 index (i, columnCount () - 1, fmi));
451 break;
452 }
453 }
454
455 for (const auto& rem : removed)
456 RemoveFromTag (rem, pidx);
457
458 for (const auto& add : added)
459 AddForTag (add, pidx);
460 }
461
462 void FlatToFoldersProxyModel::HandleDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
463 {
465 {
466 topLeft.sibling (topLeft.row (), 0),
467 bottomRight.sibling (bottomRight.row (), 0)
468 };
469 for (const auto& index : range.indexes ())
470 HandleChanged (index);
471 }
472
473 void FlatToFoldersProxyModel::HandleModelReset ()
474 {
475 if (const int size = Root_->C_.size ())
476 {
477 beginRemoveRows (QModelIndex (), 0, size - 1);
478 Root_->C_.clear ();
479 Items_.clear ();
480 endRemoveRows ();
481 }
482
483 if (SourceModel_)
484 for (int i = 0, size = SourceModel_->rowCount (); i < size; ++i)
485 HandleRowInserted (i);
486 }
487
488 void FlatToFoldersProxyModel::HandleRowsInserted (const QModelIndex&, int start, int end)
489 {
490 for (int i = start; i <= end; ++i)
491 HandleRowInserted (i);
492 }
493
494 void FlatToFoldersProxyModel::HandleRowsAboutToBeRemoved (const QModelIndex&, int start, int end)
495 {
496 for (int i = start; i <= end; ++i)
497 HandleRowRemoved (i);
498 }
499}
500
Tags manager's interface.
virtual QString GetTag(tag_id id) const =0
Returns the tag with the given id.
QVariant headerData(int, Qt::Orientation, int) const override
Qt::DropActions supportedDropActions() const override
int rowCount(const QModelIndex &={}) const override
QAbstractItemModel * GetSourceModel() 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
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
Container< T > Filter(const Container< T > &c, F f)
Definition prelude.h:118
auto AsSet(const T &cont)
FlatTreeItem * ToFlat(const QModelIndex &idx)
@ RoleTags
Definition structures.h:178
STL namespace.
QList< FlatTreeItem_ptr > C_