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*> &sections)
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, &section, depth);
398             if (offset != -1) {
399                 *nextModel = section;
400                 (*depth)++;
401                 return offset;
402             }
403         } else {
404             (*currentPos)++;
405         }
406     }
407     return -1;
408 }