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