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