LeechCraft 0.6.70-19214-g121e5318a7
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
mergemodel.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
9#include <algorithm>
10#include <stdexcept>
11#include <QMimeData>
12#include <QUrl>
13#include <QtDebug>
14#include <util/sll/qtutil.h>
15#include "mergemodel.h"
16#include "modelsanitizer.h"
17
18namespace LC::Util
19{
20 MergeModel::MergeModel (QStringList headers, QObject *parent)
21 : QAbstractItemModel (parent)
22 , Headers_ (std::move (headers))
23 , Root_ (std::make_shared<ModelItem> ())
24 {
26 }
27
28 int MergeModel::columnCount (const QModelIndex& index) const
29 {
30 if (!index.isValid ())
31 return Headers_.size ();
32 const auto& mapped = mapToSource (index);
33 return mapped.model ()->columnCount (mapped);
34 }
35
36 QVariant MergeModel::headerData (int column, Qt::Orientation orient, int role) const
37 {
38 if (orient != Qt::Horizontal || role != Qt::DisplayRole)
39 return QVariant ();
40
41 return Headers_.at (column);
42 }
43
44 QVariant MergeModel::data (const QModelIndex& index, int role) const
45 {
46 if (!index.isValid ())
47 return QVariant ();
48
49 try
50 {
51 return mapToSource (index).data (role);
52 }
53 catch (const std::exception& e)
54 {
55 qWarning () << Q_FUNC_INFO
56 << e.what ();
57 return {};
58 }
59 }
60
61 Qt::ItemFlags MergeModel::flags (const QModelIndex& index) const
62 {
63 try
64 {
65 return mapToSource (index).flags ();
66 }
67 catch (const std::exception& e)
68 {
69 qWarning () << Q_FUNC_INFO
70 << e.what ();
71 return {};
72 }
73 }
74
75 QModelIndex MergeModel::index (int row, int column, const QModelIndex& parent) const
76 {
77 if (!hasIndex (row, column, parent))
78 return {};
79
80 auto parentItem = parent.isValid () ?
81 static_cast<ModelItem*> (parent.internalPointer ()) :
82 Root_.get ();
83
84 return createIndex (row, column, parentItem->EnsureChild (row));
85 }
86
87 QModelIndex MergeModel::parent (const QModelIndex& index) const
88 {
89 if (!index.isValid () || index.internalPointer () == Root_.get ())
90 return {};
91
92 auto item = static_cast<ModelItem*> (index.internalPointer ());
93 auto parent = item->GetParent ();
94 if (parent == Root_)
95 return {};
96
97 return createIndex (parent->GetRow (), 0, parent.get ());
98 }
99
100 int MergeModel::rowCount (const QModelIndex& parent) const
101 {
102 if (!parent.isValid ())
103 return Root_->GetRowCount ();
104
105 const auto item = static_cast<ModelItem*> (parent.internalPointer ());
106 return item->GetModel ()->rowCount (item->GetIndex ());
107 }
108
109 QStringList MergeModel::mimeTypes () const
110 {
111 QStringList result;
112 for (const auto model : GetAllModels ())
113 for (const auto& type : model->mimeTypes ())
114 if (!result.contains (type))
115 result << type;
116 return result;
117 }
118
119 namespace
120 {
121 void Merge (QMimeData& out, const QMimeData& sub)
122 {
123 for (const auto& format : sub.formats ())
124 if (format != "text/uri-list"_ql && !out.hasFormat (format))
125 out.setData (format, sub.data (format));
126
127 out.setUrls (out.urls () + sub.urls ());
128 }
129 }
130
131 QMimeData* MergeModel::mimeData (const QModelIndexList& indexes) const
132 {
133 std::unique_ptr<QMimeData> result;
134
135 for (const auto& index : indexes)
136 {
137 const auto& src = mapToSource (index);
138
139 std::unique_ptr<QMimeData> subresult { src.model ()->mimeData ({ src }) };
140 if (!subresult)
141 continue;
142
143 if (!result)
144 result = std::move (subresult);
145 else
146 Merge (*result, *subresult);
147 }
148
149 return result.release ();
150 }
151
152 QModelIndex MergeModel::mapFromSource (const QModelIndex& sourceIndex) const
153 {
154 if (!sourceIndex.isValid ())
155 return {};
156
158 auto parent = sourceIndex;
159 while (parent.isValid ())
160 {
161 hier.prepend (parent);
162 parent = parent.parent ();
163 }
164
165 auto currentItem = Root_;
166 for (const auto& idx : hier)
167 {
168 currentItem = currentItem->FindChild (idx);
169 if (!currentItem)
170 {
171 qWarning () << Q_FUNC_INFO
172 << "no next item for"
173 << idx
174 << hier;
175 return {};
176 }
177 }
178
179 return createIndex (currentItem->GetRow (), sourceIndex.column (), currentItem.get ());
180 }
181
182 QModelIndex MergeModel::mapToSource (const QModelIndex& proxyIndex) const
183 {
184 const auto item = proxyIndex.isValid () ?
185 static_cast<ModelItem*> (proxyIndex.internalPointer ()) :
186 Root_.get ();
187
188 const auto& srcIdx = item->GetIndex ();
189 return srcIdx.sibling (srcIdx.row (), proxyIndex.column ());
190 }
191
192 void MergeModel::setSourceModel (QAbstractItemModel*)
193 {
194 throw std::runtime_error ("You should not set source model via setSourceModel()");
195 }
196
197 void MergeModel::SetHeaders (const QStringList& headers)
198 {
199 Headers_ = headers;
200 }
201
202 void MergeModel::AddModel (QAbstractItemModel *model)
203 {
204 if (!model)
205 return;
206
207 if (FindModel (model) != Models_.end ())
208 qFatal () << Q_FUNC_INFO << "model already added:" << model;
209
210 Models_.push_back (model);
211
212 auto withModel = [this, model]<typename... Args> (void (MergeModel::*method) (QAbstractItemModel*, Args...))
213 {
214 return [this, model, method] (Args... args) { (this->*method) (model, args...); };
215 };
216
217 connect (model,
218 &QAbstractItemModel::dataChanged,
219 this,
220 [this] (const QModelIndex& topLeft, const QModelIndex& bottomRight)
221 {
222 emit dataChanged (mapFromSource (topLeft), mapFromSource (bottomRight));
223 });
224 connect (model,
225 &QAbstractItemModel::layoutAboutToBeChanged,
226 this,
228 connect (model,
229 &QAbstractItemModel::layoutChanged,
230 this,
231 withModel (&MergeModel::HandleModelReset));
232 connect (model,
233 &QAbstractItemModel::modelAboutToBeReset,
234 this,
236 connect (model,
237 &QAbstractItemModel::modelReset,
238 this,
239 withModel (&MergeModel::HandleModelReset));
240 connect (model,
241 &QAbstractItemModel::rowsAboutToBeInserted,
242 this,
244 connect (model,
245 &QAbstractItemModel::rowsAboutToBeRemoved,
246 this,
248 connect (model,
249 &QAbstractItemModel::rowsInserted,
250 this,
251 withModel (&MergeModel::HandleRowsInserted));
252 connect (model,
253 &QAbstractItemModel::rowsRemoved,
254 this,
255 withModel (&MergeModel::HandleRowsRemoved));
256
257 if (const auto rc = model->rowCount ())
258 {
259 beginInsertRows ({}, rowCount ({}), rowCount ({}) + rc - 1);
260
261 for (auto i = 0; i < rc; ++i)
262 Root_->AppendChild (model, model->index (i, 0), Root_);
263
264 endInsertRows ();
265 }
266 }
267
268 MergeModel::const_iterator MergeModel::FindModel (const QAbstractItemModel *model) const
269 {
270 return std::find (Models_.begin (), Models_.end (), model);
271 }
272
273 MergeModel::iterator MergeModel::FindModel (const QAbstractItemModel *model)
274 {
275 return std::find (Models_.begin (), Models_.end (), model);
276 }
277
278 void MergeModel::RemoveModel (QAbstractItemModel *model)
279 {
280 auto i = FindModel (model);
281
282 if (i == Models_.end ())
283 {
284 qWarning () << Q_FUNC_INFO << "not found model" << model;
285 return;
286 }
287
288 for (auto r = Root_->begin (); r != Root_->end (); )
289 if ((*r)->GetModel () == model)
290 {
291 const auto idx = static_cast<int> (std::distance (Root_->begin (), r));
292
293 beginRemoveRows ({}, idx, idx);
294 r = Root_->EraseChild (r);
295 endRemoveRows ();
296 }
297 else
298 ++r;
299
300 Models_.erase (i);
301 }
302
303 size_t MergeModel::Size () const
304 {
305 return Models_.size ();
306 }
307
309 {
310 int result = 0;
311 for (auto i = Models_.begin (); i != it; ++i)
312 result += (*i)->rowCount ({});
313 return result;
314 }
315
317 {
318 int result = 0;
319 for (auto i = Models_.begin (); i != it; ++i)
320 result += (*i)->rowCount ({});
321 return result;
322 }
323
325 {
326 const auto child = Root_->GetChild (row);
327 const auto it = FindModel (child->GetModel ());
328
329 if (starting)
330 *starting = GetStartingRow (it);
331
332 return it;
333 }
334
336 {
337 const auto child = Root_->GetChild (row);
338 const auto it = FindModel (child->GetModel ());
339
340 if (starting)
341 *starting = GetStartingRow (it);
342
343 return it;
344 }
345
347 {
349 for (auto p : Models_)
350 if (p)
351 result << p.data ();
352 return result;
353 }
354
355 void MergeModel::HandleRowsAboutToBeInserted (QAbstractItemModel *model, const QModelIndex& parent, int first, int last)
356 {
357 const auto startingRow = parent.isValid () ?
358 0 :
359 GetStartingRow (FindModel (model));
360 beginInsertRows (mapFromSource (parent),
361 first + startingRow, last + startingRow);
362 }
363
364 void MergeModel::HandleRowsAboutToBeRemoved (QAbstractItemModel *model, const QModelIndex& parent, int first, int last)
365 {
366 const auto startingRow = parent.isValid () ?
367 0 :
368 GetStartingRow (FindModel (model));
369 const auto mergedParent = mapFromSource (parent);
370 beginRemoveRows (mergedParent, first + startingRow, last + startingRow);
371
372 const auto rawItem = parent.isValid () ?
373 static_cast<ModelItem*> (mergedParent.internalPointer ()) :
374 Root_.get ();
375 const auto& item = rawItem->shared_from_this ();
376
377 auto it = item->EraseChildren (item->begin () + startingRow + first,
378 item->begin () + startingRow + last + 1);
379
380 RemovalRefreshers_.push ([=] () mutable
381 {
382 for ( ; it != item->end () && (*it)->GetModel () == model; ++it)
383 (*it)->RefreshIndex (startingRow);
384 });
385 }
386
387 void MergeModel::HandleRowsInserted (QAbstractItemModel *model, const QModelIndex& parent, int first, int last)
388 {
389 const auto startingRow = parent.isValid () ?
390 0 :
391 GetStartingRow (FindModel (model));
392
393 const auto rawItem = parent.isValid () ?
394 static_cast<ModelItem*> (mapFromSource (parent).internalPointer ()) :
395 Root_.get ();
396 const auto& item = rawItem->shared_from_this ();
397
398 for ( ; first <= last; ++first)
399 {
400 const auto& srcIdx = model->index (first, 0, parent);
401 item->InsertChild (startingRow + first, model, srcIdx, item);
402 }
403
404 ++last;
405 last += startingRow;
406
407 for (int rc = item->GetRowCount (); last < rc; ++last)
408 {
409 const auto child = item->GetChild (last);
410 if (child->GetModel () != model)
411 break;
412
413 child->RefreshIndex (startingRow);
414 }
415
416 endInsertRows ();
417 }
418
419 void MergeModel::HandleRowsRemoved (QAbstractItemModel*, const QModelIndex&, int, int)
420 {
421 RemovalRefreshers_.pop () ();
422 endRemoveRows ();
423 }
424
425 void MergeModel::HandleModelAboutToBeReset (QAbstractItemModel *model)
426 {
427 if (const auto rc = model->rowCount ())
428 {
429 const auto startingRow = GetStartingRow (FindModel (model));
430 beginRemoveRows ({}, startingRow, rc + startingRow - 1);
431 Root_->EraseChildren (Root_->begin () + startingRow, Root_->begin () + startingRow + rc);
432 endRemoveRows ();
433 }
434 }
435
436 void MergeModel::HandleModelReset (QAbstractItemModel *model)
437 {
438 if (const auto rc = model->rowCount ())
439 {
440 const auto startingRow = GetStartingRow (FindModel (model));
441
442 beginInsertRows ({}, startingRow, rc + startingRow - 1);
443
444 for (int i = 0; i < rc; ++i)
445 Root_->InsertChild (startingRow + i, model, model->index (i, 0, {}), Root_);
446
447 endInsertRows ();
448 }
449 }
450
451 bool MergeModel::AcceptsRow (QAbstractItemModel*, int) const
452 {
453 DefaultAcceptsRowImpl_ = true;
454 return true;
455 }
456
457 int MergeModel::RowCount (QAbstractItemModel *model) const
458 {
459 if (!model)
460 return 0;
461
462 int orig = model->rowCount ();
463 if (DefaultAcceptsRowImpl_)
464 return orig;
465
466 int result = 0;
467 for (int i = 0; i < orig; ++i)
468 result += AcceptsRow (model, i) ? 1 : 0;
469 return result;
470 }
471}
QList< QAbstractItemModel * > GetAllModels() const
Returns all models intalled into this one.
QModelIndex index(int, int, const QModelIndex &=QModelIndex()) const override
const_iterator FindModel(const QAbstractItemModel *model) const
Returns a const_iterator corresponding to the passed model, or one-past-end if no such model is found...
virtual bool AcceptsRow(QAbstractItemModel *model, int row) const
Allows to filter rows from the resulting model.
const_iterator GetModelForRow(int row, int *starting=nullptr) const
Returns the model for the given row.
virtual void HandleRowsAboutToBeInserted(QAbstractItemModel *, const QModelIndex &, int, int)
void AddModel(QAbstractItemModel *model)
Adds a model to the list of source models.
QMimeData * mimeData(const QModelIndexList &indices) const override
Returns the MIME data for the given indices.
virtual void HandleModelReset(QAbstractItemModel *)
size_t Size() const
Returns the number of child models in the merger.
virtual void HandleModelAboutToBeReset(QAbstractItemModel *)
virtual void setSourceModel(QAbstractItemModel *)
int GetStartingRow(const_iterator it) const
Finds starting row for the model pointed by it.
virtual void HandleRowsAboutToBeRemoved(QAbstractItemModel *, const QModelIndex &, int, int)
QModelIndex parent(const QModelIndex &) const override
virtual void HandleRowsRemoved(QAbstractItemModel *, const QModelIndex &, int, int)
virtual QModelIndex mapToSource(const QModelIndex &index) const
Returns the source model index corresponding to the given index from the sorting filter model.
void RemoveModel(QAbstractItemModel *model)
Removes a model from the list of source models.
int columnCount(const QModelIndex &=QModelIndex()) const override
int rowCount(const QModelIndex &=QModelIndex()) const override
void SetHeaders(const QStringList &headers)
Sets the new headers for this model.
Qt::ItemFlags flags(const QModelIndex &) const override
models_t::const_iterator const_iterator
Definition mergemodel.h:48
virtual QModelIndex mapFromSource(const QModelIndex &index) const
Returns the model index in the MergeModel given the index from the source model.
QStringList mimeTypes() const override
Returns the union of MIME types of the models.
models_t::iterator iterator
Definition mergemodel.h:47
QVariant data(const QModelIndex &, int=Qt::DisplayRole) const override
MergeModel(QStringList headers, QObject *parent=nullptr)
Constructs the merge model.
virtual void HandleRowsInserted(QAbstractItemModel *, const QModelIndex &, int, int)
QVariant headerData(int, Qt::Orientation, int=Qt::DisplayRole) const override
iterator EraseChildren(iterator begin, iterator end)
Erases all child items in the given range.
Provides a proxying API on top of an QAbstractItemModel.
Definition modelitem.h:32
QAbstractItemModel * GetModel() const
Returns the wrapped model.
Definition modelitem.cpp:45
const QModelIndex & GetIndex() const
Returns the index this ModelItem instance wraps.
Definition modelitem.cpp:34
void InstallModelSanitizer(QAbstractItemModel &model)