LeechCraft 0.6.70-19214-g121e5318a7
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 <algorithm>
11#include <QSet>
12#include <QMimeData>
14#include <interfaces/iinfo.h>
16#include "modelsanitizer.h"
17
18namespace LC::Util
19{
21 {
22 enum class Type : std::uint8_t
23 {
24 Root,
25 Folder,
26 Item
27 };
28
30 FlatTreeItem * const Parent_ = nullptr;
31
33 QPersistentModelIndex Index_;
34 QString Tag_;
35
36 FlatTreeItem () = default;
37
38 FlatTreeItem (Type type, FlatTreeItem& parent, QString tag)
39 : Type_ { type }
40 , Parent_ { &parent }
41 , Tag_ { std::move (tag) }
42 {
43 }
44
45 int Row () const
46 {
47 if (!Parent_)
48 return 0;
49
50 const auto& siblings = Parent_->C_;
51 const auto pos = std::ranges::find_if (siblings, [this] (const auto& sibling) { return sibling.get () == this; });
52 return pos - siblings.begin ();
53 }
54
55 FlatTreeItem_ptr AddFolder (const QString& tag)
56 {
57 return C_.emplaceBack (std::make_shared<FlatTreeItem> (Type::Folder, *this, tag));
58 }
59
60 FlatTreeItem_ptr AddLeaf (const QPersistentModelIndex& index)
61 {
62 const auto& leaf = C_.emplaceBack (std::make_shared<FlatTreeItem> (Type::Item, *this, Tag_));
63 leaf->Index_ = index;
64 return leaf;
65 }
66 };
67
68 namespace
69 {
70 FlatTreeItem* ToFlat (const QModelIndex& idx)
71 {
72 return static_cast<FlatTreeItem*> (idx.internalPointer ());
73 }
74
75 QStringList TagsOf (const QModelIndex& idx)
76 {
77 auto tags = idx.data (+CustomDataRoles::Tags).toStringList ();
78 if (tags.isEmpty ())
79 tags << QString {};
80 return tags;
81 }
82 }
83
85 : QAbstractItemModel { parent }
86 , TM_ { itm }
87 , Root_ { std::make_shared<FlatTreeItem> () }
88 {
90 }
91
92 int FlatToFoldersProxyModel::columnCount (const QModelIndex&) const
93 {
94 return SourceModel_ ?
95 SourceModel_->columnCount ({}) :
96 0;
97 }
98
99 QVariant FlatToFoldersProxyModel::data (const QModelIndex& index, int role) const
100 {
101 if (!index.isValid ())
102 return {};
103
104 const auto fti = ToFlat (index);
105
106 if (fti->Type_ == FlatTreeItem::Type::Item)
107 {
108 const auto& source = fti->Index_;
109 return source.sibling (source.row (), index.column ()).data (role);
110 }
111
112 if (fti->Type_ == FlatTreeItem::Type::Folder && index.column () == 0)
113 {
114 switch (role)
115 {
116 case Qt::DisplayRole:
117 {
118 if (fti->Tag_.isEmpty ())
119 return tr ("untagged");
120
121 const auto& ut = TM_->GetTag (fti->Tag_);
122 return ut.isEmpty () ? tr ("<unknown tag>") : ut;
123 }
125 return fti->Tag_;
126 default:
127 return {};
128 }
129 }
130
131 return {};
132 }
133
134 QVariant FlatToFoldersProxyModel::headerData (int section, Qt::Orientation orient, int role) const
135 {
136 if (!SourceModel_)
137 return {};
138
139 return SourceModel_->headerData (section, orient, role);
140 }
141
142 Qt::ItemFlags FlatToFoldersProxyModel::flags (const QModelIndex& index) const
143 {
144 if (!index.isValid ())
145 return Qt::ItemIsDropEnabled;
146
147 if (const auto fti = ToFlat (index);
148 fti->Type_ == FlatTreeItem::Type::Item)
149 {
150 const auto& source = fti->Index_;
151 return source.sibling (source.row (), index.column ()).flags ();
152 }
153
154 return Qt::ItemIsSelectable |
155 Qt::ItemIsEnabled |
156 Qt::ItemIsDragEnabled |
157 Qt::ItemIsDropEnabled;
158 }
159
160 bool FlatToFoldersProxyModel::setData (const QModelIndex& index, const QVariant& value, int role)
161 {
162 if (!index.isValid ())
163 return false;
164
165 const auto fti = ToFlat (index);
166 if (fti->Type_ != FlatTreeItem::Type::Item)
167 return false;
168
169 const auto& source = fti->Index_;
170 return SourceModel_->setData (source.sibling (source.row (), index.column ()), value, role);
171 }
172
173 QModelIndex FlatToFoldersProxyModel::index (int row, int column, const QModelIndex& parent) const
174 {
175 if (!hasIndex (row, column, parent))
176 return {};
177
178 return createIndex (row, column, ToFlatOrRoot (parent).C_.at (row).get ());
179 }
180
181 QModelIndex FlatToFoldersProxyModel::parent (const QModelIndex& index) const
182 {
183 const auto parent = ToFlatOrRoot (index).Parent_;
184 if (!parent || parent->Type_ == FlatTreeItem::Type::Root)
185 return {};
186
187 return IndexOf (*parent);
188 }
189
190 int FlatToFoldersProxyModel::rowCount (const QModelIndex& index) const
191 {
192 if (index.column () > 0)
193 return 0;
194
195 return ToFlatOrRoot (index).C_.size ();
196 }
197
199 {
200 return SourceModel_ ?
201 SourceModel_->supportedDropActions () :
202 QAbstractItemModel::supportedDropActions ();
203 }
204
206 {
207 return SourceModel_ ?
208 SourceModel_->mimeTypes () :
209 QAbstractItemModel::mimeTypes ();
210 }
211
212 QMimeData* FlatToFoldersProxyModel::mimeData (const QModelIndexList& indexes) const
213 {
214 if (!SourceModel_)
215 return QAbstractItemModel::mimeData (indexes);
216
217 QModelIndexList sourceIdxs;
218 for (const auto& index : indexes)
219 {
220 auto item = static_cast<FlatTreeItem*> (index.internalPointer ());
221 switch (item->Type_)
222 {
224 sourceIdxs << MapToSource (index);
225 break;
227 for (const auto& subItem : item->C_)
228 sourceIdxs << subItem->Index_;
229 break;
230 default:
231 break;
232 }
233 }
234
235 return SourceModel_->mimeData (sourceIdxs);
236 }
237
238 bool FlatToFoldersProxyModel::dropMimeData (const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex& parent)
239 {
240 if (!SourceModel_)
241 return false;
242
243 QMimeData modified;
244 for (const auto& format : data->formats ())
245 modified.setData (format, data->data (format));
246
247 if (parent.isValid ())
248 modified.setData (QStringLiteral ("x-leechcraft/tag"), ToFlat (parent)->Tag_.toLatin1 ());
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 nullptr,
258 this,
259 nullptr);
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 // Source layout changes aren't forwarded either: the class contract
268 // requires rows to move as units, which leaves nothing to show here.
269 connect (model,
270 &QAbstractItemModel::headerDataChanged,
271 this,
272 &QAbstractItemModel::headerDataChanged);
273 connect (model,
274 &QAbstractItemModel::dataChanged,
275 this,
276 &FlatToFoldersProxyModel::HandleDataChanged);
277 connect (model,
278 &QAbstractItemModel::modelReset,
279 this,
280 &FlatToFoldersProxyModel::HandleModelReset);
281 connect (model,
282 &QAbstractItemModel::rowsInserted,
283 this,
284 &FlatToFoldersProxyModel::HandleRowsInserted);
285 connect (model,
286 &QAbstractItemModel::rowsAboutToBeRemoved,
287 this,
288 &FlatToFoldersProxyModel::HandleRowsAboutToBeRemoved);
289 }
290
291 HandleModelReset ();
292 }
293
294 QAbstractItemModel* FlatToFoldersProxyModel::GetSourceModel () const
295 {
296 return SourceModel_;
297 }
298
299 QModelIndex FlatToFoldersProxyModel::MapToSource (const QModelIndex& proxy) const
300 {
301 if (!GetSourceModel ())
302 return {};
303
304 if (!proxy.isValid ())
305 return {};
306
307 const auto item = ToFlat (proxy);
308
309 if (item->Type_ != FlatTreeItem::Type::Item)
310 return {};
311
312 return item->Index_;
313 }
314
316 {
317 QList<QModelIndex> result;
318 for (const auto& leaf : Items_.values (QPersistentModelIndex { source }))
319 result << IndexOf (*leaf);
320 return result;
321 }
322
323 bool FlatToFoldersProxyModel::IsFolder (const QModelIndex& index) const
324 {
325 const auto item = ToFlat (index);
326 return item && item->Type_ == FlatTreeItem::Type::Folder;
327 }
328
330 {
331 if (!IsFolder (index))
332 return {};
333
334 const auto& children = ToFlat (index)->C_;
335 QList<QModelIndex> result;
336 result.reserve (children.size ());
337 for (const auto& child : children)
338 result << child->Index_;
339 return result;
340 }
341
343 {
344 const auto& children = GetChildren (index);
345
346 QList<QVariant> result;
347 result.reserve (children.size ());
348 for (const auto& child : children)
349 result.push_back (child.data (role));
350 return result;
351 }
352
353 FlatTreeItem_ptr FlatToFoldersProxyModel::FindFolder (const QString& tag) const
354 {
355 const auto& folders = Root_->C_;
356 const auto pos = std::ranges::find (folders, tag, &FlatTreeItem::Tag_);
357 return pos == folders.end () ? nullptr : *pos;
358 }
359
360 FlatTreeItem_ptr FlatToFoldersProxyModel::GetFolder (const QString& tag)
361 {
362 if (const auto& folder = FindFolder (tag))
363 return folder;
364
365 const auto row = Root_->C_.size ();
366 beginInsertRows ({}, row, row);
367 const auto& folder = Root_->AddFolder (tag);
368 endInsertRows ();
369 return folder;
370 }
371
372 const FlatTreeItem& FlatToFoldersProxyModel::ToFlatOrRoot (const QModelIndex& idx) const
373 {
374 return idx.isValid () ? *ToFlat (idx) : *Root_;
375 }
376
377 QModelIndex FlatToFoldersProxyModel::IndexOf (const FlatTreeItem& item, int column) const
378 {
379 return createIndex (item.Row (), column, &item);
380 }
381
382 void FlatToFoldersProxyModel::AddForTag (const QString& tag, const QPersistentModelIndex& pidx)
383 {
384 const auto& folder = GetFolder (tag);
385 const auto row = folder->C_.size ();
386 beginInsertRows (IndexOf (*folder), row, row);
387 Items_.insert (pidx, folder->AddLeaf (pidx));
388 endInsertRows ();
389 }
390
391 void FlatToFoldersProxyModel::RemoveLeaf (const FlatTreeItem_ptr& leaf)
392 {
393 const auto folder = leaf->Parent_;
394 const auto leafRow = leaf->Row ();
395 beginRemoveRows (IndexOf (*folder), leafRow, leafRow);
396 Items_.remove (leaf->Index_, leaf);
397 folder->C_.removeAt (leafRow);
398 endRemoveRows ();
399
400 if (!folder->C_.isEmpty ())
401 return;
402
403 const auto folderRow = folder->Row ();
404 beginRemoveRows ({}, folderRow, folderRow);
405 Root_->C_.removeAt (folderRow);
406 endRemoveRows ();
407 }
408
409 void FlatToFoldersProxyModel::HandleDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles)
410 {
411 for (int row = topLeft.row (); row <= bottomRight.row (); ++row)
412 {
413 const QPersistentModelIndex pidx { topLeft.sibling (row, 0) };
414 auto tags = AsSet (TagsOf (pidx));
415 for (const auto& leaf : Items_.values (pidx))
416 if (tags.remove (leaf->Tag_))
417 emit dataChanged (IndexOf (*leaf), IndexOf (*leaf, columnCount () - 1), roles);
418 else
419 RemoveLeaf (leaf);
420
421 for (const auto& tag : tags)
422 AddForTag (tag, pidx);
423 }
424 }
425
426 void FlatToFoldersProxyModel::HandleModelReset ()
427 {
428 beginResetModel ();
429 Items_.clear ();
430 Root_->C_.clear ();
431 if (SourceModel_)
432 for (int i = 0, size = SourceModel_->rowCount (); i < size; ++i)
433 {
434 const QPersistentModelIndex pidx { SourceModel_->index (i, 0) };
435 for (const auto& tag : TagsOf (pidx))
436 {
437 auto folder = FindFolder (tag);
438 if (!folder)
439 folder = Root_->AddFolder (tag);
440 Items_.insert (pidx, folder->AddLeaf (pidx));
441 }
442 }
443 endResetModel ();
444 }
445
446 void FlatToFoldersProxyModel::HandleRowsInserted (const QModelIndex&, int start, int end)
447 {
448 for (int i = start; i <= end; ++i)
449 {
450 const QPersistentModelIndex pidx { SourceModel_->index (i, 0) };
451 for (const auto& tag : TagsOf (pidx))
452 AddForTag (tag, pidx);
453 }
454 }
455
456 void FlatToFoldersProxyModel::HandleRowsAboutToBeRemoved (const QModelIndex&, int start, int end)
457 {
458 for (int i = start; i <= end; ++i)
459 for (const auto& leaf : Items_.values (QPersistentModelIndex { SourceModel_->index (i, 0) }))
460 RemoveLeaf (leaf);
461 }
462}
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
bool setData(const QModelIndex &, const QVariant &, int=Qt::EditRole) 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)
void InstallModelSanitizer(QAbstractItemModel &model)
FlatTreeItem(Type type, FlatTreeItem &parent, QString tag)
QList< FlatTreeItem_ptr > C_
FlatTreeItem_ptr AddLeaf(const QPersistentModelIndex &index)
FlatTreeItem_ptr AddFolder(const QString &tag)