diff options
-rw-r--r-- | libqmenumodel/src/qmenumodel.cpp | 108 | ||||
-rw-r--r-- | libqmenumodel/src/qmenumodel.h | 14 | ||||
-rw-r--r-- | tests/client/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/client/modelsignalstest.cpp | 231 | ||||
-rw-r--r-- | tests/client/modeltest.cpp | 10 |
5 files changed, 322 insertions, 42 deletions
diff --git a/libqmenumodel/src/qmenumodel.cpp b/libqmenumodel/src/qmenumodel.cpp index 57c61d0..5127cc2 100644 --- a/libqmenumodel/src/qmenumodel.cpp +++ b/libqmenumodel/src/qmenumodel.cpp @@ -38,7 +38,11 @@ extern "C" { QMenuModel::QMenuModel(GMenuModel *other, QObject *parent) : QAbstractListModel(parent), m_menuModel(0), - m_signalChangedId(0) + m_signalChangedId(0), + m_rowCount(0), + m_currentOperationPosition(0), + m_currentOperationAdded(0), + m_currentOperationRemoved(0) { m_cache = new QHash<int, QMenuModel*>; setMenuModel(other); @@ -87,7 +91,7 @@ QVariantMap QMenuModel::get(int row) const */ int QMenuModel::count() const { - return rowCount(); + return m_rowCount; } /*! \internal */ @@ -105,12 +109,13 @@ void QMenuModel::setMenuModel(GMenuModel *other) if (m_menuModel) { g_object_ref(m_menuModel); - // this will trigger the menu load - (void) g_menu_model_get_n_items(m_menuModel); + m_rowCount = g_menu_model_get_n_items(m_menuModel); m_signalChangedId = g_signal_connect(m_menuModel, "items-changed", G_CALLBACK(QMenuModel::onItemsChanged), this); + } else { + m_rowCount = 0; } endResetModel(); @@ -160,25 +165,34 @@ QHash<int, QByteArray> QMenuModel::roleNames() const QVariant QMenuModel::data(const QModelIndex &index, int role) const { QVariant attribute; - int rowCountValue = rowCount(); - if ((rowCountValue > 0) && (index.row() >= 0) && (index.row() < rowCountValue)) { + // Return a empty variant if the remove operation is in progress + if ((m_currentOperationRemoved > 0) && + (index.row() >= m_currentOperationPosition) && + (index.row() < (m_currentOperationPosition + m_currentOperationRemoved))) { + return attribute; + } + + int rowCountValue = rowCount() + (m_currentOperationAdded - m_currentOperationRemoved); + int row = rowIndex(index); + + if ((row >= 0) && (row < rowCountValue)) { if (m_menuModel) { switch (role) { case Action: - attribute = getStringAttribute(index, G_MENU_ATTRIBUTE_ACTION); + attribute = getStringAttribute(row, G_MENU_ATTRIBUTE_ACTION); break; case Label: - attribute = getStringAttribute(index, G_MENU_ATTRIBUTE_LABEL); + attribute = getStringAttribute(row, G_MENU_ATTRIBUTE_LABEL); break; case LinkSection: - attribute = getLink(index, G_MENU_LINK_SECTION); + attribute = getLink(row, G_MENU_LINK_SECTION); break; case LinkSubMenu: - attribute = getLink(index, G_MENU_LINK_SUBMENU); + attribute = getLink(row, G_MENU_LINK_SUBMENU); break; case Extra: - attribute = getExtraProperties(index); + attribute = getExtraProperties(row); break; default: break; @@ -197,20 +211,35 @@ QModelIndex QMenuModel::parent(const QModelIndex &index) const /*! \internal */ int QMenuModel::rowCount(const QModelIndex &) const { - if (m_menuModel) { - return g_menu_model_get_n_items(m_menuModel); + return m_rowCount; +} + +/*! \internal */ +int QMenuModel::rowIndex(const QModelIndex &index) const +{ + int row = index.row(); + /* + if ((m_currentOperationAdded > 0) && (row >= m_currentOperationPosition)) { + row += m_currentOperationAdded; + } else if ((m_currentOperationRemoved > 0) && (row >= (row >= m_currentOperationPosition))) { + } - return 0; + */ + if (row >= m_currentOperationPosition) { + row += (m_currentOperationAdded - m_currentOperationRemoved); + } + return row; } + /*! \internal */ -QVariant QMenuModel::getStringAttribute(const QModelIndex &index, +QVariant QMenuModel::getStringAttribute(int row, const QString &attribute) const { QVariant result; gchar* value = NULL; g_menu_model_get_item_attribute(m_menuModel, - index.row(), + row, attribute.toUtf8().data(), "s", &value); if (value) { @@ -221,24 +250,23 @@ QVariant QMenuModel::getStringAttribute(const QModelIndex &index, } /*! \internal */ -QVariant QMenuModel::getLink(const QModelIndex &index, +QVariant QMenuModel::getLink(int row, const QString &linkName) const { GMenuModel *link = g_menu_model_get_item_link(m_menuModel, - index.row(), + row, linkName.toUtf8().data()); if (link) { QMenuModel* child = 0; - int key = index.row(); - if (m_cache->contains(key)) { - QMenuModel* cached = m_cache->value(key); - if (cached->menuModel() == link) { - child = cached; + if (m_cache->contains(row)) { + child = m_cache->value(row); + if (child->menuModel() != link) { + child->setMenuModel(link); } } if (child == 0) { child = new QMenuModel(link); - m_cache->insert(key, child); + m_cache->insert(row, child); } g_object_unref(link); return QVariant::fromValue<QObject*>(child); @@ -257,9 +285,9 @@ QString QMenuModel::parseExtraPropertyName(const QString &name) const } /*! \internal */ -QVariant QMenuModel::getExtraProperties(const QModelIndex &index) const +QVariant QMenuModel::getExtraProperties(int row) const { - GMenuAttributeIter *iter = g_menu_model_iterate_item_attributes(m_menuModel, index.row()); + GMenuAttributeIter *iter = g_menu_model_iterate_item_attributes(m_menuModel, row); if (iter == NULL) { return QVariant(); } @@ -293,15 +321,35 @@ void QMenuModel::onItemsChanged(GMenuModel *model, QMenuModel *self = reinterpret_cast<QMenuModel*>(data); QHash<int, QMenuModel*>* cache = self->m_cache; + self->m_currentOperationPosition = position; + self->m_currentOperationAdded = added; + self->m_currentOperationRemoved = removed; + int prevcount = g_menu_model_get_n_items(model) + removed - added; if (removed > 0) { + // help function to proxy model + for(int i=position, iMax=(position + removed - 1); i <= iMax; i++) { + if (cache->contains(i)) { + Q_EMIT self->aboutToRemoveLink(cache->value(i), i); + } + } + + // We need clear the removed items before start remove it, + // because we do not have information about the previous data, and QML + // will try to retrieve it during the signal 'rowsAboutToBeRemoved' + QModelIndex start = self->index(position); + QModelIndex end = self->index(position + removed -1); + Q_EMIT self->dataChanged(start, end); + self->beginRemoveRows(QModelIndex(), position, position + removed - 1); + self->m_currentOperationPosition = self->m_currentOperationAdded = self->m_currentOperationRemoved = 0; + self->m_rowCount -= removed; // Remove invalidated menus from the cache for (int i = position, iMax = position + removed; i < iMax; ++i) { if (cache->contains(i)) { - QMenuModel *model = cache->take(i); - model->setMenuModel(NULL); - model->deleteLater(); + QMenuModel *cached = cache->take(i); + cached->setMenuModel(NULL); + cached->deleteLater(); } } // Update the indexes of other cached menus to account for the removals @@ -315,12 +363,14 @@ void QMenuModel::onItemsChanged(GMenuModel *model, if (added > 0) { self->beginInsertRows(QModelIndex(), position, position + added - 1); + self->m_currentOperationPosition = self->m_currentOperationAdded = self->m_currentOperationRemoved = 0; // Update the indexes of cached menus to account for the insertions for (int i = prevcount - removed - 1; i >= position; --i) { if (cache->contains(i)) { cache->insert(i + added, cache->take(i)); } } + self->m_rowCount += added; self->endInsertRows(); } } diff --git a/libqmenumodel/src/qmenumodel.h b/libqmenumodel/src/qmenumodel.h index f186b5f..d83a35c 100644 --- a/libqmenumodel/src/qmenumodel.h +++ b/libqmenumodel/src/qmenumodel.h @@ -54,6 +54,7 @@ public: Q_SIGNALS: void countChanged(); + void aboutToRemoveLink(QMenuModel *link, int row); protected: QMenuModel(GMenuModel *other=0, QObject *parent=0); @@ -67,13 +68,20 @@ private: QHash<int, QMenuModel*>* m_cache; GMenuModel *m_menuModel; guint m_signalChangedId; + guint m_rowCount; - QVariant getStringAttribute(const QModelIndex &index, const QString &attribute) const; - QVariant getLink(const QModelIndex &index, const QString &linkName) const; - QVariant getExtraProperties(const QModelIndex &index) const; + //control variables + int m_currentOperationPosition; + int m_currentOperationAdded; + int m_currentOperationRemoved; + + QVariant getStringAttribute(int row, const QString &attribute) const; + QVariant getLink(int row, const QString &linkName) const; + QVariant getExtraProperties(int row) const; QString parseExtraPropertyName(const QString &name) const; void clearModel(bool destructor=false); int count() const; + int rowIndex(const QModelIndex &index) const; static void onItemsChanged(GMenuModel *model, gint position, gint removed, gint added, gpointer data); }; diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 0fcac9e..54faf34 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -59,6 +59,7 @@ declare_test(actiongrouptest) declare_test(qmltest)
declare_simple_test(convertertest)
declare_simple_test(cachetest)
+declare_simple_test(modelsignalstest)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/qmlfiles.h.in
${CMAKE_CURRENT_BINARY_DIR}/qmlfiles.h)
diff --git a/tests/client/modelsignalstest.cpp b/tests/client/modelsignalstest.cpp new file mode 100644 index 0000000..40a76c6 --- /dev/null +++ b/tests/client/modelsignalstest.cpp @@ -0,0 +1,231 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Renato Araujo Oliveira Filho <renato@canonical.com> + */ + +#include "qmenumodel.h" + +#include <QObject> +#include <QtTest> +#include <QDebug> + +extern "C" { +#include <gio/gio.h> +} + + +class MenuModelTestClass : public QMenuModel +{ + Q_OBJECT +public: + MenuModelTestClass() + : QMenuModel(G_MENU_MODEL(g_menu_new())), m_step(0) + { + } + + void loadModel() + { + GMenu *root = G_MENU(menuModel()); + + // STEP 0 + m_step = 0; + GMenu *section = g_menu_new(); + g_menu_append(section, "msg1", NULL); + g_menu_append_section(root, "section1", G_MENU_MODEL(section)); + + // STEP 1 + m_step++; + GMenu *section2 = g_menu_new(); + g_menu_append_section(root, "section2", G_MENU_MODEL(section2)); + + // STEP 2 + m_step++; + g_menu_append(root, "item1", NULL); + + // STEP 3 + m_step++; + g_menu_insert(root, 1, "item2", NULL); + } + + void clear() + { + GMenu *root = G_MENU(menuModel()); + + // STEP 0 + m_step = 0; + g_menu_remove(root, 0); + + // STEP 1 + m_step++; + g_menu_remove(root, 2); + + // STEP 2 + m_step++; + g_menu_remove(root, 0); + + // STEP 3 + m_step++; + g_menu_remove(root, 0); + } + +public Q_SLOTS: + void checkModelStateBeforeInsert(const QModelIndex &parent, int start, int end) + { + if (m_step == 0) { + QCOMPARE(rowCount(), 0); + QVERIFY(data(index(1), QMenuModel::Label).isNull()); + } else if (m_step == 1) { + QCOMPARE(rowCount(), 1); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QVERIFY(data(index(1), QMenuModel::Label).isNull()); + } else if (m_step == 2) { + QCOMPARE(rowCount(), 2); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(2), QMenuModel::Label).isNull()); + } else if (m_step == 3) { + QCOMPARE(rowCount(), 3); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QCOMPARE(data(index(2), QMenuModel::Label).toString(), QString("item1")); + QVERIFY(data(index(3), QMenuModel::Label).isNull()); + } + } + + void checkModelStateAfterInsert(const QModelIndex &parent, int start, int end) + { + if (m_step == 0) { + QCOMPARE(rowCount(), 1); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QVERIFY(data(index(1), QMenuModel::Label).isNull()); + } else if (m_step == 1) { + QCOMPARE(rowCount(), 2); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(2), QMenuModel::Label).isNull()); + } else if (m_step == 2) { + QCOMPARE(rowCount(), 3); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QCOMPARE(data(index(2), QMenuModel::Label).toString(), QString("item1")); + QVERIFY(data(index(3), QMenuModel::Label).isNull()); + } else if (m_step == 3) { + QCOMPARE(rowCount(), 4); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section1")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("item2")); + QCOMPARE(data(index(2), QMenuModel::Label).toString(), QString("section2")); + QCOMPARE(data(index(3), QMenuModel::Label).toString(), QString("item1")); + QVERIFY(data(index(4), QMenuModel::Label).isNull()); + } + } + + void checkModelStateBeforeRemove(const QModelIndex &parent, int start, int end) + { + if (m_step == 0) { + QCOMPARE(rowCount(), 4); + QVERIFY(data(index(0), QMenuModel::Label).isNull()); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("item2")); + QCOMPARE(data(index(2), QMenuModel::Label).toString(), QString("section2")); + QCOMPARE(data(index(3), QMenuModel::Label).toString(), QString("item1")); + QVERIFY(data(index(4), QMenuModel::Label).isNull()); + } else if (m_step == 1) { + QCOMPARE(rowCount(), 3); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("item2")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(3), QMenuModel::Label).isNull()); + QVERIFY(data(index(4), QMenuModel::Label).isNull()); + } else if (m_step == 2) { + QCOMPARE(rowCount(), 2); + QVERIFY(data(index(0), QMenuModel::Label).isNull()); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(2), QMenuModel::Label).isNull()); + } else if (m_step == 3) { + QCOMPARE(rowCount(), 1); + QVERIFY(data(index(0), QMenuModel::Label).isNull()); + QVERIFY(data(index(1), QMenuModel::Label).isNull()); + } + } + + void checkModelStateAfterRemove(const QModelIndex &parent, int start, int end) + { + if (m_step == 0) { + QCOMPARE(rowCount(), 3); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("item2")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QCOMPARE(data(index(2), QMenuModel::Label).toString(), QString("item1")); + QVERIFY(data(index(3), QMenuModel::Label).isNull()); + } else if (m_step == 1) { + QCOMPARE(rowCount(), 2); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("item2")); + QCOMPARE(data(index(1), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(3), QMenuModel::Label).isNull()); + } else if (m_step == 2) { + QCOMPARE(rowCount(), 1); + QCOMPARE(data(index(0), QMenuModel::Label).toString(), QString("section2")); + QVERIFY(data(index(1), QMenuModel::Label).isNull()); + } else if (m_step == 3) { + QCOMPARE(rowCount(), 0); + QVERIFY(data(index(0), QMenuModel::Label).isNull()); + } + } + +private: + int m_step; +}; + + +class ModelSignalsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase() + { + g_type_init(); + } + + /* + * Test if the model state still correct before and after insert a new row + */ + void testSignalInsertRows() + { + MenuModelTestClass model; + QObject::connect(&model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + &model, SLOT(checkModelStateBeforeInsert(QModelIndex,int,int))); + QObject::connect(&model, SIGNAL(rowsInserted(QModelIndex,int,int)), + &model, SLOT(checkModelStateAfterInsert(QModelIndex,int,int))); + model.loadModel(); + } + + /* + * Test if the model state still correct before and after remove a row + */ + void testSignalRemoveRows() + { + MenuModelTestClass model; + model.loadModel(); + + QObject::connect(&model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + &model, SLOT(checkModelStateBeforeRemove(QModelIndex,int,int))); + QObject::connect(&model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + &model, SLOT(checkModelStateAfterRemove(QModelIndex,int,int))); + model.clear(); + } +}; + +QTEST_MAIN(ModelSignalsTest) + +#include "modelsignalstest.moc" diff --git a/tests/client/modeltest.cpp b/tests/client/modeltest.cpp index 3366177..017f859 100644 --- a/tests/client/modeltest.cpp +++ b/tests/client/modeltest.cpp @@ -29,15 +29,6 @@ extern "C" { #include <gio/gio.h> } -class TestMenuModel : public QMenuModel -{ -public: - TestMenuModel(GMenuModel *other, QObject *parent=0) - : QMenuModel(other, parent) - { - } -}; - class ModelTest : public QObject { Q_OBJECT @@ -296,7 +287,6 @@ private Q_SLOTS: QVariantMap extra = data["extra"].toMap(); QCOMPARE(extra.size(), 13); QCOMPARE(extra["boolean"].toBool(), true); - } }; |