on xenial help sign in
Fixed CachedUnityMenuModel destruction unhinging indicators. Fixes: 1328646, 1357311
cd04516Nick Dedekind
plugins/Unity/Indicators/CachedUnityMenuModel.qmldeleted−60
@@ -1,60 +0,0 @@
1−/*
2− * Copyright 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 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− * Nick Dedekind <nick.dedekind@canonical.com>
18− */
19−
20−import QtQuick 2.0
21−import QMenuModel 0.1 as QMenuModel
22−import Unity.Indicators 0.1 as Indicators
23−
24−// Make sure we don't duplicate models.
25−Item {
26− id: cachedModel
27− property string busName: ""
28− property string actionsObjectPath: ""
29− property string menuObjectPath: ""
30−
31− readonly property bool ready: busName!=="" && actionsObjectPath!=="" && menuObjectPath!==""
32−
33− property QtObject model: {
34− if (!ready) {
35− return null;
36− }
37−
38− var component = Indicators.UnityMenuModelCache.model(menuObjectPath);
39− if (!component) {
40− component = modelComponent.createObject(cachedModel);
41− return component;
42− }
43− return component;
44− }
45−
46− Component {
47− id: modelComponent
48−
49− QMenuModel.UnityMenuModel {
50− id: unityModel
51− busName: cachedModel.busName
52− actions: { "indicator": cachedModel.actionsObjectPath }
53− menuObjectPath: cachedModel.menuObjectPath
54−
55− Component.onCompleted: {
56− Indicators.UnityMenuModelCache.registerModel(cachedModel.menuObjectPath, unityModel);
57− }
58− }
59− }
60−}
plugins/Unity/Indicators/qmldir−3
@@ -1,6 +1,3 @@
1module Unity.Indicators
1module Unity.Indicators
2plugin IndicatorsQml
2plugin IndicatorsQml
3typeinfo Indicators.qmltypes
3typeinfo Indicators.qmltypes
4−
5−CachedUnityMenuModel 0.1 CachedUnityMenuModel.qml
6−IndicatorBase 0.1 IndicatorBase.qml
plugins/Unity/Indicators/unitymenumodelcache.cpp+22−7
@@ -29,14 +29,29 @@ UnityMenuModelCache::~UnityMenuModelCache()
29{
29{
30}
30}
31
31
32−
33−UnityMenuModel* UnityMenuModelCache::model(const QString& path) const
32+UnityMenuModel* UnityMenuModelCache::model(const QByteArray& bus,
33+ const QByteArray& path,
34+ const QVariantMap& actions)
34{
35{
35− return m_menuModels.value(path, NULL);
36+ if (m_registry.contains(path))
37+ return m_registry[path];
38+
39+ UnityMenuModel* menuModel = new UnityMenuModel;
40+ connect(menuModel, &QObject::destroyed, this, [menuModel, this](QObject*) {
41+ QList<QByteArray> keys = m_registry.keys(menuModel);
42+ Q_FOREACH(const QByteArray& key, keys) {
43+ m_registry.remove(key);
44+ }
45+ });
46+ m_registry[path] = menuModel;
47+
48+ menuModel->setBusName(bus);
49+ menuModel->setMenuObjectPath(path);
50+ menuModel->setActions(actions);
51+ return menuModel;
36}
52}
37
53
38−void UnityMenuModelCache::registerModel(const QString& path, UnityMenuModel* menuModel)
54+bool UnityMenuModelCache::contains(const QByteArray& path)
39{
55{
40− menuModel->setParent(this);
56+ return m_registry.contains(path);
41− m_menuModels[path] = menuModel;
42−}
57+}
plugins/Unity/Indicators/unitymenumodelcache.h+5−3
@@ -34,11 +34,13 @@ public:
34 UnityMenuModelCache(QObject*parent=NULL);
34 UnityMenuModelCache(QObject*parent=NULL);
35 ~UnityMenuModelCache();
35 ~UnityMenuModelCache();
36
36
37− Q_INVOKABLE UnityMenuModel* model(const QString& path) const;
37+ Q_INVOKABLE UnityMenuModel* model(const QByteArray& bus,
38− Q_INVOKABLE void registerModel(const QString& path, UnityMenuModel* menuModel);
38+ const QByteArray& path,
39+ const QVariantMap& actions);
40+ Q_INVOKABLE bool contains(const QByteArray& path);
39
41
40private:
42private:
41− QHash<QString, UnityMenuModel*> m_menuModels;
43+ QHash<QByteArray, UnityMenuModel*> m_registry;
42};
44};
43
45
44#endif // UNITYMENUMODELCACHE_H
46#endif // UNITYMENUMODELCACHE_H
qml/Greeter/Clock.qml+2−1
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import "../Panel/Indicators"
19import Unity.Indicators 0.1 as Indicators
20import Unity.Indicators 0.1 as Indicators
20
21
21Item {
22Item {
@@ -33,7 +34,7 @@ Item {
33 }
34 }
34 }
35 }
35
36
36− Indicators.CachedUnityMenuModel {
37+ CachedUnityMenuModel {
37 id: timeModel
38 id: timeModel
38 objectName: "timeModel"
39 objectName: "timeModel"
39
40
tests/mocks/Unity/Indicators/CachedUnityMenuModel.qml → qml/Panel/Indicators/CachedUnityMenuModel.qmlrenamed+14−5
@@ -18,13 +18,22 @@
18 */
18 */
19
19
20import QtQuick 2.0
20import QtQuick 2.0
21+import QMenuModel 0.1
22+import Unity.Indicators 0.1 as Indicators
21
23
24+// Make sure we don't duplicate models.
22Item {
25Item {
26+ id: cachedModel
23− property string busName: ""
27+ property string busName
24− property string actionsObjectPath: ""
28+ property string actionsObjectPath
25− property string menuObjectPath: ""
29+ property string menuObjectPath
30+ readonly property bool ready: busName!=="" && actionsObjectPath!=="" && menuObjectPath!==""
26
31
27− readonly property bool ready: false
32+ property var model: {
33+ if (!ready) return null;
28
34
29− property QtObject model: null
35+ return Indicators.UnityMenuModelCache.model(cachedModel.busName,
36+ cachedModel.menuObjectPath,
37+ { "indicator": cachedModel.actionsObjectPath });
38+ }
30}
39}
qml/Panel/Indicators/DefaultIndicatorWidget.qml+2−1
@@ -63,6 +63,7 @@ IndicatorBase {
63 }
63 }
64
64
65 Row {
65 Row {
66+ id: iconRow
66 anchors {
67 anchors {
67 top: parent.top
68 top: parent.top
68 bottom: parent.bottom
69 bottom: parent.bottom
@@ -73,7 +74,7 @@ IndicatorBase {
73
74
74 Item {
75 Item {
75 width: itemImage.width + units.gu(1)
76 width: itemImage.width + units.gu(1)
76− anchors { top: parent.top; bottom: parent.bottom }
77+ height: iconRow.height
77
78
78 StatusIcon {
79 StatusIcon {
79 id: itemImage
80 id: itemImage
qml/Panel/Indicators/IndicatorBase.qml+4−2
@@ -34,6 +34,7 @@ Item {
34 property string deviceMenuObjectPath: menuObjectPath
34 property string deviceMenuObjectPath: menuObjectPath
35
35
36 property alias menuModel: cachedModel.model
36 property alias menuModel: cachedModel.model
37+ property alias rootActionState: rootAction
37
38
38 CachedUnityMenuModel {
39 CachedUnityMenuModel {
39 id: cachedModel
40 id: cachedModel
@@ -42,8 +43,9 @@ Item {
42 menuObjectPath: indicatorItem.deviceMenuObjectPath
43 menuObjectPath: indicatorItem.deviceMenuObjectPath
43 }
44 }
44
45
45− property RootActionState rootActionState: RootActionState {
46+ RootActionState {
47+ id: rootAction
46− menu: menuModel
48+ menu: menuModel ? menuModel : null
47 onUpdated: indicatorItem.rootActionStateChanged()
49 onUpdated: indicatorItem.rootActionStateChanged()
48 }
50 }
49}
51}
tests/mocks/QMenuModel/unitymenumodel.cpp+4−5
@@ -17,7 +17,6 @@
17 */
17 */
18
18
19#include "unitymenumodel.h"
19#include "unitymenumodel.h"
20−#include <QDebug>
21
20
22enum MenuRoles {
21enum MenuRoles {
23 LabelRole = Qt::DisplayRole + 1,
22 LabelRole = Qt::DisplayRole + 1,
@@ -38,6 +37,10 @@ UnityMenuModel::UnityMenuModel(QObject *parent)
38{
37{
39}
38}
40
39
40+UnityMenuModel::~UnityMenuModel()
41+{
42+}
43+
41QVariant UnityMenuModel::modelData() const
44QVariant UnityMenuModel::modelData() const
42{
45{
43 return m_modelData;
46 return m_modelData;
@@ -83,10 +86,6 @@ void UnityMenuModel::removeRow(int row)
83 endRemoveRows();
86 endRemoveRows();
84}
87}
85
88
86−UnityMenuModel::~UnityMenuModel()
87−{
88−}
89−
90QByteArray UnityMenuModel::busName() const
89QByteArray UnityMenuModel::busName() const
91{
90{
92 return m_busName;
91 return m_busName;
tests/mocks/Unity/Indicators/CMakeLists.txt+6−1
@@ -14,6 +14,7 @@ set(IndicatorsFakeQml_SOURCES
14 fakeplugin.cpp
14 fakeplugin.cpp
15 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
15 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
16 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
17+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelcache.cpp
17 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelstack.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelstack.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/visibleindicatorsmodel.cpp
19 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/visibleindicatorsmodel.cpp
19 fakeindicatorsmodel.cpp
20 fakeindicatorsmodel.cpp
@@ -33,7 +34,11 @@ add_library(IndicatorsFakeQml SHARED
33)
34)
34add_definitions(-DUNITYINDICATORS_LIBRARY)
35add_definitions(-DUNITYINDICATORS_LIBRARY)
35
36
37+add_dependencies(IndicatorsFakeQml QMenuModelQml)
36−target_link_libraries(IndicatorsFakeQml)
38+target_link_libraries(IndicatorsFakeQml
39+ -L${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel
40+ -lqmenumodel
41+)
37
42
38qt5_use_modules(IndicatorsFakeQml Core Qml Quick)
43qt5_use_modules(IndicatorsFakeQml Core Qml Quick)
39
44
tests/mocks/Unity/Indicators/fakeplugin.cpp+9
@@ -26,9 +26,16 @@
26#include "fakeindicatorsmodel.h"
26#include "fakeindicatorsmodel.h"
27#include "indicators.h"
27#include "indicators.h"
28#include "menucontentactivator.h"
28#include "menucontentactivator.h"
29+#include "unitymenumodelcache.h"
29#include "unitymenumodelstack.h"
30#include "unitymenumodelstack.h"
30#include "visibleindicatorsmodel.h"
31#include "visibleindicatorsmodel.h"
31
32
33+static QObject* menuModelCacheSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
34+ Q_UNUSED(engine);
35+ Q_UNUSED(scriptEngine);
36+ return new UnityMenuModelCache;
37+}
38+
32void IndicatorsFakePlugin::registerTypes(const char * uri)
39void IndicatorsFakePlugin::registerTypes(const char * uri)
33{
40{
34 Q_INIT_RESOURCE(indicators_fake);
41 Q_INIT_RESOURCE(indicators_fake);
@@ -41,6 +48,8 @@ void IndicatorsFakePlugin::registerTypes(const char * uri)
41 qmlRegisterType<UnityMenuModelStack>(uri, 0, 1, "UnityMenuModelStack");
48 qmlRegisterType<UnityMenuModelStack>(uri, 0, 1, "UnityMenuModelStack");
42 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
49 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
43
50
51+ qmlRegisterSingletonType<UnityMenuModelCache>(uri, 0, 1, "UnityMenuModelCache", menuModelCacheSingleton);
52+
44 // external uncreatables
53 // external uncreatables
45 qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
54 qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
46 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
55 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
tests/mocks/Unity/Indicators/qmldir−1
@@ -2,7 +2,6 @@ module Unity.Indicators
2plugin IndicatorsFakeQml
2plugin IndicatorsFakeQml
3typeinfo Indicators.qmltypes
3typeinfo Indicators.qmltypes
4
4
5−CachedUnityMenuModel 0.1 CachedUnityMenuModel.qml
6IndicatorsModel 0.1 IndicatorsModel.qml
5IndicatorsModel 0.1 IndicatorsModel.qml
7FakeMenuPage 0.1 FakeMenuPage.qml
6FakeMenuPage 0.1 FakeMenuPage.qml
8RootActionState 0.1 RootActionState.qml
7RootActionState 0.1 RootActionState.qml
tests/qmltests/CMakeLists.txt+1
@@ -72,6 +72,7 @@ add_qml_test(Panel Panel)
72add_qml_test(Panel SearchIndicator)
72add_qml_test(Panel SearchIndicator)
73add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
73add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
74add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
74add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
75+add_qml_test(Panel/Indicators CachedUnityMenuModel ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
75# These MenuItemFactory tests need the test/mocks/ to come before plugins/
76# These MenuItemFactory tests need the test/mocks/ to come before plugins/
76add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
77add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
77add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
78add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
tests/qmltests/Panel/Indicators/tst_CachedUnityMenuModel.qmlnew file+137
@@ -0,0 +1,137 @@
1+/*
2+ * Copyright 2014 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+import QtQuick 2.0
18+import QtTest 1.0
19+import Unity.Test 0.1 as UT
20+import QMenuModel 0.1
21+import Unity.Indicators 0.1 as Indicators
22+import "../../../../qml/Panel/Indicators"
23+
24+Item {
25+ id: root
26+ width: units.gu(40)
27+ height: units.gu(70)
28+
29+ Component {
30+ id: model
31+ CachedUnityMenuModel {}
32+ }
33+
34+ UT.UnityTestCase {
35+ name: "CachedUnityMenuModel"
36+ when: windowShown
37+
38+ function cleanup() {
39+ doGC();
40+ }
41+
42+ function doGC() {
43+ // need to put some wait cycles here to get gc going properly.
44+ wait(10);
45+ gc();
46+ wait(10);
47+ }
48+
49+ function test_createDifferent() {
50+ var cachedObject = model.createObject(null,
51+ {
52+ "busName": "com.canonical.test1",
53+ "menuObjectPath": "/com/canonical/test1",
54+ "actionsObjectPath": "/com/canonical/test1"
55+ });
56+
57+ var cachedObject2 = model.createObject(null,
58+ {
59+ "busName": "com.canonical.test2",
60+ "menuObjectPath": "/com/canonical/test2",
61+ "actionsObjectPath": "/com/canonical/test2"
62+ });
63+
64+ verify(cachedObject.model !== cachedObject2.model);
65+ }
66+
67+ function test_createSame() {
68+ var cachedObject = model.createObject(null,
69+ {
70+ "busName": "com.canonical.test3",
71+ "menuObjectPath": "/com/canonical/test3",
72+ "actionsObjectPath": "/com/canonical/test3"
73+ });
74+
75+ var cachedObject2 = model.createObject(null,
76+ {
77+ "busName": "com.canonical.test3",
78+ "menuObjectPath": "/com/canonical/test3",
79+ "actionsObjectPath": "/com/canonical/test3"
80+ });
81+
82+ verify(cachedObject.model === cachedObject2.model);
83+ }
84+
85+ // Tests that changing cached model data does not change the model path of others
86+ function test_lp1328646() {
87+ var cachedObject = model.createObject(null,
88+ {
89+ "busName": "com.canonical.test4",
90+ "menuObjectPath": "/com/canonical/test4",
91+ "actionsObjectPath": "/com/canonical/test4"
92+ });
93+
94+ var cachedObject2 = model.createObject(null,
95+ {
96+ "busName": "com.canonical.test4",
97+ "menuObjectPath": "/com/canonical/test4",
98+ "actionsObjectPath": "/com/canonical/test4"
99+ });
100+
101+ cachedObject.menuObjectPath = "/com/canonical/test5";
102+ compare(cachedObject.model.menuObjectPath, "/com/canonical/test5");
103+ compare(cachedObject2.model.menuObjectPath, "/com/canonical/test4");
104+
105+ verify(cachedObject.model !== cachedObject2.model);
106+ }
107+
108+ function createAndDestroy(test) {
109+ var cachedObject = model.createObject(null,
110+ {
111+ "busName": "com.canonical."+test,
112+ "menuObjectPath": "/com/canonical/"+test,
113+ "actionsObjectPath": "/com/canonical/"+test
114+ });
115+ var cachedObject2 = model.createObject(null,
116+ {
117+ "busName": "com.canonical."+test,
118+ "menuObjectPath": "/com/canonical/"+test,
119+ "actionsObjectPath": "/com/canonical/"+test
120+ });
121+ cachedObject.destroy();
122+ return cachedObject2;
123+ }
124+
125+ function test_destroyAllDeletesModel() {
126+ createAndDestroy("test6");
127+ doGC();
128+ compare(Indicators.UnityMenuModelCache.contains("/com/canonical/test6"), false);
129+ }
130+
131+ function test_destroyPartialKeepsModel() {
132+ var model = createAndDestroy("test7");
133+ doGC();
134+ compare(Indicators.UnityMenuModelCache.contains("/com/canonical/test7"), true);
135+ }
136+ }
137+}