Unity 8
qlimitproxymodelqml.cpp
1 /*
2  * Copyright (C) 2012, 2013 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 // self
18 #include "qlimitproxymodelqml.h"
19 
20 // Qt
21 #include <QDebug>
22 
23 QLimitProxyModelQML::QLimitProxyModelQML(QObject *parent)
24  : QIdentityProxyModel(parent)
25  , m_limit(-1)
26  , m_sourceInserting(false)
27  , m_sourceRemoving(false)
28  , m_dataChangedBegin(-1)
29  , m_dataChangedEnd(-1)
30 {
31  connect(this, &QLimitProxyModelQML::modelReset, this, &QLimitProxyModelQML::countChanged);
32  connect(this, &QLimitProxyModelQML::rowsInserted, this, &QLimitProxyModelQML::countChanged);
33  connect(this, &QLimitProxyModelQML::rowsRemoved, this, &QLimitProxyModelQML::countChanged);
34 }
35 
36 QHash<int, QByteArray> QLimitProxyModelQML::roleNames() const
37 {
38  return sourceModel() ? sourceModel()->roleNames() : QHash<int, QByteArray>();
39 }
40 
41 void
42 QLimitProxyModelQML::setModel(QAbstractItemModel *itemModel)
43 {
44  if (itemModel != sourceModel()) {
45  if (sourceModel() != nullptr) {
46  sourceModel()->disconnect(this);
47  }
48 
49  setSourceModel(itemModel);
50 
51  if (sourceModel() != nullptr) {
52  // Disconnect the QIdentityProxyModel handling for rows removed/added...
53  disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, this, nullptr);
54  disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, nullptr);
55  disconnect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, nullptr);
56  disconnect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, nullptr);
57 
58  // ... and use our own
59  connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted,
60  this, &QLimitProxyModelQML::sourceRowsAboutToBeInserted);
61  connect(sourceModel(), &QAbstractItemModel::rowsInserted,
62  this, &QLimitProxyModelQML::sourceRowsInserted);
63  connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved,
64  this, &QLimitProxyModelQML::sourceRowsAboutToBeRemoved);
65  connect(sourceModel(), &QAbstractItemModel::rowsRemoved,
66  this, &QLimitProxyModelQML::sourceRowsRemoved);
67  }
68  Q_EMIT modelChanged();
69  }
70 }
71 
72 int
73 QLimitProxyModelQML::rowCount(const QModelIndex &parent) const
74 {
75  if (parent.isValid()) // We are not a tree
76  return 0;
77 
78  const int unlimitedCount = QIdentityProxyModel::rowCount(parent);
79  return m_limit < 0 ? unlimitedCount : qMin(m_limit, unlimitedCount);
80 }
81 
82 int
83 QLimitProxyModelQML::limit() const
84 {
85  return m_limit;
86 }
87 
88 void
89 QLimitProxyModelQML::setLimit(int limit)
90 {
91  if (limit != m_limit) {
92  bool inserting = false;
93  bool removing = false;
94  const int oldCount = rowCount();
95  const int unlimitedCount = QIdentityProxyModel::rowCount();
96  if (m_limit < 0) {
97  if (limit < oldCount) {
98  removing = true;
99  beginRemoveRows(QModelIndex(), limit, oldCount - 1);
100  }
101  } else if (limit < 0) {
102  if (m_limit < unlimitedCount) {
103  inserting = true;
104  beginInsertRows(QModelIndex(), m_limit, unlimitedCount - 1);
105  }
106  } else {
107  if (limit > m_limit && unlimitedCount > m_limit) {
108  inserting = true;
109  beginInsertRows(QModelIndex(), m_limit, qMin(limit, unlimitedCount) - 1);
110  } else if (limit < m_limit && limit < oldCount) {
111  removing = true;
112  beginRemoveRows(QModelIndex(), limit, oldCount - 1);
113  }
114  }
115 
116  m_limit = limit;
117 
118  if (inserting) {
119  endInsertRows();
120  } else if (removing) {
121  endRemoveRows();
122  }
123 
124  Q_EMIT limitChanged();
125  }
126 }
127 
128 void
129 QLimitProxyModelQML::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
130 {
131  if (m_limit < 0) {
132  beginInsertRows(mapFromSource(parent), start, end);
133  m_sourceInserting = true;
134  } else if (start < m_limit) {
135  const int nSourceAddedItems = end - start + 1;
136  const int currentCount = QIdentityProxyModel::rowCount();
137  if (currentCount + nSourceAddedItems <= m_limit) {
138  // After Inserting items we will be under the limit
139  // so just proceed with the insertion normally
140  beginInsertRows(mapFromSource(parent), start, end);
141  m_sourceInserting = true;
142  } else if (currentCount >= m_limit) {
143  // We are already over the limit so to our users we are not adding items, just
144  // changing it's data, i.e we had something like
145  // A B C D E
146  // with a limit of 5
147  // after inserting (let's say three 'F' at position 1) we will have
148  // A F F F B
149  // so we just need to signal a dataChanged from 1 to 4
150  m_dataChangedBegin = start;
151  m_dataChangedEnd = m_limit - 1;
152  } else { // currentCount < m_limit && currentCount + nSourceAddedItems > m_limit
153  // We have less items than the limit but after adding them we will be over
154  // To our users this means we need to insert some items and change the
155  // data of some others, i.e we had something like
156  // A B C
157  // with a limit of 5
158  // after inserting (let's say three 'F' at position 1) we will have
159  // A F F F B
160  // so we need to signal an insetion from position 1 to 2, instead of from
161  // position 1 to 3 and a after that a data changed from 3 to 4
162  const int nItemsToInsert = m_limit - currentCount;
163  beginInsertRows(mapFromSource(parent), start, start + nItemsToInsert - 1);
164  m_sourceInserting = true;
165  m_dataChangedBegin = start + nItemsToInsert;
166  m_dataChangedEnd = m_limit - 1;
167  if (m_dataChangedBegin > m_dataChangedEnd) {
168  // Just in case we were empty and insert 6 items with a limit of 5
169  // We don't want to signal a dataChanged from 5 to 4
170  m_dataChangedBegin = -1;
171  m_dataChangedEnd = -1;
172  }
173  }
174  }
175 }
176 
177 void
178 QLimitProxyModelQML::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
179 {
180  if (m_limit < 0) {
181  beginRemoveRows(mapFromSource(parent), start, end);
182  m_sourceRemoving = true;
183  } else if (start < m_limit) {
184  const int nSourceRemovedItems = end - start + 1;
185  const int currentCount = QIdentityProxyModel::rowCount();
186  if (currentCount <= m_limit) {
187  // We are already under the limit so
188  // so just proceed with the removal normally
189  beginRemoveRows(mapFromSource(parent), start, end);
190  m_sourceRemoving = true;
191  } else if (currentCount - nSourceRemovedItems >= m_limit) {
192  // Even after removing items we will be at or over the limit
193  // So to our users we are not removing anything, just changing the data
194  // i.e. we had a internal model with
195  // A B C D E F G H
196  // and a limit of 5, our users just see
197  // A B C D E
198  // so if we remove 3 items starting at 1 we have to expose
199  // A E F G H
200  // that is, a dataChanged from 1 to 4
201  m_dataChangedBegin = start;
202  m_dataChangedEnd = m_limit - 1;
203  } else { // currentCount > m_limit && currentCount - nSourceRemovedItems < m_limit
204  // We have more items than the limit but after removing we will be below it
205  // So to our users we both removing and changing the data
206  // i.e. we had a internal model with
207  // A B C D E F G
208  // and a limit of 5, our users just see
209  // A B C D E
210  // so if we remove items from 1 to 3 we have to expose
211  // A E F G
212  // that is, a remove from 4 to 4 and a dataChanged from 1 to 3
213  const int nItemsToRemove = m_limit - (currentCount - nSourceRemovedItems);
214  beginRemoveRows(mapFromSource(parent), m_limit - nItemsToRemove, m_limit - 1);
215  m_sourceRemoving = true;
216  m_dataChangedBegin = start;
217  m_dataChangedEnd = m_limit - nItemsToRemove - 1;
218  if (m_dataChangedBegin > m_dataChangedEnd) {
219  m_dataChangedBegin = -1;
220  m_dataChangedEnd = -1;
221  }
222  }
223  }
224 }
225 
226 void
227 QLimitProxyModelQML::sourceRowsInserted(const QModelIndex & /*parent*/, int /*start*/, int /*end*/)
228 {
229  if (m_sourceInserting) {
230  endInsertRows();
231  m_sourceInserting = false;
232  }
233  if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) {
234  dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0));
235  m_dataChangedBegin = -1;
236  m_dataChangedEnd = -1;
237  }
238 }
239 
240 void
241 QLimitProxyModelQML::sourceRowsRemoved(const QModelIndex & /*parent*/, int /*start*/, int /*end*/)
242 {
243  if (m_sourceRemoving) {
244  endRemoveRows();
245  m_sourceRemoving = false;
246  }
247  if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) {
248  dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0));
249  m_dataChangedBegin = -1;
250  m_dataChangedEnd = -1;
251  }
252 }