1 /*
2 * Copyright 2012 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Renato Araujo Oliveira Filho <renato@canonical.com>
18 */
19
20 extern "C" {
21 #include <gio/gio.h>
22 }
23
24 #include "inlinesectionmenuproxymodel.h"
25
26 #include <qmenumodel.h>
27 #include <QDebug>
28
29 class SectionIndex
30 {
31 public:
32 SectionIndex(QMenuModel *menu, int index)
33 : menu(menu),
34 index(index)
35 {
36 }
37
38 QMenuModel *menu;
39 int index;
40 };
41
42 class SectionData
43 {
44 public:
45 SectionData(InlineSectionMenuProxyModel *parent, QMenuModel *section, const SectionIndex &index)
46 : parent(parent),
47 section(section),
48 index(index)
49 {
50 QObject::connect(section, SIGNAL(rowsInserted(QModelIndex, int, int)), parent, SLOT(onModelRowsInserted(QModelIndex, int, int)));
51 QObject::connect(section, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), parent, SLOT(onModelRowsRemoved(QModelIndex, int, int)));
52 QObject::connect(section, SIGNAL(modelReset()), parent, SLOT(onModelReseted()));
53 }
54
55 ~SectionData()
56 {
57 section->disconnect(parent);
58 }
59
60 QObject *parent;
61 QMenuModel *section;
62 SectionIndex index;
63 };
64
65 /*!
66 \qmltype InlineSectionMenuProxyModel
67 \inherits QAbstractListModel
68
69 \brief The InlineSectionMenuProxyModel class implement QMenuModels with the sections in the same level as the parent menu.
70
71 \b {This component is under heavy development.}
72
73 \code
74 QDBusMenuModel {
75 id: menuModel
76
77 busType: myBusType
78 busName: myBusName
79 objectPath: myObjectPath
80 }
81
82 InlineSectionMenuProxyModel {
83 id: proxyModel
84
85 baseModel: menuModel
86 }
87 \endcode
88 */
89
90 InlineSectionMenuProxyModel::InlineSectionMenuProxyModel(QObject *parent)
91 : QAbstractListModel(parent),
92 m_baseModel(0)
93 {
CID 12369 - UNINIT_CTOR
Non-static class member "m_count" is not initialized in this constructor nor in any functions that it calls.
94 }
95
96 /*! \internal */
97 InlineSectionMenuProxyModel::~InlineSectionMenuProxyModel()
98 {
99 }
100
101 /*!
102 \qmlproperty QMenuModel* InlineSectionMenuProxyModel::baseModel
103 This property holds the base model used for InlineSectionMenuProxyModel
104 */
105
106 QMenuModel *InlineSectionMenuProxyModel::baseModel() const
107 {
108 return m_baseModel;
109 }
110
111 /*! \internal */
112 void InlineSectionMenuProxyModel::setBaseModel(QMenuModel *model)
113 {
114 if (m_baseModel != model) {
115 beginResetModel();
116
117 clearData();
118 m_baseModel = model;
119 trackModel(m_baseModel);
120 Q_EMIT baseModelChanged(m_baseModel);
121
122 endResetModel();
123 }
124 }
125
126 /*!
127 \qmlmethod PluginModel::get(int)
128
129 Returns the item at index in the model. This allows the item data to be accessed from JavaScript:
130 \b Note: methods should only be called after the Component has completed.
131 */
132 QVariantMap InlineSectionMenuProxyModel::get(int row) const
133 {
134 QVariantMap result;
135
136 QModelIndex index = this->index(row);
137 if (index.isValid()) {
138 QMap<int, QVariant> data = itemData(index);
139 const QHash<int, QByteArray> roleNames = this->roleNames();
140 Q_FOREACH(int i, roleNames.keys()) {
141 result.insert(roleNames[i], data[i]);
142 }
143 }
144 return result;
145 }
146
147 /*! \internal */
148 QHash<int, QByteArray> InlineSectionMenuProxyModel::roleNames() const
149 {
150 static QHash<int, QByteArray> roles;
151 if (roles.isEmpty()) {
152 roles[Action] = "action";
153 roles[Label] = "label";
154 roles[LinkSection] = "linkSection";
155 roles[LinkSubMenu] = "linkSubMenu";
156 roles[Extra] = "extra";
157 roles[Depth] = "depth";
158 }
159 return roles;
160 }
161
162 /*! \internal */
163 QVariant InlineSectionMenuProxyModel::data(const QModelIndex &index, int role) const
164 {
165 QVariant attribute;
166 int rowCountValue = rowCount();
167
168 if ((index.row() >= 0) && (index.row() < rowCountValue)) {
169 int pos = 0;
170 int depth = 0;
171 QMenuModel *base = m_baseModel;
172 int modulePos = getModelAndOffsetForPosition(index.row(), &pos, &base, &depth);
173 if (role == Depth) {
174 attribute = QVariant(depth);
175 } else {
176 QModelIndex index = base->index(modulePos);
177 attribute = base->data(index, role);
178 }
179 }
180 return attribute;
181 }
182
183 /*! \internal */
184 QModelIndex InlineSectionMenuProxyModel::parent(const QModelIndex &) const
185 {
186 return QModelIndex();
187 }
188
189 /*! \internal */
190 int InlineSectionMenuProxyModel::rowCount(const QModelIndex &) const
191 {
192 return m_count;
193 }
194
195 /*!
196 \qmlproperty PluginModel::count
197 The number of data entries in the model.
198
199 \b Note: methods should only be called after the Component has completed.
200 */
201 int InlineSectionMenuProxyModel::count() const
202 {
203 return rowCount();
204 }
205
206 /*! \internal */
207 void InlineSectionMenuProxyModel::onModelRowsInserted(const QModelIndex &, int start, int end)
208 {
209 QMenuModel *sender = qobject_cast<QMenuModel*>(QObject::sender());
210 int added = end - start + 1;
211 int offset = 0;
212 getPositionOffset(m_baseModel, sender, start, &offset);
213
214 //calculate the number of elements added
215 QList<SectionData*> addedSections;
216 for (int i=0, iMax=added; i < iMax; i++) {
217 QMenuModel *section = getSectionFromModel(sender, start + i);
218 if (section) {
219 sectionSize(section, &added, addedSections);
220 }
221 }
222
223 beginInsertRows(QModelIndex(), offset, offset + added - 1);
224
225 //add the new sections on the sections list
226 for (int i=0, iMax=(end - start + 1); i < iMax; i++) {
227 QMenuModel *section = getSectionFromModel(sender, start + i);
228 if (section) {
229 trackModel(section);
230 }
231 }
232
233 m_count += (end - start + 1);
234 endInsertRows();
235 Q_EMIT countChanged(rowCount());
236 }
237
238 /*! \internal */
239 void InlineSectionMenuProxyModel::onModelRowsRemoved(const QModelIndex &, int start, int end)
240 {
241 QMenuModel *sender = qobject_cast<QMenuModel*>(QObject::sender());
242 int removed = end - start + 1;
243 int offset = 0;
244 getPositionOffset(m_baseModel, sender, start, &offset);
245
246 QList<SectionData*> removedSections;
247
248 // calculate the number of elements removed
249 for (int i=0, iMax=removed; i < iMax; i++) {
250 Q_FOREACH(SectionData *data, m_sections) {
251 if ((data->index.menu == sender) && (data->index.index == (start + i))) {
252 sectionSize(data->section, &removed, removedSections);
253 removedSections << data;
254 }
255 }
256 }
257
258 beginRemoveRows(QModelIndex(), offset, offset + removed - 1);
259
260 // remove all the sections related with the current removed positions
261 Q_FOREACH(SectionData *data, removedSections) {
262 m_sections.removeAll(data);
263 delete data;
264 }
265
266 m_count -= removed;
267 endRemoveRows();
268 Q_EMIT countChanged(rowCount());
269 }
270
271 /*! \internal */
272 void InlineSectionMenuProxyModel::onModelReseted()
273 {
274 beginResetModel();
275 clearData();
276 trackModel(m_baseModel);
277 endResetModel();
278 Q_EMIT countChanged(rowCount());
279 }
280
281 /*! \internal */
282 void InlineSectionMenuProxyModel::clearData()
283 {
284 m_count = 0;
285 Q_FOREACH(SectionData *data, m_sections) {
286 delete data;
287 }
288 m_sections.clear();
289 }
290
291 /*! \internal */
292 QMenuModel *InlineSectionMenuProxyModel::getSectionFromModel(QMenuModel *model, int index) const
293 {
294 QModelIndex mi = model->index(index);
295 QVariant vSection = model->data(mi, QMenuModel::LinkSection);
296 if (vSection.isValid()) {
297 return qvariant_cast<QMenuModel*>(vSection);
298 }
299 return 0;
300 }
301
302 /*! \internal */
303 void InlineSectionMenuProxyModel::trackModel(QMenuModel *model, QMenuModel *parent, int index)
304 {
305 m_count += model->rowCount();
306 m_sections << new SectionData(this, model, SectionIndex(parent, index));
307
308 for (int i=0, iMax=model->rowCount(); i < iMax; i++) {
309 QMenuModel *section = getSectionFromModel(model, i);
310 if (section) {
311 trackModel(section, model, i);
312 }
313 }
314 }
315
316 /*! \internal
317 * Get the postion of a element based on a root element this positionoffset will consider any
318 * section before it.
319 *
320 * Ex:
321 * [ 0 ] Menu0
322 * [ 1 ] Menu1 [ 2 ] Menu1.1
323 * [ 3 ] Menu1.2 [ 4 ] Menu1.2.1
324 * [ 5 ] Menu2
325 *
326 */
327 bool InlineSectionMenuProxyModel::getPositionOffset(QMenuModel *root, QMenuModel *model, int pos, int *offset)
328 {
329 int size = (model == root) ? pos + 1 : root->rowCount();
330 for (int i=0; i < size; i++) {
331 if ((root == model) && (pos == i)) {
332 return true;
333 }
334
335 (*offset)++;
336 QMenuModel *section = getSectionFromModel(root, i);
337 if (section) {
338 QMenuModel *target = (model == root) ? NULL : model;
339 bool found = getPositionOffset(section, target, pos, offset);
340 if (found) {
341 return true;
342 }
343 }
344 }
345 return false;
346 }
347
348 /*! \internal
349 * calculate the size of any section including the subsections
350 */
351 void InlineSectionMenuProxyModel::sectionSize(QMenuModel *section, int *size, QList<SectionData*> §ions)
352 {
353 if (section == 0) {
354 return;
355 }
356
357 int menuSize = section->rowCount();
358 for (int i=0; i < menuSize; i++) {
359 QMenuModel *subSection = getSectionFromModel(section, i);
360 if (subSection) {
361 Q_FOREACH(SectionData *data, m_sections) {
362 if (data->index.menu == section) {
363 sections << data;
364 break;
365 }
366 }
367 sectionSize(subSection, size, sections);
368 }
369 }
370 (*size) += menuSize;
371 }
372
373 /*! \internal
374 * Based on a 'pos' this function will search for the section(nextModel) and the real posision
375 * inside of this section.
376 * Ex:
377 * <model0>
378 * Menu0 <model1>
379 * Menu1 -> Menu1.1 <model2>
380 * Menu1.2 -> Menu1.2.1
381 * Menu2
382 *
383 * pos = 5, currentPos =0, nextModel = <model0>. Will return: 2, nextModel = <model0>
384 * pos = 4, currentPos =0, nextModel = <model0>. Will return: 0, nextModel = <model2>
385 */
386 int InlineSectionMenuProxyModel::getModelAndOffsetForPosition(int globalPosition, int *currentPos, QMenuModel **nextModel, int *depth) const
387 {
388 int size = (*nextModel)->rowCount();
389 for (int i=0; i < size; i++) {
390 if (globalPosition == *currentPos) {
391 return i;
392 }
393
394 QMenuModel *section = getSectionFromModel(*nextModel, i);
395 if (section) {
396 (*currentPos)++;
397 int offset = getModelAndOffsetForPosition(globalPosition, currentPos, §ion, depth);
398 if (offset != -1) {
399 *nextModel = section;
400 (*depth)++;
401 return offset;
402 }
403 } else {
404 (*currentPos)++;
405 }
406 }
407 return -1;
408 }