on xenial help sign in
merged with trunk
commit582562805f024ed633faa240d0fe0d5b18c7d7b9 authorNick Dedekind <nick.dedekind@canonical.com> dateFri 31 Oct 2014, 17:07 (12 years ago) parentae4b8adf830686
222 files changed+5858−3412
data/unity8-dash.conf+5 debian/changelog+78 debian/control+1−1 libs/UbuntuGestures/DebugHelpers.cpp+27 libs/UbuntuGestures/DebugHelpers.h+2 plugins/AccountsService/AccountsService.qmltypes+2 plugins/Dash/CardAttributes.qml+2 plugins/Dash/CardCreator.js+3−1 plugins/Dash/Dash.qmltypes+3 plugins/LightDM/CMakeLists.txt+2 plugins/LightDM/DBusGreeter.cpp+3−24 plugins/LightDM/DBusGreeter.h+3−6 plugins/LightDM/DBusGreeterList.cpp+3−24 plugins/LightDM/DBusGreeterList.h+3−6 plugins/LightDM/LightDM.qmltypes+1 plugins/LightDM/plugin.cpp+2−9 plugins/Ubuntu/Gestures/CMakeLists.txt+1 plugins/Ubuntu/Gestures/TouchDispatcher.cpp+324 plugins/Ubuntu/Gestures/TouchDispatcher.h+86 plugins/Ubuntu/Gestures/TouchGate.cpp+7−103 plugins/Ubuntu/Gestures/TouchGate.h+5−21 plugins/Ubuntu/Payments/Payments.qmltypes+1 plugins/Unity/DashCommunicator/DashCommunicator.qmltypes+18−5 plugins/Unity/DashCommunicator/dashcommunicator.cpp+2−2 plugins/Unity/DashCommunicator/dashcommunicator.h+1−1 plugins/Unity/DashCommunicator/dashcommunicatorservice.h+1−1 plugins/Unity/DashCommunicator/dashconnection.cpp+2−2 plugins/Unity/DashCommunicator/dashconnection.h+1−1 plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.cpp+3−7 plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.h+4−4 plugins/Unity/Indicators/CMakeLists.txt+1 plugins/Unity/Indicators/Indicators.qmltypes+12−11 plugins/Unity/Indicators/indicators.h−2 plugins/Unity/Indicators/indicatorsmodel.cpp−6 plugins/Unity/Indicators/plugin.cpp+8−2 plugins/Unity/Indicators/plugin.h+1−1 plugins/Unity/Indicators/rootactionstate.cpp+1−1 plugins/Unity/Indicators/rootactionstate.h+4−4 plugins/Unity/Indicators/sharedunitymenumodel.cpp+92 plugins/Unity/Indicators/sharedunitymenumodel.h+66 plugins/Unity/Indicators/unitymenumodelcache.cpp+25−14 plugins/Unity/Indicators/unitymenumodelcache.h+11−7 plugins/Unity/Indicators/visibleindicatorsmodel.cpp−2 plugins/Unity/Launcher/CMakeLists.txt+5−1 plugins/Unity/Launcher/Launcher.qmltypes+17−12 plugins/Unity/Launcher/dbusinterface.cpp+3−36 plugins/Unity/Launcher/dbusinterface.h+2−5 plugins/Unity/Launcher/desktopfilehandler.cpp+1−1 plugins/Unity/Launcher/gsettings.cpp+11−7 plugins/Unity/Launcher/gsettings.h+10−1 plugins/Unity/Launcher/launchermodel.cpp+75−32 plugins/Unity/Launcher/launchermodel.h+5−1 plugins/Unity/Session/CMakeLists.txt+5−1 plugins/Unity/Session/Session.qmltypes+10 plugins/Unity/Session/dbusunitysessionservice.cpp+2−8 plugins/Unity/Session/dbusunitysessionservice.h+2−2 plugins/Utils/Utils.qmltypes+6 po/de.po+18−20 po/hy.po+63−59 qml/Components/Carousel.qml+3−2 qml/Components/Dialogs.qml+21−17 qml/Components/DragHandle.qml+6−1 qml/Components/EdgeDemo.qml+13−10 qml/Components/Flickables/GridView.qml−25 qml/Components/Flickables/ListView.qml−25 qml/Components/Flickables/ListViewWithPageHeader.qml−23 qml/Components/ListItems/VerticalThinDivider.qml−26 qml/Components/ListItems/graphics/ListItemDividerVertical@18.png qml/Components/Lockscreen.qml+4−1 qml/Components/PassphraseLockscreen.qml+2 qml/Components/PinLockscreen.qml+2 qml/Components/ResponsiveGridView.qml+1−2 qml/Components/ScrollCalculator.qml+81 qml/Components/ZoomableImage.qml+19−16 qml/Dash/CardHorizontalList.qml+1−2 qml/Dash/CardVerticalJournal.qml+17 qml/Dash/Dash.qml+1−9 qml/Dash/DashContent.qml+4−3 qml/Dash/DashNavigationButton.qml+1−2 qml/Dash/DashNavigationList.qml+1−2 qml/Dash/PageHeader.qml+1−2 qml/Dash/PreviewListView.qml+3−2 qml/Dash/Previews/Preview.qml+1−2 qml/Dash/Previews/PreviewImageGallery.qml+14−82 qml/Dash/Previews/PreviewOverlay.qml+115 qml/Dash/Previews/PreviewZoomableImage.qml+38−5 qml/Dash/ScopeListView.qml+6−3 qml/Dash/ScopeSettingsPage.qml+1−2 qml/Dash/ScopesOverviewAll.qml+1−2 qml/Dash/ScopesOverviewFavorites.qml+2−4 qml/Greeter/Clock.qml+9−9 qml/Greeter/Greeter.qml+5 qml/Greeter/LoginList.qml+2−2 qml/Hud/Hud.qml+1−2 qml/Hud/HudParametrizedActionsPage.qml+1−2 qml/Launcher/Launcher.qml+1−1 qml/Launcher/LauncherDelegate.qml+2−19 qml/Launcher/LauncherPanel.qml+1−7 qml/Notifications/Notification.qml+73−10 qml/Notifications/Notifications.qml+3−2 qml/Panel/ActiveCallHint.qml+3−1 qml/Components/Flickables/Flickable.qml → qml/Panel/Handle.qml+24−4 qml/Panel/IndicatorItem.qml+232−27 qml/Panel/IndicatorItemRow.qml+297 qml/Panel/Indicators/DefaultIndicatorPage.qml → qml/Panel/IndicatorPage.qml+11−18 qml/Panel/IndicatorRow.qml−163 qml/Panel/Indicators.qml−415 qml/Panel/Indicators/CachedUnityMenuModel.qml−39 qml/Panel/Indicators/DefaultIndicatorWidget.qml−119 qml/Panel/Indicators/IndicatorBase.qml+3−7 qml/Panel/Indicators/IndicatorDelegate.qml+2−2 qml/Panel/Indicators/VisibleIndicators.qml+4−5 qml/Panel/Indicators/client/IndicatorsPage.qml → qml/Panel/Indicators/client/IndicatorRepresentation.qml+21−10 qml/Panel/Indicators/client/IndicatorsList.qml+3−4 qml/Panel/Indicators/client/IndicatorsTree.qml+5−28 qml/Panel/IndicatorsBar.qml+269 qml/Panel/IndicatorsMenu.qml+318 qml/Panel/MenuContent.qml+26−83 qml/Panel/Panel.qml+48−41 qml/Panel/PanelVelocityCalculator.qml+54 qml/Shell.qml+39−12 qml/Stages/PhoneStage.qml+7−3 qml/Stages/TabletStage.qml+6−3 src/libunity8-private/CMakeLists.txt+2 src/libunity8-private/unitydbusobject.cpp+86 src/libunity8-private/unitydbusobject.h+46 src/libunity8-private/unitydbusvirtualobject.cpp+83 src/libunity8-private/unitydbusvirtualobject.h+47 tests/autopilot/unity8/indicators/tests/test_indicators.py+1−1 tests/autopilot/unity8/shell/emulators/dash.py+4 tests/autopilot/unity8/shell/emulators/main_window.py+6−6 tests/autopilot/unity8/shell/tests/__init__.py+1−1 tests/autopilot/unity8/shell/tests/test_notifications.py+6−5 tests/mocks/AccountsService/AccountsService.qmltypes+2 tests/mocks/LightDM/CMakeLists.txt+2 tests/mocks/LightDM/LightDM.qmltypes+1 tests/mocks/QMenuModel/QMenuModel.qmltypes+1−1 tests/mocks/QMenuModel/unitymenumodel.cpp+1−2 tests/mocks/QMenuModel/unitymenumodel.h+6−2 tests/mocks/Ubuntu/Payments/Payments.qmltypes+2 tests/mocks/Ubuntu/Telephony/Telephony.qmltypes+2−1 tests/mocks/Unity/Application/Application.qmltypes+13 tests/mocks/Unity/DashCommunicator/DashCommunicator.qmltypes+6−6 tests/mocks/Unity/DashCommunicator/dashcommunicator.cpp+2−2 tests/mocks/Unity/DashCommunicator/dashcommunicator.h+2−2 tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.cpp+2−2 tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.h+2−2 tests/mocks/Unity/Indicators/CMakeLists.txt+3−10 tests/mocks/Unity/Indicators/FakeMenuPage.qml−27 tests/mocks/Unity/Indicators/Indicators.qmltypes+43−15 tests/mocks/Unity/Indicators/IndicatorsModel.qml+152−34 tests/mocks/Unity/Indicators/RootActionState.qml+18−10 tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp+50−21 tests/mocks/Unity/Indicators/fakeindicatorsmodel.h+8−2 tests/mocks/Unity/Indicators/fakeplugin.cpp+8−4 tests/mocks/Unity/Indicators/fakeunitymenumodelcache.cpp+59 tests/mocks/Unity/Indicators/fakeunitymenumodelcache.h+45 tests/mocks/Unity/Indicators/indicators_fake.qrc−14 tests/mocks/Unity/Indicators/qml/fake_menu_page1.qml−35 tests/mocks/Unity/Indicators/qml/fake_menu_page2.qml−35 tests/mocks/Unity/Indicators/qml/fake_menu_page3.qml−35 tests/mocks/Unity/Indicators/qml/fake_menu_page4.qml−35 tests/mocks/Unity/Indicators/qml/fake_menu_page5.qml−35 tests/mocks/Unity/Indicators/qml/fake_menu_widget1.qml−23 tests/mocks/Unity/Indicators/qml/fake_menu_widget2.qml−23 tests/mocks/Unity/Indicators/qml/fake_menu_widget4.qml−23 tests/mocks/Unity/Indicators/qml/fake_menu_widget5.qml−23 tests/mocks/Unity/Indicators/qmldir−1 tests/mocks/Unity/Launcher/Launcher.qmltypes+13 tests/mocks/Unity/Unity.qmltypes+32 tests/plugins/Dash/cardcreator/1.res+2 tests/plugins/Dash/cardcreator/2.res+2 tests/plugins/Dash/cardcreator/3.res+2 tests/plugins/Dash/cardcreator/4.res+2 tests/plugins/Dash/cardcreator/5.res+3−1 tests/plugins/Dash/cardcreator/6.res+2 tests/plugins/Dash/cardcreator/7.res+2 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+1 tests/plugins/Ubuntu/Gestures/GestureTest.cpp+70 tests/plugins/Ubuntu/Gestures/GestureTest.h+40−1 tests/mocks/Unity/Indicators/qml/fake_menu_widget3.qml → tests/plugins/Ubuntu/Gestures/empty.qml+4−5 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+4−73 tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+90 tests/plugins/Unity/Indicators/CMakeLists.txt+1 tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp+102 tests/plugins/Unity/Launcher/CMakeLists.txt+5−1 tests/plugins/Unity/Launcher/gsettings.cpp+8−4 tests/plugins/Unity/Launcher/gsettings.h+8 tests/plugins/Unity/Launcher/launchermodeltest.cpp+88−11 tests/plugins/Unity/Session/CMakeLists.txt+4 tests/plugins/Unity/Session/sessionbackendtest.cpp+1 tests/qmltests/CMakeLists.txt+10−11 tests/qmltests/Components/tst_ZoomableImage.qml+21−21 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+25−24 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+30−8 tests/qmltests/Dash/tst_Card.qml+13 tests/qmltests/Dash/tst_Dash.qml+6−26 tests/qmltests/Dash/tst_DashContent.qml+8−8 tests/qmltests/Dash/tst_GenericScopeView.qml+33 tests/qmltests/Greeter/tst_Clock.qml+49−34 tests/qmltests/Notifications/tst_Notifications.qml+172−41 tests/qmltests/Panel/IndicatorTest.qml+80 tests/qmltests/Panel/Indicators/tst_CachedUnityMenuModel.qml−137 tests/qmltests/Panel/Indicators/tst_DefaultIndicatorWidget.qml−60 tests/qmltests/Panel/tst_ActiveCallHint.qml−1 tests/qmltests/Panel/tst_IndicatorItem.qml+173−18 tests/qmltests/Panel/tst_IndicatorItemRow.qml+290 tests/qmltests/Panel/Indicators/tst_DefaultIndicatorPage.qml → tests/qmltests/Panel/tst_IndicatorPage.qml+18−25 tests/qmltests/Panel/tst_IndicatorRow.qml−160 tests/qmltests/Panel/tst_Indicators.qml−230 tests/qmltests/Panel/tst_IndicatorsBar.qml+224 tests/qmltests/Panel/tst_IndicatorsMenu.qml+258 tests/qmltests/Panel/tst_MenuContent.qml+9−25 tests/qmltests/Panel/tst_Panel.qml+185−214 tests/qmltests/Panel/tst_SearchIndicator.qml−1 tests/qmltests/Stages/tst_PhoneStage.qml+17 tests/qmltests/tst_Shell.qml+133−44 tests/qmltests/tst_ShellWithPin.qml+5 tests/qmltests/tst_TabletShell.qml+5 tests/uqmlscene/main.cpp+5−5 tests/utils/modules/Unity/Test/testutil.cpp+10−22 tests/utils/modules/Unity/Test/testutil.h−1
data/unity8-dash.conf+5
@@ -8,6 +8,11 @@ stop on stopping unity8
88
99kill timeout 120
1010
11+# Focused app is 100 so we want to be less likely to get
12+# killed than the focused app but more than Unity8
13+
14+oom score 50
15+
1116respawn
1217
1318env APP_ID=unity8-dash
debian/changelog+78
@@ -1,3 +1,81 @@
1+unity8 (8.01+15.04.20141030-0ubuntu1) vivid; urgency=medium
2+
3+ [ Michael Terry ]
4+ * Provide 'passphrase' as a field of Components.Lockscreen
5+ * Fix a race between Qml loading and DBus registration that caused
6+ problems when jenkins tried to unlock the phone.
7+ * Set domain explicitly for the Dialogs component because the welcome
8+ wizard wants to import it. (LP: #1381731)
9+ * When greeter or lockscreen has focus, show active call panel. (LP:
10+ #1378872)
11+
12+ [ Ted Gould ]
13+ * Set the default OOM score for the dash (LP: #1379786)
14+
15+ [ Michał Sawicz ]
16+ * Revert lp:~unity-team/unity8/flickables-speed-workaround to avoid
17+ risk in RTM.
18+ * Updated behaviour for zoomable image, workaround for sourcesize (LP:
19+ #1333187)
20+
21+ [ Michael Frey ]
22+ * Added a check for Proximity to determine if we show the lock screen.
23+ (LP: #1378012)
24+
25+ [ Ying-Chun Liu ]
26+ * Add non-interactive code into GenericScopeView. (LP: #1384441)
27+
28+ [ Mirco Müller ]
29+ * Also use modal nature of snap-decision notifications when
30+ greeter/lockscreen is shown. This fixes LP: #1378827. (LP: #1378827)
31+
32+ [ Michael Zanetti ]
33+ * Make the launcher update on dconf changes (LP: #1376707)
34+ * exit spread on background tap (LP: #1368261)
35+ * Use an index instead of a scope id in DashCommunicator (LP:
36+ #1376044)
37+
38+ [ Andrea Cimitan ]
39+ * Updated behaviour for zoomable image, workaround for sourcesize (LP:
40+ #1333187)
41+
42+ [ Daniel d'Andrada ]
43+ * Make TouchGate synthesize QMouseEvents for mouse-based target items
44+ * Don't specify a distanceThreshold as it conflicts with
45+ hintDisplacment
46+
47+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 30 Oct 2014 21:43:42 +0000
48+
49+unity8 (8.00+15.04.20141030-0ubuntu1) vivid; urgency=low
50+
51+ [ josharenson ]
52+ * Fix lp:1370240 by making stages interactive when a snap decision is
53+ dismissed. (LP: #1370240)
54+
55+ [ Nick Dedekind ]
56+ * remove qml ownership confusion for caching unitymenumodels (LP:
57+ #1368856)
58+ * Implementation of expandable panel design (LP: #1368856)
59+
60+ [ Albert Astals ]
61+ * CardAttributes: Specify column and row since the gridlayout gets
62+ confused sometimes (LP: #1381092)
63+ * Reset VerticalJournal until the cardTool settles (LP: #1381255)
64+
65+ [ Mirco Müller ]
66+ * Added synchronous/confirmation notification support to unity8. (LP:
67+ #1232633)
68+
69+ [ Michael Zanetti ]
70+ * Fix lp:1370240 by making stages interactive when a snap decision is
71+ dismissed. (LP: #1370240)
72+ * Drop all visual indication of "pinning" (LP: #1381054)
73+
74+ [ Antti Kaijanmäki ]
75+ * Unlock all modems on boot. (LP: #1333121)
76+
77+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 30 Oct 2014 10:51:17 +0000
78+
179unity8 (8.00+14.10.20141013.2-0ubuntu1) utopic; urgency=low
280
381 [ Michael Terry ]
debian/control+1−1
@@ -97,7 +97,7 @@ Depends: gsettings-desktop-schemas,
9797Recommends: unity-scope-click,
9898 unity-scope-mediascanner2,
9999 unity-scope-scopes,
100−Breaks: indicator-network (<< 0.5.1),
100+Breaks: indicator-network (<< 0.5.1+14.10.20141014),
101101 ubuntu-touch-session (<< 0.107),
102102 unity8-greeter (<< 7.89),
103103Replaces: ubuntu-touch-session (<< 0.82~),
libs/UbuntuGestures/DebugHelpers.cpp+27
@@ -16,6 +16,7 @@
1616
1717#include "DebugHelpers.h"
1818#include <QTouchEvent>
19+#include <QMouseEvent>
1920
2021QString touchPointStateToString(Qt::TouchPointState state)
2122{
@@ -66,3 +67,29 @@ QString touchEventToString(const QTouchEvent *ev)
6667
6768 return message;
6869}
70+
71+QString mouseEventToString(const QMouseEvent *ev)
72+{
73+ QString message;
74+
75+ switch (ev->type()) {
76+ case QEvent::MouseButtonPress:
77+ message.append("MouseButtonPress ");
78+ break;
79+ case QEvent::MouseButtonRelease:
80+ message.append("MouseButtonRelease ");
81+ break;
82+ case QEvent::MouseButtonDblClick:
83+ message.append("MouseButtonDblClick ");
84+ break;
85+ case QEvent::MouseMove:
86+ message.append("MouseButtonMove ");
87+ break;
88+ default:
89+ message.append("INVALID_MOUSE_EVENT_TYPE ");
90+ }
91+
92+ message.append(QString("pos(%1, %2)").arg(ev->x()).arg(ev->y()));
93+
94+ return message;
95+}
libs/UbuntuGestures/DebugHelpers.h+2
@@ -21,9 +21,11 @@
2121
2222#include "UbuntuGesturesGlobal.h"
2323
24+class QMouseEvent;
2425class QTouchEvent;
2526
2627UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state);
2728UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev);
29+UBUNTUGESTURES_EXPORT QString mouseEventToString(const QMouseEvent *ev);
2830
2931#endif // UBUNTUGESTURES_DEBUG_HELPER_H
plugins/AccountsService/AccountsService.qmltypes+2
@@ -23,6 +23,8 @@ Module {
2323 }
2424 Property { name: "user"; type: "string" }
2525 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "enableLauncherWhileLocked"; type: "bool"; isReadonly: true }
27+ Property { name: "enableIndicatorsWhileLocked"; type: "bool"; isReadonly: true }
2628 Property { name: "backgroundFile"; type: "string"; isReadonly: true }
2729 Property { name: "statsWelcomeScreen"; type: "bool"; isReadonly: true }
2830 Property { name: "passwordDisplayHint"; type: "PasswordDisplayHint"; isReadonly: true }
plugins/Dash/CardAttributes.qml+2
@@ -41,6 +41,8 @@ GridLayout {
4141 if (column == grid.columns - 1 || index == repeater.count - 1) return Qt.AlignRight;
4242 if (column == 1) return Qt.AlignHCenter;
4343 }
44+ Layout.column: index % grid.columns
45+ Layout.row: index / grid.columns
4446 Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
4547 Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
4648 Layout.fillWidth: true
plugins/Dash/CardCreator.js+3−1
@@ -313,7 +313,9 @@ function cardString(template, components) {
313313 readonly property string title: cardData && cardData["title"] || ""; \n\
314314 property bool asynchronous: true; \n\
315315 property bool showHeader: true; \n\
316− implicitWidth: childrenRect.width; \n';
316+ implicitWidth: childrenRect.width; \n\
317+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true); \n\
318+ \n';
317319
318320 var hasArt = components["art"] && components["art"]["field"] || false;
319321 var hasSummary = components["summary"] || false;
plugins/Dash/Dash.qmltypes+3
@@ -40,9 +40,12 @@ Module {
4040 Property { name: "forceNoClip"; type: "bool" }
4141 Property { name: "stickyHeaderHeight"; type: "int"; isReadonly: true }
4242 Property { name: "headerItemShownHeight"; type: "double"; isReadonly: true }
43+ Property { name: "cacheBuffer"; type: "double" }
4344 Signal { name: "headerChanged" }
4445 Method { name: "positionAtBeginning" }
4546 Method { name: "showHeader" }
47+ Method { name: "firstCreatedIndex"; type: "int" }
48+ Method { name: "createdItemCount"; type: "int" }
4649 Method {
4750 name: "item"
4851 type: "QQuickItem*"
plugins/LightDM/CMakeLists.txt+2
@@ -10,6 +10,7 @@ include_directories(
1010 ${CMAKE_SOURCE_DIR}/plugins/Utils
1111 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
1212 #${LIBLIGHTDM_INCLUDE_DIRS}
13+ ${libunity8-private_SOURCE_DIR}
1314 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
1415)
1516
@@ -28,6 +29,7 @@ add_library(LightDM-qml MODULE
2829
2930target_link_libraries(LightDM-qml
3031 MockLightDM-demo
32+ unity8-private
3133# TODO: Once we split out a separate greeter process, uncomment these lines
3234# ${LIBLIGHTDM_LDFLAGS}
3335 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
plugins/LightDM/DBusGreeter.cpp+3−24
@@ -20,11 +20,9 @@
2020#include <QDBusMessage>
2121#include <QStringList>
2222
23−DBusGreeter::DBusGreeter(Greeter *greeter, const QDBusConnection &connection, const QString &path)
24− : QObject(greeter),
25− m_greeter(greeter),
26− m_connection(connection),
27− m_path(path)
23+DBusGreeter::DBusGreeter(Greeter *greeter, const QString &path)
24+ : UnityDBusObject(path, "com.canonical.UnityGreeter", true, greeter),
25+ m_greeter(greeter)
2826{
2927 connect(m_greeter, SIGNAL(isActiveChanged()), this, SLOT(isActiveChangedHandler()));
3028}
@@ -49,22 +47,3 @@ void DBusGreeter::isActiveChangedHandler()
4947 notifyPropertyChanged("IsActive", isActive());
5048 Q_EMIT isActiveChanged();
5149}
52−
53−// Manually emit a PropertiesChanged signal over DBus, because QtDBus
54−// doesn't do it for us on Q_PROPERTIES, oddly enough.
55−void DBusGreeter::notifyPropertyChanged(const QString& propertyName, const QVariant &value)
56−{
57− QDBusMessage message;
58− QVariantMap changedProps;
59−
60− changedProps.insert(propertyName, value);
61−
62− message = QDBusMessage::createSignal(m_path,
63− "org.freedesktop.DBus.Properties",
64− "PropertiesChanged");
65− message << "com.canonical.UnityGreeter";
66− message << changedProps;
67− message << QStringList();
68−
69− m_connection.send(message);
70−}
plugins/LightDM/DBusGreeter.h+3−6
@@ -17,15 +17,15 @@
1717#ifndef UNITY_DBUSGREETER_H
1818#define UNITY_DBUSGREETER_H
1919
20+#include "unitydbusobject.h"
2021#include <QDBusConnection>
21−#include <QObject>
2222
2323class Greeter;
2424
2525/** This is an internal class used to talk with the indicators.
2626 */
2727
28−class DBusGreeter : public QObject
28+class DBusGreeter : public UnityDBusObject
2929{
3030 Q_OBJECT
3131 Q_CLASSINFO("D-Bus Interface", "com.canonical.UnityGreeter")
@@ -33,7 +33,7 @@ class DBusGreeter : public QObject
3333 Q_PROPERTY(bool IsActive READ isActive NOTIFY isActiveChanged) // since 14.10
3434
3535public:
36− explicit DBusGreeter(Greeter *greeter, const QDBusConnection &connection, const QString &path);
36+ explicit DBusGreeter(Greeter *greeter, const QString &path);
3737
3838 bool isActive() const;
3939 Q_SCRIPTABLE void ShowGreeter(); // temporary, until we split the greeter again
@@ -44,12 +44,9 @@ Q_SIGNALS:
4444
4545private Q_SLOTS:
4646 void isActiveChangedHandler();
47− void notifyPropertyChanged(const QString &propertyName, const QVariant &value);
4847
4948private:
5049 Greeter *m_greeter;
51− QDBusConnection m_connection;
52− QString m_path;
5350};
5451
5552#endif
plugins/LightDM/DBusGreeterList.cpp+3−24
@@ -20,11 +20,9 @@
2020#include <QDBusMessage>
2121#include <QStringList>
2222
23−DBusGreeterList::DBusGreeterList(Greeter *greeter, const QDBusConnection &connection, const QString &path)
24− : QObject(greeter),
25− m_greeter(greeter),
26− m_connection(connection),
27− m_path(path)
23+DBusGreeterList::DBusGreeterList(Greeter *greeter, const QString &path)
24+ : UnityDBusObject(path, "com.canonical.UnityGreeter", true, greeter),
25+ m_greeter(greeter)
2826{
2927 connect(m_greeter, SIGNAL(authenticationUserChanged(const QString &)), this, SLOT(authenticationUserChangedHandler(const QString &)));
3028 connect(m_greeter, SIGNAL(promptlessChanged()), this, SLOT(promptlessChangedHandler()));
@@ -56,22 +54,3 @@ void DBusGreeterList::promptlessChangedHandler()
5654 notifyPropertyChanged("EntryIsLocked", entryIsLocked());
5755 Q_EMIT entryIsLockedChanged();
5856}
59−
60−// Manually emit a PropertiesChanged signal over DBus, because QtDBus
61−// doesn't do it for us on Q_PROPERTIES, oddly enough.
62−void DBusGreeterList::notifyPropertyChanged(const QString& propertyName, const QVariant &value)
63−{
64− QDBusMessage message;
65− QVariantMap changedProps;
66−
67− changedProps.insert(propertyName, value);
68−
69− message = QDBusMessage::createSignal(m_path,
70− "org.freedesktop.DBus.Properties",
71− "PropertiesChanged");
72− message << "com.canonical.UnityGreeter.List";
73− message << changedProps;
74− message << QStringList();
75−
76− m_connection.send(message);
77−}
plugins/LightDM/DBusGreeterList.h+3−6
@@ -17,15 +17,15 @@
1717#ifndef UNITY_DBUSGREETERLIST_H
1818#define UNITY_DBUSGREETERLIST_H
1919
20+#include "unitydbusobject.h"
2021#include <QDBusConnection>
21−#include <QObject>
2222
2323class Greeter;
2424
2525/** This is an internal class used to talk with the indicators.
2626 */
2727
28−class DBusGreeterList : public QObject
28+class DBusGreeterList : public UnityDBusObject
2929{
3030 Q_OBJECT
3131 Q_CLASSINFO("D-Bus Interface", "com.canonical.UnityGreeter.List")
@@ -34,7 +34,7 @@ class DBusGreeterList : public QObject
3434 Q_PROPERTY(bool EntryIsLocked READ entryIsLocked NOTIFY entryIsLockedChanged) // since 14.04
3535
3636public:
37− explicit DBusGreeterList(Greeter *greeter, const QDBusConnection &connection, const QString &path);
37+ explicit DBusGreeterList(Greeter *greeter, const QString &path);
3838
3939 Q_SCRIPTABLE void SetActiveEntry(const QString &entry); // since 13.04
4040 Q_SCRIPTABLE QString GetActiveEntry() const; // since 13.10
@@ -49,12 +49,9 @@ Q_SIGNALS:
4949private Q_SLOTS:
5050 void authenticationUserChangedHandler(const QString &user);
5151 void promptlessChangedHandler();
52− void notifyPropertyChanged(const QString &propertyName, const QVariant &value);
5352
5453private:
5554 Greeter *m_greeter;
56− QDBusConnection m_connection;
57− QString m_path;
5855};
5956
6057#endif
plugins/LightDM/LightDM.qmltypes+1
@@ -37,6 +37,7 @@ Module {
3737 Signal { name: "isActiveChanged" }
3838 Signal { name: "isAuthenticatedChanged" }
3939 Signal { name: "showGreeter" }
40+ Signal { name: "hideGreeter" }
4041 Signal {
4142 name: "requestAuthenticationUser"
4243 Parameter { name: "user"; type: "string" }
plugins/LightDM/plugin.cpp+2−9
@@ -30,21 +30,14 @@
3030#include <QDBusConnection>
3131#include <QtQml/qqml.h>
3232
33−static const char* GREETER_LIST_DBUS_PATH = "/list";
34−static const char* GREETER_DBUS_SERVICE = "com.canonical.UnityGreeter";
35−
3633static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
3734{
3835 Q_UNUSED(engine)
3936 Q_UNUSED(scriptEngine)
4037
4138 Greeter *greeter = new Greeter();
42− QDBusConnection connection = QDBusConnection::sessionBus();
43− DBusGreeter *root = new DBusGreeter(greeter, connection, "/");
44− connection.registerObject("/", root, QDBusConnection::ExportScriptableContents);
45− DBusGreeterList *list = new DBusGreeterList(greeter, connection, GREETER_LIST_DBUS_PATH);
46− connection.registerObject(GREETER_LIST_DBUS_PATH, list, QDBusConnection::ExportScriptableContents);
47− connection.registerService(GREETER_DBUS_SERVICE);
39+ new DBusGreeter(greeter, "/");
40+ new DBusGreeterList(greeter, "/list");
4841
4942 return greeter;
5043}
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -8,6 +8,7 @@ set(UbuntuGesturesQml_SOURCES
88 DirectionalDragArea.cpp
99 PressedOutsideNotifier.cpp
1010 TimeSource.cpp
11+ TouchDispatcher.cpp
1112 TouchGate.cpp
1213)
1314
plugins/Ubuntu/Gestures/TouchDispatcher.cppnew file+324
@@ -0,0 +1,324 @@
1+/*
2+ * Copyright (C) 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+#include "TouchDispatcher.h"
18+
19+#include <QScopedPointer>
20+
21+#pragma GCC diagnostic push
22+#pragma GCC diagnostic ignored "-pedantic"
23+#include <private/qquickitem_p.h>
24+#pragma GCC diagnostic pop
25+
26+#define TOUCHDISPATCHER_DEBUG 0
27+
28+#if TOUCHDISPATCHER_DEBUG
29+#include <DebugHelpers.h>
30+#endif
31+
32+TouchDispatcher::TouchDispatcher()
33+ : m_status(NoActiveTouch)
34+ , m_touchMouseId(-1)
35+{
36+}
37+
38+void TouchDispatcher::setTargetItem(QQuickItem *target)
39+{
40+ if (target != m_targetItem) {
41+ m_targetItem = target;
42+ if (m_status != NoActiveTouch) {
43+ qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
44+ m_status = TargetRejectedTouches;
45+ }
46+ }
47+}
48+
49+void TouchDispatcher::dispatch(QEvent::Type eventType,
50+ QTouchDevice *device,
51+ Qt::KeyboardModifiers modifiers,
52+ const QList<QTouchEvent::TouchPoint> &touchPoints,
53+ QWindow *window,
54+ ulong timestamp)
55+{
56+ if (m_targetItem.isNull()) {
57+ qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
58+ return;
59+ }
60+
61+ if (eventType == QEvent::TouchBegin) {
62+ dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
63+
64+ } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
65+
66+ if (m_status == DeliveringTouchEvents) {
67+ dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
68+ } else if (m_status == DeliveringMouseEvents) {
69+ dispatchAsMouse(device, modifiers, touchPoints, timestamp);
70+ } else {
71+ Q_ASSERT(m_status == TargetRejectedTouches);
72+ #if TOUCHDISPATCHER_DEBUG
73+ qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
74+ << "because it already rejected the touch stream.";
75+ #endif
76+ // Do nothing
77+ }
78+
79+ if (eventType == QEvent::TouchEnd) {
80+ m_status = NoActiveTouch;
81+ m_touchMouseId = -1;
82+ }
83+
84+ } else {
85+ // Should never happen
86+ qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
87+ Q_ASSERT(false);
88+ return;
89+ }
90+}
91+
92+void TouchDispatcher::dispatchTouchBegin(
93+ QTouchDevice *device,
94+ Qt::KeyboardModifiers modifiers,
95+ const QList<QTouchEvent::TouchPoint> &touchPoints,
96+ QWindow *window,
97+ ulong timestamp)
98+{
99+ Q_ASSERT(m_status == NoActiveTouch);
100+ QQuickItem *targetItem = m_targetItem.data();
101+
102+ if (!targetItem->isEnabled() || !targetItem->isVisible()) {
103+ #if TOUCHDISPATCHER_DEBUG
104+ qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
105+ << "because it's disabled or invisible.";
106+ #endif
107+ return;
108+ }
109+
110+ // Map touch points to targetItem coordinates
111+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
112+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
113+
114+ QScopedPointer<QTouchEvent> touchEvent(
115+ createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
116+
117+
118+ #if TOUCHDISPATCHER_DEBUG
119+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
120+ << "to" << targetItem;
121+ #endif
122+ QCoreApplication::sendEvent(targetItem, touchEvent.data());
123+
124+
125+ if (touchEvent->isAccepted()) {
126+ #if TOUCHDISPATCHER_DEBUG
127+ qDebug() << "[TouchDispatcher] Item accepted the touch event.";
128+ #endif
129+ m_status = DeliveringTouchEvents;
130+ } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
131+ #if TOUCHDISPATCHER_DEBUG
132+ qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
133+ #endif
134+ // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
135+ QScopedPointer<QMouseEvent> mouseEvent(
136+ touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
137+ modifiers, false /* transformNeeded */));
138+ Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
139+
140+ #if TOUCHDISPATCHER_DEBUG
141+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
142+ << "to" << m_targetItem.data();
143+ #endif
144+ QCoreApplication::sendEvent(targetItem, mouseEvent.data());
145+ if (mouseEvent->isAccepted()) {
146+ #if TOUCHDISPATCHER_DEBUG
147+ qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
148+ #endif
149+ m_status = DeliveringMouseEvents;
150+ m_touchMouseId = targetTouchPoints.at(0).id();
151+ } else {
152+ #if TOUCHDISPATCHER_DEBUG
153+ qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
154+ #endif
155+ m_status = TargetRejectedTouches;
156+ }
157+ } else {
158+ #if TOUCHDISPATCHER_DEBUG
159+ qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
160+ #endif
161+ m_status = TargetRejectedTouches;
162+ }
163+}
164+
165+void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
166+ QTouchDevice *device,
167+ Qt::KeyboardModifiers modifiers,
168+ const QList<QTouchEvent::TouchPoint> &touchPoints,
169+ QWindow *window,
170+ ulong timestamp)
171+{
172+ QQuickItem *targetItem = m_targetItem.data();
173+
174+ // Map touch points to targetItem coordinates
175+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
176+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
177+
178+ QScopedPointer<QTouchEvent> eventForTargetItem(
179+ createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
180+
181+
182+ #if TOUCHDISPATCHER_DEBUG
183+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
184+ << "to" << targetItem;
185+ #endif
186+ QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
187+}
188+
189+void TouchDispatcher::dispatchAsMouse(
190+ QTouchDevice * /*device*/,
191+ Qt::KeyboardModifiers modifiers,
192+ const QList<QTouchEvent::TouchPoint> &touchPoints,
193+ ulong timestamp)
194+{
195+ // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
196+
197+ Q_ASSERT(!touchPoints.isEmpty());
198+
199+ const QTouchEvent::TouchPoint *touchMouse = nullptr;
200+
201+ if (m_touchMouseId != -1) {
202+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
203+ const auto &touchPoint = touchPoints.at(i);
204+ if (touchPoint.id() == m_touchMouseId) {
205+ touchMouse = &touchPoint;
206+ }
207+ }
208+
209+ Q_ASSERT(touchMouse);
210+ if (!touchMouse) {
211+ // should not happen, but deal with it just in case.
212+ qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
213+ m_touchMouseId);
214+ m_touchMouseId = touchPoints.at(0).id();
215+ touchMouse = &touchPoints.at(0);
216+ }
217+ } else {
218+ // Try to find a new touch for mouse emulation
219+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
220+ const auto &touchPoint = touchPoints.at(i);
221+ if (touchPoint.state() == Qt::TouchPointPressed) {
222+ touchMouse = &touchPoint;
223+ m_touchMouseId = touchMouse->id();
224+ }
225+ }
226+ }
227+
228+ if (touchMouse) {
229+ QEvent::Type eventType;
230+ if (touchMouse->state() == Qt::TouchPointPressed) {
231+ eventType = QEvent::MouseButtonPress;
232+ } if (touchMouse->state() == Qt::TouchPointReleased) {
233+ eventType = QEvent::MouseButtonRelease;
234+ m_touchMouseId = -1;
235+ } else {
236+ eventType = QEvent::MouseMove;
237+ }
238+
239+ QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
240+ true /* transformNeeded */));
241+
242+ #if TOUCHDISPATCHER_DEBUG
243+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
244+ << "to" << m_targetItem.data();
245+ #endif
246+ QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
247+ }
248+}
249+
250+QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
251+ QTouchDevice *device,
252+ Qt::KeyboardModifiers modifiers,
253+ const QList<QTouchEvent::TouchPoint> &touchPoints,
254+ QWindow *window,
255+ ulong timestamp)
256+{
257+ Qt::TouchPointStates eventStates = 0;
258+ for (int i = 0; i < touchPoints.count(); i++)
259+ eventStates |= touchPoints[i].state();
260+ // if all points have the same state, set the event type accordingly
261+ switch (eventStates) {
262+ case Qt::TouchPointPressed:
263+ eventType = QEvent::TouchBegin;
264+ break;
265+ case Qt::TouchPointReleased:
266+ eventType = QEvent::TouchEnd;
267+ break;
268+ default:
269+ eventType = QEvent::TouchUpdate;
270+ break;
271+ }
272+
273+ QTouchEvent *touchEvent = new QTouchEvent(eventType);
274+ touchEvent->setWindow(window);
275+ touchEvent->setTarget(m_targetItem.data());
276+ touchEvent->setDevice(device);
277+ touchEvent->setModifiers(modifiers);
278+ touchEvent->setTouchPoints(touchPoints);
279+ touchEvent->setTouchPointStates(eventStates);
280+ touchEvent->setTimestamp(timestamp);
281+ touchEvent->accept();
282+ return touchEvent;
283+}
284+
285+// NB: From QQuickWindow
286+void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
287+{
288+ QMatrix4x4 transformMatrix(transform);
289+ for (int i=0; i<touchPoints.count(); i++) {
290+ QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
291+ touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
292+ touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
293+ touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
294+ touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
295+ }
296+}
297+
298+// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
299+QMouseEvent *TouchDispatcher::touchToMouseEvent(
300+ QEvent::Type type, const QTouchEvent::TouchPoint &p,
301+ ulong timestamp, Qt::KeyboardModifiers modifiers,
302+ bool transformNeeded)
303+{
304+ QQuickItem *item = m_targetItem.data();
305+
306+ // The touch point local position and velocity are not yet transformed.
307+ QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
308+ p.scenePos(), p.screenPos(), Qt::LeftButton,
309+ (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
310+ modifiers);
311+ me->setAccepted(true);
312+ me->setTimestamp(timestamp);
313+ QVector2D transformedVelocity = p.velocity();
314+ if (transformNeeded) {
315+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
316+ QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
317+ transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
318+ }
319+
320+ // Add these later if needed:
321+ //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
322+ //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
323+ return me;
324+}
plugins/Ubuntu/Gestures/TouchDispatcher.hnew file+86
@@ -0,0 +1,86 @@
1+/*
2+ * Copyright (C) 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+#ifndef UBUNTU_TOUCH_DISPATCHER_H
18+#define UBUNTU_TOUCH_DISPATCHER_H
19+
20+#include "UbuntuGesturesQmlGlobal.h"
21+
22+#include <QPointer>
23+#include <QQuickItem>
24+
25+/*
26+ Dispatches touches to the given target, converting the touch point
27+ coordinates accordingly.
28+
29+ Also takes care of synthesizing mouse events in case the target
30+ doesn't work with touch events.
31+ */
32+class UBUNTUGESTURESQML_EXPORT TouchDispatcher {
33+public:
34+ TouchDispatcher();
35+
36+ void setTargetItem(QQuickItem *target);
37+ QQuickItem *targetItem() { return m_targetItem; }
38+
39+ void dispatch(QEvent::Type eventType,
40+ QTouchDevice *device,
41+ Qt::KeyboardModifiers modifiers,
42+ const QList<QTouchEvent::TouchPoint> &touchPoints,
43+ QWindow *window,
44+ ulong timestamp);
45+private:
46+ void dispatchTouchBegin(
47+ QTouchDevice *device,
48+ Qt::KeyboardModifiers modifiers,
49+ const QList<QTouchEvent::TouchPoint> &touchPoints,
50+ QWindow *window,
51+ ulong timestamp);
52+ void dispatchAsTouch(QEvent::Type eventType,
53+ QTouchDevice *device,
54+ Qt::KeyboardModifiers modifiers,
55+ const QList<QTouchEvent::TouchPoint> &touchPoints,
56+ QWindow *window,
57+ ulong timestamp);
58+ void dispatchAsMouse(
59+ QTouchDevice *device,
60+ Qt::KeyboardModifiers modifiers,
61+ const QList<QTouchEvent::TouchPoint> &touchPoints,
62+ ulong timestamp);
63+
64+ static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
65+ QTouchEvent *createQTouchEvent(QEvent::Type eventType,
66+ QTouchDevice *device,
67+ Qt::KeyboardModifiers modifiers,
68+ const QList<QTouchEvent::TouchPoint> &touchPoints,
69+ QWindow *window,
70+ ulong timestamp);
71+ QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
72+ ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
73+
74+ QPointer<QQuickItem> m_targetItem;
75+
76+ enum {
77+ NoActiveTouch,
78+ DeliveringTouchEvents,
79+ DeliveringMouseEvents,
80+ TargetRejectedTouches
81+ } m_status;
82+
83+ int m_touchMouseId;
84+};
85+
86+#endif // UBUNTU_TOUCH_DISPATCHER_H
plugins/Ubuntu/Gestures/TouchGate.cpp+7−103
@@ -22,16 +22,10 @@
2222#include <TouchOwnershipEvent.h>
2323#include <TouchRegistry.h>
2424
25−#pragma GCC diagnostic push
26−#pragma GCC diagnostic ignored "-pedantic"
27−#include <private/qquickitem_p.h>
28−#pragma GCC diagnostic pop
29−
3025#if TOUCHGATE_DEBUG
3126#include <DebugHelpers.h>
3227#endif
3328
34−
3529bool TouchGate::event(QEvent *e)
3630{
3731 if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
@@ -189,125 +183,35 @@ void TouchGate::setTargetItem(QQuickItem *item)
189183 // TODO: changing the target item while dispatch of touch events is taking place will
190184 // create a mess
191185
192− if (item == m_targetItem.data())
186+ if (item == m_dispatcher.targetItem())
193187 return;
194188
195− m_targetItem = item;
189+ m_dispatcher.setTargetItem(item);
196190 Q_EMIT targetItemChanged(item);
197191}
198192
199193void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
200194{
201− dispatchTouchEventToTarget(event.eventType,
195+ removeTouchInfoForEndedTouches(event.touchPoints);
196+ m_dispatcher.dispatch(event.eventType,
202197 event.device,
203198 event.modifiers,
204199 event.touchPoints,
205− event.target,
206200 event.window,
207201 event.timestamp);
208202}
209203
210204void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
211205{
212− dispatchTouchEventToTarget(event->type(),
206+ removeTouchInfoForEndedTouches(event->touchPoints());
207+ m_dispatcher.dispatch(event->type(),
213208 event->device(),
214209 event->modifiers(),
215210 event->touchPoints(),
216− event->target(),
217211 event->window(),
218212 event->timestamp());
219213}
220214
221−void TouchGate::dispatchTouchEventToTarget(QEvent::Type eventType,
222− QTouchDevice *device,
223− Qt::KeyboardModifiers modifiers,
224− const QList<QTouchEvent::TouchPoint> &touchPoints,
225− QObject *target,
226− QWindow *window,
227− ulong timestamp)
228−{
229− removeTouchInfoForEndedTouches(touchPoints);
230−
231− if (m_targetItem.isNull()) {
232− qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
233− return;
234− }
235−
236− QQuickItem *targetItem = m_targetItem.data();
237−
238− if (!targetItem->isEnabled() || !targetItem->isVisible()) {
239− #if TOUCHGATE_DEBUG
240− qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
241− << "because it's disabled or invisible.";
242− #endif
243− return;
244− }
245−
246− // Map touch points to targetItem coordinates
247− QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
248− transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
249− QTouchEvent *eventForTargetItem = createQTouchEvent(eventType, device, modifiers, targetTouchPoints,
250− target, window, timestamp);
251−
252− #if TOUCHGATE_DEBUG
253− qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
254− << "to" << targetItem;
255− #endif
256−
257− QCoreApplication::sendEvent(targetItem, eventForTargetItem);
258−
259− delete eventForTargetItem;
260−}
261−
262−// NB: From QQuickWindow
263−void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
264−{
265− QMatrix4x4 transformMatrix(transform);
266− for (int i=0; i<touchPoints.count(); i++) {
267− QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
268− touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
269− touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
270− touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
271− touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
272− }
273−}
274−
275−QTouchEvent *TouchGate::createQTouchEvent(QEvent::Type eventType,
276− QTouchDevice *device,
277− Qt::KeyboardModifiers modifiers,
278− const QList<QTouchEvent::TouchPoint> &touchPoints,
279− QObject *target,
280− QWindow *window,
281− ulong timestamp)
282−{
283− Qt::TouchPointStates eventStates = 0;
284− for (int i = 0; i < touchPoints.count(); i++)
285− eventStates |= touchPoints[i].state();
286− // if all points have the same state, set the event type accordingly
287− switch (eventStates) {
288− case Qt::TouchPointPressed:
289− eventType = QEvent::TouchBegin;
290− break;
291− case Qt::TouchPointReleased:
292− eventType = QEvent::TouchEnd;
293− break;
294− default:
295− eventType = QEvent::TouchUpdate;
296− break;
297− }
298−
299− QTouchEvent *touchEvent = new QTouchEvent(eventType);
300− touchEvent->setWindow(window);
301− touchEvent->setTarget(target);
302− touchEvent->setDevice(device);
303− touchEvent->setModifiers(modifiers);
304− touchEvent->setTouchPoints(touchPoints);
305− touchEvent->setTouchPointStates(eventStates);
306− touchEvent->setTimestamp(timestamp);
307− touchEvent->accept();
308− return touchEvent;
309−}
310−
311215void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
312216{
313217 for (int i = 0; i < touchPoints.size(); ++i) {\
@@ -327,7 +231,7 @@ TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
327231 , device(event->device())
328232 , modifiers(event->modifiers())
329233 , touchPoints(event->touchPoints())
330− , target(event->target())
234+ , target(qobject_cast<QQuickItem*>(event->target()))
331235 , window(event->window())
332236 , timestamp(event->timestamp())
333237{
plugins/Ubuntu/Gestures/TouchGate.h+5−21
@@ -18,10 +18,10 @@
1818#define UBUNTU_TOUCH_GATE_H
1919
2020#include "UbuntuGesturesQmlGlobal.h"
21+#include "TouchDispatcher.h"
2122
2223#include <QQuickItem>
2324#include <QList>
24−#include <QPointer>
2525#include <QMap>
2626
2727#define TOUCHGATE_DEBUG 0
@@ -47,7 +47,7 @@ class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
4747public:
4848 bool event(QEvent *e) override;
4949
50− QQuickItem *targetItem() { return m_targetItem; }
50+ QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
5151 void setTargetItem(QQuickItem *item);
5252
5353Q_SIGNALS:
@@ -66,7 +66,7 @@ private:
6666 QTouchDevice *device;
6767 Qt::KeyboardModifiers modifiers;
6868 QList<QTouchEvent::TouchPoint> touchPoints;
69− QObject *target;
69+ QQuickItem *target;
7070 QWindow *window;
7171 ulong timestamp;
7272 };
@@ -80,23 +80,7 @@ private:
8080
8181 void dispatchTouchEventToTarget(const TouchEvent &event);
8282 void dispatchTouchEventToTarget(QTouchEvent* event);
83− void dispatchTouchEventToTarget(
84− QEvent::Type eventType,
85− QTouchDevice *device,
86− Qt::KeyboardModifiers modifiers,
87− const QList<QTouchEvent::TouchPoint> &touchPoints,
88− QObject *target,
89− QWindow *window,
90− ulong timestamp);
91−
92− void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
93− static QTouchEvent *createQTouchEvent(QEvent::Type eventType,
94− QTouchDevice *device,
95− Qt::KeyboardModifiers modifiers,
96− const QList<QTouchEvent::TouchPoint> &touchPoints,
97− QObject *target,
98− QWindow *window,
99− ulong timestamp);
83+
10084 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
10185
10286 #if TOUCHGATE_DEBUG
@@ -118,7 +102,7 @@ private:
118102 };
119103 QMap<int, TouchInfo> m_touchInfoMap;
120104
121− QPointer<QQuickItem> m_targetItem;
105+ TouchDispatcher m_dispatcher;
122106
123107 friend class tst_TouchGate;
124108};
plugins/Ubuntu/Payments/Payments.qmltypes+1
@@ -37,6 +37,7 @@ Module {
3737 Parameter { name: "error"; type: "string" }
3838 }
3939 Signal { name: "purchaseCompleted" }
40+ Signal { name: "purchaseCancelled" }
4041 Method { name: "start" }
4142 }
4243}
plugins/Unity/DashCommunicator/DashCommunicator.qmltypes+18−5
@@ -9,14 +9,14 @@ import QtQuick.tooling 1.1
99Module {
1010 Component {
1111 name: "DashCommunicator"
12− prototype: "QObject"
12+ prototype: "QThread"
1313 exports: ["Unity.DashCommunicator/DashCommunicator 0.1"]
1414 exportMetaObjectRevisions: [0]
1515 Method {
1616 name: "setCurrentScope"
17− Parameter { name: "scopeId"; type: "string" }
17+ Parameter { name: "index"; type: "int" }
1818 Parameter { name: "animate"; type: "bool" }
19− Parameter { name: "reset"; type: "bool" }
19+ Parameter { name: "isSwipe"; type: "bool" }
2020 }
2121 }
2222 Component {
@@ -26,9 +26,22 @@ Module {
2626 exportMetaObjectRevisions: [0]
2727 Signal {
2828 name: "setCurrentScopeRequested"
29− Parameter { name: "scopeId"; type: "string" }
29+ Parameter { name: "index"; type: "int" }
3030 Parameter { name: "animate"; type: "bool" }
31− Parameter { name: "reset"; type: "bool" }
31+ Parameter { name: "isSwipe"; type: "bool" }
32+ }
33+ }
34+ Component {
35+ name: "QThread"
36+ prototype: "QObject"
37+ Signal { name: "started" }
38+ Signal { name: "finished" }
39+ Method {
40+ name: "start"
41+ Parameter { type: "Priority" }
3242 }
43+ Method { name: "start" }
44+ Method { name: "terminate" }
45+ Method { name: "quit" }
3346 }
3447}
plugins/Unity/DashCommunicator/dashcommunicator.cpp+2−2
@@ -27,12 +27,12 @@ DashCommunicator::DashCommunicator(QObject *parent):
2727 start();
2828}
2929
30−void DashCommunicator::setCurrentScope(const QString &scopeId, bool animate, bool isSwipe)
30+void DashCommunicator::setCurrentScope(int index, bool animate, bool isSwipe)
3131{
3232 m_mutex.lock();
3333 if (m_created) {
3434 QMetaObject::invokeMethod(m_dashConnection, "setCurrentScope",
35− Q_ARG(QString, scopeId),
35+ Q_ARG(int, index),
3636 Q_ARG(bool, animate),
3737 Q_ARG(bool, isSwipe));
3838 }
plugins/Unity/DashCommunicator/dashcommunicator.h+1−1
@@ -31,7 +31,7 @@ public:
3131 ~DashCommunicator() = default;
3232
3333public Q_SLOTS:
34− void setCurrentScope(const QString &scopeId, bool animate, bool isSwipe);
34+ void setCurrentScope(int index, bool animate, bool isSwipe);
3535
3636protected:
3737 void run() override;
plugins/Unity/DashCommunicator/dashcommunicatorservice.h+1−1
@@ -29,7 +29,7 @@ public:
2929 ~DashCommunicatorService();
3030
3131Q_SIGNALS:
32− void setCurrentScopeRequested(const QString &scopeId, bool animate, bool isSwipe);
32+ void setCurrentScopeRequested(int index, bool animate, bool isSwipe);
3333
3434private:
3535 DBusDashCommunicatorService *m_dbusService;
plugins/Unity/DashCommunicator/dashconnection.cpp+2−2
@@ -25,9 +25,9 @@ DashConnection::DashConnection(const QString &service, const QString &path, cons
2525
2626}
2727
28−void DashConnection::setCurrentScope(const QString &scopeId, bool animate, bool isSwipe)
28+void DashConnection::setCurrentScope(int index, bool animate, bool isSwipe)
2929{
3030 if (dbusInterface()) {
31− dbusInterface()->asyncCall("SetCurrentScope", scopeId, animate, isSwipe);
31+ dbusInterface()->asyncCall("SetCurrentScope", index, animate, isSwipe);
3232 }
3333}
plugins/Unity/DashCommunicator/dashconnection.h+1−1
@@ -28,7 +28,7 @@ public:
2828 DashConnection(const QString &service, const QString &path, const QString &interface, QObject *parent = 0);
2929
3030public Q_SLOTS:
31− void setCurrentScope(const QString &scopeId, bool animate, bool isSwipe);
31+ void setCurrentScope(int index, bool animate, bool isSwipe);
3232};
3333
3434#endif
plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.cpp+3−7
@@ -21,19 +21,15 @@
2121#include <QDebug>
2222
2323DBusDashCommunicatorService::DBusDashCommunicatorService(QObject *parent):
24− QObject(parent)
24+ UnityDBusObject("/com/canonical/UnityDash", "com.canonical.UnityDash", false, parent)
2525{
26− QDBusConnection connection = QDBusConnection::sessionBus();
27−
28− connection.registerService("com.canonical.UnityDash");
29− connection.registerObject("/com/canonical/UnityDash", this, QDBusConnection::ExportScriptableSlots);
3026}
3127
3228DBusDashCommunicatorService::~DBusDashCommunicatorService()
3329{
3430}
3531
36−void DBusDashCommunicatorService::SetCurrentScope(const QString &scopeId, bool animate, bool isSwipe)
32+void DBusDashCommunicatorService::SetCurrentScope(int index, bool animate, bool isSwipe)
3733{
38− Q_EMIT setCurrentScopeRequested(scopeId, animate, isSwipe);
34+ Q_EMIT setCurrentScopeRequested(index, animate, isSwipe);
3935}
plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.h+4−4
@@ -17,9 +17,9 @@
1717#ifndef DBUSDASHCOMMUNICATORSERVICE_H
1818#define DBUSDASHCOMMUNICATORSERVICE_H
1919
20−#include <QObject>
20+#include "unitydbusobject.h"
2121
22−class DBusDashCommunicatorService: public QObject
22+class DBusDashCommunicatorService: public UnityDBusObject
2323{
2424 Q_OBJECT
2525 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.DashCommunicator")
@@ -29,10 +29,10 @@ public:
2929 ~DBusDashCommunicatorService();
3030
3131Q_SIGNALS:
32− void setCurrentScopeRequested(const QString &scopeId, bool animate, bool isSwipe);
32+ void setCurrentScopeRequested(int index, bool animate, bool isSwipe);
3333
3434public Q_SLOTS:
35− Q_SCRIPTABLE void SetCurrentScope(const QString &scopeId, bool animate, bool isSwipe);
35+ Q_SCRIPTABLE void SetCurrentScope(int index, bool animate, bool isSwipe);
3636
3737};
3838
plugins/Unity/Indicators/CMakeLists.txt+1
@@ -24,6 +24,7 @@ set(IndicatorsQML_SOURCES
2424 modelprinter.cpp
2525 plugin.cpp
2626 rootactionstate.cpp
27+ sharedunitymenumodel.cpp
2728 unitymenumodelcache.cpp
2829 unitymenumodelstack.cpp
2930 visibleindicatorsmodel.cpp
plugins/Unity/Indicators/Indicators.qmltypes+12−11
@@ -99,10 +99,8 @@ Module {
9999 values: {
100100 "Identifier": 0,
101101 "Position": 1,
102− "WidgetSource": 2,
103− "PageSource": 3,
104− "IndicatorProperties": 4,
105− "IsVisible": 5
102+ "IndicatorProperties": 2,
103+ "IsVisible": 3
106104 }
107105 }
108106 }
@@ -209,6 +207,16 @@ Module {
209207 Signal { name: "updated" }
210208 Signal { name: "indexChanged" }
211209 }
210+ Component {
211+ name: "SharedUnityMenuModel"
212+ prototype: "QObject"
213+ exports: ["Unity.Indicators/SharedUnityMenuModel 0.1"]
214+ exportMetaObjectRevisions: [0]
215+ Property { name: "busName"; type: "QByteArray" }
216+ Property { name: "menuObjectPath"; type: "QByteArray" }
217+ Property { name: "actions"; type: "QVariantMap" }
218+ Property { name: "model"; type: "UnityMenuModel"; isReadonly: true; isPointer: true }
219+ }
212220 Component {
213221 name: "UnityMenuModelCache"
214222 prototype: "QObject"
@@ -216,13 +224,6 @@ Module {
216224 isCreatable: false
217225 isSingleton: true
218226 exportMetaObjectRevisions: [0]
219− Method {
220− name: "model"
221− type: "UnityMenuModel*"
222− Parameter { name: "bus"; type: "QByteArray" }
223− Parameter { name: "path"; type: "QByteArray" }
224− Parameter { name: "actions"; type: "QVariantMap" }
225− }
226227 Method {
227228 name: "contains"
228229 type: "bool"
plugins/Unity/Indicators/indicators.h−2
@@ -74,8 +74,6 @@ public:
7474 enum Roles {
7575 Identifier = 0,
7676 Position,
77− WidgetSource,
78− PageSource,
7977 IndicatorProperties,
8078 IsVisible
8179 };
plugins/Unity/Indicators/indicatorsmodel.cpp−6
@@ -202,8 +202,6 @@ QHash<int, QByteArray> IndicatorsModel::roleNames() const
202202 {
203203 roles[IndicatorsModelRole::Identifier] = "identifier";
204204 roles[IndicatorsModelRole::Position] = "position";
205− roles[IndicatorsModelRole::WidgetSource] = "widgetSource";
206− roles[IndicatorsModelRole::PageSource] = "pageSource";
207205 roles[IndicatorsModelRole::IndicatorProperties] = "indicatorProperties";
208206 }
209207 return roles;
@@ -248,10 +246,6 @@ QVariant IndicatorsModel::data(const QModelIndex &index, int role) const
248246 return QVariant(indicator->indicatorProperties());
249247 }
250248 break;
251− case IndicatorsModelRole::WidgetSource:
252− return qmlDirectory()+"/Panel/Indicators/DefaultIndicatorWidget.qml";
253− case IndicatorsModelRole::PageSource:
254− return qmlDirectory()+"/Panel/Indicators/DefaultIndicatorPage.qml";
255249 default:
256250 break;
257251 }
plugins/Unity/Indicators/plugin.cpp+8−2
@@ -30,18 +30,23 @@
3030#include "menucontentactivator.h"
3131#include "modelprinter.h"
3232#include "rootactionstate.h"
33+#include "sharedunitymenumodel.h"
3334#include "unitymenumodelcache.h"
3435#include "unitymenumodelstack.h"
3536#include "visibleindicatorsmodel.h"
3637
38+#include <unitymenumodel.h>
39+
3740static QObject* menuModelCacheSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
3841 Q_UNUSED(engine);
3942 Q_UNUSED(scriptEngine);
40− return new UnityMenuModelCache;
43+ return UnityMenuModelCache::singleton();
4144}
4245
43−void Indicators2Plugin::registerTypes(const char *uri)
46+void IndicatorsPlugin::registerTypes(const char *uri)
4447{
48+ qRegisterMetaType<UnityMenuModel*>("UnityMenuModel*");
49+
4550 qmlRegisterType<IndicatorsManager>(uri, 0, 1, "IndicatorsManager");
4651 qmlRegisterType<IndicatorsModel>(uri, 0, 1, "IndicatorsModel");
4752 qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
@@ -49,6 +54,7 @@ void Indicators2Plugin::registerTypes(const char *uri)
4954 qmlRegisterType<RootActionState>(uri, 0, 1, "RootActionState");
5055 qmlRegisterType<ModelPrinter>(uri, 0, 1, "ModelPrinter");
5156 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
57+ qmlRegisterType<SharedUnityMenuModel>(uri, 0, 1, "SharedUnityMenuModel");
5258
5359 qmlRegisterSingletonType<UnityMenuModelCache>(uri, 0, 1, "UnityMenuModelCache", menuModelCacheSingleton);
5460
plugins/Unity/Indicators/plugin.h+1−1
@@ -22,7 +22,7 @@
2222
2323#include <QtQml/QQmlExtensionPlugin>
2424
25−class Indicators2Plugin : public QQmlExtensionPlugin
25+class IndicatorsPlugin : public QQmlExtensionPlugin
2626{
2727 Q_OBJECT
2828 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
plugins/Unity/Indicators/rootactionstate.cpp+1−1
@@ -156,7 +156,7 @@ QString RootActionState::accessibleName() const
156156 return m_cachedState.value("accessible-desc", QVariant::fromValue(QString())).toString();
157157}
158158
159−bool RootActionState::isVisible() const
159+bool RootActionState::indicatorVisible() const
160160{
161161 if (!isValid()) return false;
162162
plugins/Unity/Indicators/rootactionstate.h+4−4
@@ -37,7 +37,7 @@ class UNITYINDICATORS_EXPORT RootActionState : public ActionStateParser
3737 Q_PROPERTY(QString rightLabel READ rightLabel NOTIFY rightLabelChanged)
3838 Q_PROPERTY(QStringList icons READ icons NOTIFY iconsChanged)
3939 Q_PROPERTY(QString accessibleName READ accessibleName NOTIFY accessibleNameChanged)
40− Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged)
40+ Q_PROPERTY(bool indicatorVisible READ indicatorVisible NOTIFY indicatorVisibleChanged)
4141public:
4242 RootActionState(QObject *parent = 0);
4343 virtual ~RootActionState();
@@ -54,10 +54,10 @@ public:
5454 QString rightLabel() const;
5555 QStringList icons() const;
5656 QString accessibleName() const;
57− bool isVisible() const;
57+ bool indicatorVisible() const;
5858
5959 // from ActionStateParser
60− virtual QVariant toQVariant(GVariant* state) const;
60+ virtual QVariant toQVariant(GVariant* state) const override;
6161
6262Q_SIGNALS:
6363 void updated();
@@ -71,7 +71,7 @@ Q_SIGNALS:
7171 void rightLabelChanged();
7272 void iconsChanged();
7373 void accessibleNameChanged();
74− void visibleChanged();
74+ void indicatorVisibleChanged();
7575
7676private Q_SLOTS:
7777 void onModelRowsAdded(const QModelIndex& parent, int start, int end);
plugins/Unity/Indicators/sharedunitymenumodel.cppnew file+92
@@ -0,0 +1,92 @@
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 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+ */
17+
18+#include "sharedunitymenumodel.h"
19+#include "unitymenumodelcache.h"
20+
21+#include <unitymenumodel.h>
22+
23+SharedUnityMenuModel::SharedUnityMenuModel(QObject* parent)
24+ : QObject(parent)
25+{
26+}
27+
28+QByteArray SharedUnityMenuModel::busName() const
29+{
30+ return m_busName;
31+}
32+
33+void SharedUnityMenuModel::setBusName(const QByteArray& busName)
34+{
35+ if (m_busName != busName) {
36+ m_busName = busName;
37+ Q_EMIT busNameChanged();
38+ initialize();
39+ }
40+}
41+
42+QByteArray SharedUnityMenuModel::menuObjectPath() const
43+{
44+ return m_menuObjectPath;
45+}
46+
47+void SharedUnityMenuModel::setMenuObjectPath(const QByteArray& menuObjectPath)
48+{
49+ if (m_menuObjectPath != menuObjectPath) {
50+ m_menuObjectPath = menuObjectPath;
51+ Q_EMIT menuObjectPathChanged();
52+ initialize();
53+ }
54+}
55+
56+QVariantMap SharedUnityMenuModel::actions() const
57+{
58+ return m_actions;
59+}
60+
61+void SharedUnityMenuModel::setActions(const QVariantMap& actions)
62+{
63+ if (m_actions != actions) {
64+ m_actions = actions;
65+ Q_EMIT actionsChanged();
66+ initialize();
67+ }
68+}
69+
70+UnityMenuModel* SharedUnityMenuModel::model() const
71+{
72+ return m_model ? m_model.data() : nullptr;
73+}
74+
75+void SharedUnityMenuModel::initialize()
76+{
77+ if (m_busName.isEmpty() || m_menuObjectPath.isEmpty() || m_actions.isEmpty()) {
78+ if (!m_model.isNull()) {
79+ m_model.clear();
80+ Q_EMIT modelChanged();
81+ }
82+ } else {
83+ QSharedPointer<UnityMenuModel> model = UnityMenuModelCache::singleton()->model(m_menuObjectPath);
84+ if (model != m_model) {
85+ if (model->busName() != m_busName) model->setBusName(m_busName);
86+ if (model->actions() != m_actions) model->setActions(m_actions);
87+
88+ m_model = model;
89+ Q_EMIT modelChanged();
90+ }
91+ }
92+}
plugins/Unity/Indicators/sharedunitymenumodel.hnew file+66
@@ -0,0 +1,66 @@
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 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+ */
17+
18+#ifndef SHAREDUNITYMENUMODEL_H
19+#define SHAREDUNITYMENUMODEL_H
20+
21+#include "unityindicatorsglobal.h"
22+
23+#include <QObject>
24+#include <QSharedPointer>
25+#include <QVariantMap>
26+
27+class UnityMenuModel;
28+
29+class UNITYINDICATORS_EXPORT SharedUnityMenuModel : public QObject
30+{
31+ Q_OBJECT
32+ Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged)
33+ Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged)
34+ Q_PROPERTY(QVariantMap actions READ actions WRITE setActions NOTIFY actionsChanged)
35+ Q_PROPERTY(UnityMenuModel* model READ model NOTIFY modelChanged)
36+
37+public:
38+ SharedUnityMenuModel(QObject* parent = nullptr);
39+
40+ QByteArray busName() const;
41+ void setBusName(const QByteArray&);
42+
43+ QByteArray menuObjectPath() const;
44+ void setMenuObjectPath(const QByteArray&);
45+
46+ QVariantMap actions() const;
47+ void setActions(const QVariantMap&);
48+
49+ UnityMenuModel* model() const;
50+
51+Q_SIGNALS:
52+ void busNameChanged();
53+ void menuObjectPathChanged();
54+ void actionsChanged();
55+ void modelChanged();
56+
57+private:
58+ void initialize();
59+
60+ QByteArray m_busName;
61+ QByteArray m_menuObjectPath;
62+ QVariantMap m_actions;
63+ QSharedPointer<UnityMenuModel> m_model;
64+};
65+
66+#endif // SHAREDUNITYMENUMODEL_H
plugins/Unity/Indicators/unitymenumodelcache.cpp+25−14
@@ -20,34 +20,45 @@
2020#include "unitymenumodelcache.h"
2121#include <unitymenumodel.h>
2222
23−UnityMenuModelCache::UnityMenuModelCache(QObject* parent)
24− : QObject(parent)
23+#include <QQmlEngine>
24+
25+QPointer<UnityMenuModelCache> UnityMenuModelCache::theCache = nullptr;
26+
27+UnityMenuModelCache* UnityMenuModelCache::singleton()
2528{
29+ if (theCache.isNull()) {
30+ theCache = new UnityMenuModelCache();
31+ }
32+ return theCache.data();
2633}
2734
28−UnityMenuModelCache::~UnityMenuModelCache()
35+UnityMenuModelCache::UnityMenuModelCache(QObject* parent)
36+ : QObject(parent)
2937{
3038}
3139
32−UnityMenuModel* UnityMenuModelCache::model(const QByteArray& bus,
33− const QByteArray& path,
34− const QVariantMap& actions)
40+QSharedPointer<UnityMenuModel> UnityMenuModelCache::model(const QByteArray& path)
3541{
3642 if (m_registry.contains(path))
3743 return m_registry[path];
3844
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);
45+ UnityMenuModel* model = new UnityMenuModel;
46+ QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
47+
48+ QSharedPointer<UnityMenuModel> menuModel(model);
49+ connect(model, &QObject::destroyed, this, [this] {
50+ QMutableHashIterator<QByteArray, QWeakPointer<UnityMenuModel>> iter(m_registry);
51+ while(iter.hasNext()) {
52+ auto keyVal = iter.next();
53+ if (keyVal.value().isNull()) {
54+ iter.remove();
55+ break;
56+ }
4457 }
4558 });
46− m_registry[path] = menuModel;
59+ m_registry[path] = menuModel.toWeakRef();
4760
48− menuModel->setBusName(bus);
4961 menuModel->setMenuObjectPath(path);
50− menuModel->setActions(actions);
5162 return menuModel;
5263}
5364
plugins/Unity/Indicators/unitymenumodelcache.h+11−7
@@ -24,6 +24,8 @@
2424
2525#include <QObject>
2626#include <QHash>
27+#include <QPointer>
28+#include <QWeakPointer>
2729
2830class UnityMenuModel;
2931
@@ -32,15 +34,17 @@ class UNITYINDICATORS_EXPORT UnityMenuModelCache : public QObject
3234 Q_OBJECT
3335public:
3436 UnityMenuModelCache(QObject*parent=nullptr);
35− ~UnityMenuModelCache();
3637
37− Q_INVOKABLE UnityMenuModel* model(const QByteArray& bus,
38− const QByteArray& path,
39− const QVariantMap& actions);
40− Q_INVOKABLE bool contains(const QByteArray& path);
38+ static UnityMenuModelCache* singleton();
4139
42−private:
43− QHash<QByteArray, UnityMenuModel*> m_registry;
40+ virtual QSharedPointer<UnityMenuModel> model(const QByteArray& path);
41+
42+ // for tests use
43+ Q_INVOKABLE virtual bool contains(const QByteArray& path);
44+
45+protected:
46+ QHash<QByteArray, QWeakPointer<UnityMenuModel>> m_registry;
47+ static QPointer<UnityMenuModelCache> theCache;
4448};
4549
4650#endif // UNITYMENUMODELCACHE_H
plugins/Unity/Indicators/visibleindicatorsmodel.cpp−2
@@ -33,8 +33,6 @@ QHash<int, QByteArray> VisibleIndicatorsModel::roleNames() const
3333 {
3434 roles[IndicatorsModelRole::Identifier] = "identifier";
3535 roles[IndicatorsModelRole::Position] = "position";
36− roles[IndicatorsModelRole::WidgetSource] = "widgetSource";
37− roles[IndicatorsModelRole::PageSource] = "pageSource";
3836 roles[IndicatorsModelRole::IndicatorProperties] = "indicatorProperties";
3937 roles[IndicatorsModelRole::IsVisible] = "isVisible";
4038 }
plugins/Unity/Launcher/CMakeLists.txt+5−1
@@ -9,6 +9,7 @@ include_directories(
99 ${CMAKE_CURRENT_SOURCE_DIR}
1010 ${CMAKE_SOURCE_DIR}/plugins/AccountsService
1111 ${GSETTINGS_QT_INCLUDE_DIRS}
12+ ${libunity8-private_SOURCE_DIR}
1213)
1314
1415set(QMLLAUNCHERPLUGIN_SRC
@@ -30,7 +31,10 @@ add_library(UnityLauncher-qml MODULE
3031 ${QMLLAUNCHERPLUGIN_SRC}
3132 )
3233
33−target_link_libraries(UnityLauncher-qml ${GSETTINGS_QT_LDFLAGS})
34+target_link_libraries(UnityLauncher-qml
35+ unity8-private
36+ ${GSETTINGS_QT_LDFLAGS}
37+ )
3438
3539qt5_use_modules(UnityLauncher-qml DBus Qml Gui)
3640
plugins/Unity/Launcher/Launcher.qmltypes+17−12
@@ -13,14 +13,6 @@ Module {
1313 exports: ["Unity.Launcher/LauncherItem 0.1"]
1414 isCreatable: false
1515 exportMetaObjectRevisions: [0]
16− Signal {
17− name: "favoriteChanged"
18− Parameter { name: "favorite"; type: "bool" }
19− }
20− Signal {
21− name: "runningChanged"
22− Parameter { name: "running"; type: "bool" }
23− }
2416 }
2517 Component {
2618 name: "LauncherModel"
@@ -29,6 +21,10 @@ Module {
2921 isCreatable: false
3022 isSingleton: true
3123 exportMetaObjectRevisions: [0]
24+ Method {
25+ name: "requestRemove"
26+ Parameter { name: "appId"; type: "string" }
27+ }
3228 Method {
3329 name: "get"
3430 type: "unity::shell::launcher::LauncherItemInterface*"
@@ -48,10 +44,6 @@ Module {
4844 name: "pin"
4945 Parameter { name: "appId"; type: "string" }
5046 }
51− Method {
52− name: "requestRemove"
53− Parameter { name: "appId"; type: "string" }
54− }
5547 Method {
5648 name: "quickListActionInvoked"
5749 Parameter { name: "appId"; type: "string" }
@@ -88,6 +80,7 @@ Module {
8880 Property { name: "recent"; type: "bool"; isReadonly: true }
8981 Property { name: "progress"; type: "int"; isReadonly: true }
9082 Property { name: "count"; type: "int"; isReadonly: true }
83+ Property { name: "countVisible"; type: "bool"; isReadonly: true }
9184 Property { name: "focused"; type: "bool"; isReadonly: true }
9285 Property {
9386 name: "quickList"
@@ -95,6 +88,14 @@ Module {
9588 isReadonly: true
9689 isPointer: true
9790 }
91+ Signal {
92+ name: "nameChanged"
93+ Parameter { name: "name"; type: "string" }
94+ }
95+ Signal {
96+ name: "iconChanged"
97+ Parameter { name: "icon"; type: "string" }
98+ }
9899 Signal {
99100 name: "pinnedChanged"
100101 Parameter { name: "pinned"; type: "bool" }
@@ -115,6 +116,10 @@ Module {
115116 name: "countChanged"
116117 Parameter { name: "count"; type: "int" }
117118 }
119+ Signal {
120+ name: "countVisibleChanged"
121+ Parameter { name: "countVisible"; type: "bool" }
122+ }
118123 Signal {
119124 name: "focusedChanged"
120125 Parameter { name: "focused"; type: "bool" }
plugins/Unity/Launcher/dbusinterface.cpp+3−36
@@ -27,25 +27,13 @@
2727#include <QDebug>
2828
2929DBusInterface::DBusInterface(LauncherModel *parent):
30− QDBusVirtualObject(parent),
30+ UnityDBusVirtualObject("/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher", true, parent),
3131 m_launcherModel(parent)
3232{
33− /* Set up ourselves on DBus */
34− QDBusConnection con = QDBusConnection::sessionBus();
35− if (!con.registerService("com.canonical.Unity.Launcher")) {
36− qWarning() << "Unable to register launcher name";
37− }
38− if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
39− qWarning() << "Unable to register launcher object";
40− }
4133}
4234
4335DBusInterface::~DBusInterface()
4436{
45− /* Remove oursevles from DBus */
46− QDBusConnection con = QDBusConnection::sessionBus();
47− con.unregisterService("com.canonical.Unity.Launcher");
48− con.unregisterObject("/com/canonical/Unity/Launcher");
4937}
5038
5139QString DBusInterface::introspect(const QString &path) const
@@ -186,13 +174,13 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
186174 int newCount = message.arguments()[2].toInt();
187175 if (!item || newCount != item->count()) {
188176 Q_EMIT countChanged(appid, newCount);
189− emitPropChangedDbus(appid, "count", QVariant(newCount));
177+ notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "count", QVariant(newCount));
190178 }
191179 } else if (message.arguments()[1].toString() == "countVisible") {
192180 bool newVisible = message.arguments()[2].toBool();
193181 if (!item || newVisible != item->countVisible()) {
194182 Q_EMIT countVisibleChanged(appid, newVisible);
195− emitPropChangedDbus(appid, "countVisible", newVisible);
183+ notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "countVisible", newVisible);
196184 }
197185 }
198186 } else if (message.member() == "GetAll") {
@@ -209,24 +197,3 @@ bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnec
209197 QDBusMessage reply = message.createReply(retval);
210198 return connection.send(reply);
211199}
212−
213−void DBusInterface::emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value)
214−{
215− QString path("/com/canonical/Unity/Launcher/");
216− path.append(encodeAppId(appId));
217−
218− QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
219−
220− QList<QVariant> arguments;
221− QVariantHash changedprops;
222− changedprops[property] = QVariant::fromValue(QDBusVariant(value));
223− QVariantList deletedprops;
224−
225− arguments.append(changedprops);
226− arguments.append(deletedprops);
227−
228− message.setArguments(arguments);
229−
230− QDBusConnection con = QDBusConnection::sessionBus();
231− con.send(message);
232−}
plugins/Unity/Launcher/dbusinterface.h+2−5
@@ -18,12 +18,11 @@
1818 */
1919
2020#include "launcheritem.h"
21−
22−#include <QDBusVirtualObject>
21+#include "unitydbusvirtualobject.h"
2322
2423class LauncherModel;
2524
26−class DBusInterface: public QDBusVirtualObject
25+class DBusInterface: public UnityDBusVirtualObject
2726{
2827 Q_OBJECT
2928public:
@@ -43,8 +42,6 @@ private:
4342 static QString decodeAppId(const QString& path);
4443 static QString encodeAppId(const QString& appId);
4544
46− void emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value);
47−
4845 LauncherModel *m_launcherModel;
4946
5047};
plugins/Unity/Launcher/desktopfilehandler.cpp+1−1
@@ -70,7 +70,7 @@ void DesktopFileHandler::load()
7070
7171 QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
7272#ifdef LAUNCHER_TESTING
73− searchDirs << "";
73+ searchDirs << ".";
7474#endif
7575
7676 QString path;
plugins/Unity/Launcher/gsettings.cpp+11−7
@@ -19,22 +19,20 @@
1919
2020#include "gsettings.h"
2121
22−#include <QGSettings>
2322#include <QVariant>
2423
2524GSettings::GSettings(QObject *parent):
2625 QObject(parent)
2726{
28−
27+ m_gSettings = new QGSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/", this);
28+ connect(m_gSettings, &QGSettings::changed, this, &GSettings::onSettingsChanged);
2929}
3030
3131QStringList GSettings::storedApplications() const
3232{
3333 QStringList storedApps;
3434
35− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
36−
37− Q_FOREACH(const QString &entry, gSettings.get("items").toStringList()) {
35+ Q_FOREACH(const QString &entry, m_gSettings->get("items").toStringList()) {
3836 if (entry.startsWith("application:///")) {
3937 // convert legacy entries to new world appids
4038 QString appId = entry;
@@ -57,10 +55,16 @@ QStringList GSettings::storedApplications() const
5755
5856void GSettings::setStoredApplications(const QStringList &storedApplications)
5957{
60− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
6158 QStringList gSettingsList;
6259 Q_FOREACH(const QString &entry, storedApplications) {
6360 gSettingsList << QString("appid://%1").arg(entry);
6461 }
65− gSettings.set("items", gSettingsList);
62+ m_gSettings->set("items", gSettingsList);
63+}
64+
65+void GSettings::onSettingsChanged(const QString &key)
66+{
67+ if (key == "items") {
68+ Q_EMIT changed();
69+ }
6670}
plugins/Unity/Launcher/gsettings.h+10−1
@@ -22,7 +22,7 @@
2222
2323#include <QObject>
2424#include <QStringList>
25−
25+#include <QGSettings>
2626
2727class GSettings: public QObject
2828{
@@ -32,6 +32,15 @@ public:
3232
3333 QStringList storedApplications() const;
3434 void setStoredApplications(const QStringList &storedApplications);
35+
36+Q_SIGNALS:
37+ void changed();
38+
39+private Q_SLOTS:
40+ void onSettingsChanged(const QString &key);
41+
42+private:
43+ QGSettings *m_gSettings;
3544};
3645
3746#endif
plugins/Unity/Launcher/launchermodel.cpp+75−32
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -40,20 +40,9 @@ LauncherModel::LauncherModel(QObject *parent):
4040 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
4141 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
4242
43− Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
44− DesktopFileHandler desktopFile(entry);
45− if (!desktopFile.isValid()) {
46− qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
47− continue;
48− }
43+ connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
4944
50− LauncherItem *item = new LauncherItem(entry,
51− desktopFile.displayName(),
52− desktopFile.icon(),
53− this);
54− item->setPinned(true);
55− m_list.append(item);
56− }
45+ refresh();
5746}
5847
5948LauncherModel::~LauncherModel()
@@ -173,22 +162,7 @@ void LauncherModel::pin(const QString &appId, int index)
173162
174163void LauncherModel::requestRemove(const QString &appId)
175164{
176− int index = findApplication(appId);
177− if (index < 0) {
178− return;
179− }
180−
181− if (m_appManager->findApplication(appId)) {
182− m_list.at(index)->setPinned(false);
183− QModelIndex modelIndex = this->index(index);
184− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
185− return;
186− }
187−
188− beginRemoveRows(QModelIndex(), index, index);
189− m_list.takeAt(index)->deleteLater();
190− endRemoveRows();
191−
165+ unpin(appId);
192166 storeAppList();
193167}
194168
@@ -299,6 +273,24 @@ void LauncherModel::storeAppList()
299273 m_settings->setStoredApplications(appIds);
300274}
301275
276+void LauncherModel::unpin(const QString &appId)
277+{
278+ int index = findApplication(appId);
279+ if (index < 0) {
280+ return;
281+ }
282+
283+ if (m_appManager->findApplication(appId)) {
284+ m_list.at(index)->setPinned(false);
285+ QModelIndex modelIndex = this->index(index);
286+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
287+ } else {
288+ beginRemoveRows(QModelIndex(), index, index);
289+ m_list.takeAt(index)->deleteLater();
290+ endRemoveRows();
291+ }
292+}
293+
302294int LauncherModel::findApplication(const QString &appId)
303295{
304296 for (int i = 0; i < m_list.count(); ++i) {
@@ -320,7 +312,6 @@ void LauncherModel::progressChanged(const QString &appId, int progress)
320312 }
321313}
322314
323−
324315void LauncherModel::countChanged(const QString &appId, int count)
325316{
326317 int idx = findApplication(appId);
@@ -362,10 +353,15 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
362353
363354void LauncherModel::refresh()
364355{
356+ // First walk through all the existing items and see if we need to remove something
365357 QList<LauncherItem*> toBeRemoved;
366358 Q_FOREACH (LauncherItem* item, m_list) {
367359 DesktopFileHandler desktopFile(item->appId());
368360 if (!desktopFile.isValid()) {
361+ // Desktop file not available for this app => drop it!
362+ toBeRemoved << item;
363+ } else if (!m_settings->storedApplications().contains(item->appId())) {
364+ // Item not in settings any more => drop it!
369365 toBeRemoved << item;
370366 } else {
371367 int idx = m_list.indexOf(item);
@@ -376,7 +372,54 @@ void LauncherModel::refresh()
376372 }
377373
378374 Q_FOREACH (LauncherItem* item, toBeRemoved) {
379− requestRemove(item->appId());
375+ unpin(item->appId());
376+ }
377+
378+ // This brings the Launcher into sync with the settings backend again. There's an issue though:
379+ // If we can't find a .desktop file for an entry we need to skip it. That makes our settingsIndex
380+ // go out of sync with the actual index of items. So let's also use an addedIndex which reflects
381+ // the settingsIndex minus the skipped items.
382+ int addedIndex = 0;
383+
384+ // Now walk through settings and see if we need to add something
385+ for (int settingsIndex = 0; settingsIndex < m_settings->storedApplications().count(); ++settingsIndex) {
386+ QString entry = m_settings->storedApplications().at(settingsIndex);
387+ int itemIndex = -1;
388+ for (int i = 0; i < m_list.count(); ++i) {
389+ if (m_list.at(i)->appId() == entry) {
390+ itemIndex = i;
391+ break;
392+ }
393+ }
394+
395+ if (itemIndex == -1) {
396+ // Need to add it. Just add it into the addedIndex to keep same ordering as the list
397+ // in the settings.
398+ DesktopFileHandler desktopFile(entry);
399+ if (!desktopFile.isValid()) {
400+ qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
401+ continue;
402+ }
403+
404+ LauncherItem *item = new LauncherItem(entry,
405+ desktopFile.displayName(),
406+ desktopFile.icon(),
407+ this);
408+ item->setPinned(true);
409+ beginInsertRows(QModelIndex(), addedIndex, addedIndex);
410+ m_list.insert(addedIndex, item);
411+ endInsertRows();
412+ } else if (itemIndex != addedIndex) {
413+ // The item is already there, but it is in a different place then in the settings.
414+ // Move it to the addedIndex
415+ beginMoveRows(QModelIndex(), itemIndex, itemIndex, QModelIndex(), addedIndex);
416+ m_list.move(itemIndex, addedIndex);
417+ endMoveRows();
418+ }
419+
420+ // Just like settingsIndex, this will increase with every item, except the ones we
421+ // skipped with the "continue" call above.
422+ addedIndex++;
380423 }
381424}
382425
plugins/Unity/Launcher/launchermodel.h+5−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -62,6 +62,8 @@ public Q_SLOTS:
6262private:
6363 void storeAppList();
6464
65+ void unpin(const QString &appId);
66+
6567private Q_SLOTS:
6668 void countChanged(const QString &appId, int count);
6769 void countVisibleChanged(const QString &appId, int count);
@@ -79,6 +81,8 @@ private:
7981 DBusInterface *m_dbusIface;
8082
8183 ApplicationManagerInterface *m_appManager;
84+
85+ friend class LauncherModelTest;
8286};
8387
8488#endif // LAUNCHERMODEL_H
plugins/Unity/Session/CMakeLists.txt+5−1
@@ -5,6 +5,7 @@ pkg_search_module(GIO REQUIRED gio-2.0)
55include_directories(
66 ${CMAKE_CURRENT_SOURCE_DIR}
77 ${GIO_INCLUDE_DIRS}
8+ ${libunity8-private_SOURCE_DIR}
89)
910
1011set(QMLSESSIONPLUGIN_SRC
@@ -18,7 +19,10 @@ add_library(UnitySession-qml MODULE
1819 )
1920
2021qt5_use_modules(UnitySession-qml DBus Qml)
21−target_link_libraries(UnitySession-qml ${GIO_LDFLAGS})
22+target_link_libraries(UnitySession-qml
23+ unity8-private
24+ ${GIO_LDFLAGS}
25+ )
2226
2327# export the qmldir and qmltypes files
2428add_unity8_plugin(Unity.Session 0.1 Unity/Session TARGETS UnitySession-qml)
plugins/Unity/Session/Session.qmltypes+10
@@ -34,4 +34,14 @@ Module {
3434 Method { name: "RequestReboot" }
3535 Method { name: "RequestShutdown" }
3636 }
37+ Component {
38+ name: "OrientationLock"
39+ prototype: "QObject"
40+ exports: ["Unity.Session/OrientationLock 0.1"]
41+ isCreatable: false
42+ isSingleton: true
43+ exportMetaObjectRevisions: [0]
44+ Property { name: "enabled"; type: "bool"; isReadonly: true }
45+ Property { name: "savedOrientation"; type: "Qt::ScreenOrientation" }
46+ }
3747}
plugins/Unity/Session/dbusunitysessionservice.cpp+2−8
@@ -21,15 +21,9 @@
2121#include <QDBusConnection>
2222#include <QDBusInterface>
2323
24−DBusUnitySessionService::DBusUnitySessionService() : QObject()
24+DBusUnitySessionService::DBusUnitySessionService()
25+ : UnityDBusObject("/com/canonical/Unity/Session", "com.canonical.Unity")
2526{
26− QDBusConnection connection = QDBusConnection::sessionBus();
27−
28− connection.registerService("com.canonical.Unity");
29− connection.registerObject("/com/canonical/Unity/Session", this,
30− QDBusConnection::ExportScriptableSignals
31− | QDBusConnection::ExportScriptableSlots
32− | QDBusConnection::ExportScriptableInvokables);
3327}
3428
3529DBusUnitySessionService::~DBusUnitySessionService()
plugins/Unity/Session/dbusunitysessionservice.h+2−2
@@ -17,7 +17,7 @@
1717#ifndef DBUSUNITYSESSIONSERVICE_H
1818#define DBUSUNITYSESSIONSERVICE_H
1919
20−#include <QObject>
20+#include "unitydbusobject.h"
2121
2222/**
2323 * DBusUnitySessionService provides com.canonical.Unity.Session dbus
@@ -26,7 +26,7 @@
2626 * com.canonical.Unity.Session interface provides public methods
2727 * and signals to handle Logout/Reboot/Shutdown.
2828 */
29−class DBusUnitySessionService : public QObject
29+class DBusUnitySessionService : public UnityDBusObject
3030{
3131 Q_OBJECT
3232 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.Session")
plugins/Utils/Utils.qmltypes+6
@@ -106,6 +106,12 @@ Module {
106106 Parameter { name: "row"; type: "int" }
107107 }
108108 }
109+ Component {
110+ name: "RelativeTimeFormatter"
111+ prototype: "TimeFormatter"
112+ exports: ["Utils/RelativeTimeFormatter 0.1"]
113+ exportMetaObjectRevisions: [0]
114+ }
109115 Component {
110116 name: "TimeFormatter"
111117 prototype: "QObject"
po/de.po+18−20
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-10-07 11:37+0300\n"
11−"PO-Revision-Date: 2014-09-29 06:33+0000\n"
12−"Last-Translator: Niklas Wenzel <nikwen.developer@gmail.com>\n"
11+"PO-Revision-Date: 2014-10-21 11:26+0000\n"
12+"Last-Translator: Alexander Dobetsberger <Unknown>\n"
1313"Language-Team: German <de@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-10-09 06:36+0000\n"
19−"X-Generator: Launchpad (build 17196)\n"
18+"X-Launchpad-Export-Date: 2014-10-22 06:17+0000\n"
19+"X-Generator: Launchpad (build 17203)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:130
2222msgid "Password: "
@@ -33,22 +33,26 @@ msgstr "Verknüpfung entfernen"
3333
3434#: qml/Components/DelayedLockscreen.qml:47
3535msgid "Device Locked"
36−msgstr ""
36+msgstr "Gerät gesperrt"
3737
3838#: qml/Components/DelayedLockscreen.qml:62
3939msgid "You have been locked out due to too many failed passphrase attempts."
4040msgstr ""
41+"Das Gerät wurde aufgrund zu vieler falscher Passworteingaben für längere "
42+"Zeit gesperrt."
4143
4244#: qml/Components/DelayedLockscreen.qml:63
4345msgid "You have been locked out due to too many failed passcode attempts."
4446msgstr ""
47+"Das Gerät wurde aufgrund zu vieler falscher PIN-Eingaben für längere Zeit "
48+"gesperrt."
4549
4650#: qml/Components/DelayedLockscreen.qml:72
4751#, qt-format
4852msgid "Please wait %1 minute and then try again…"
4953msgid_plural "Please wait %1 minutes and then try again…"
50−msgstr[0] ""
51−msgstr[1] ""
54+msgstr[0] "Bitte warten Sie %1 Minute und versuchen Sie es dann erneut..."
55+msgstr[1] "Bitte warten Sie %1 Minuten und versuchen Sie es dann erneut..."
5256
5357#: qml/Components/Dialogs.qml:70
5458msgid "Log out"
@@ -214,11 +218,11 @@ msgstr "Senden"
214218
215219#: qml/Dash/PullToRefreshScopeStyle.qml:55
216220msgid "Pull to refresh…"
217−msgstr ""
221+msgstr "Zum Aktualisieren ziehen …"
218222
219223#: qml/Dash/PullToRefreshScopeStyle.qml:60
220224msgid "Release to refresh…"
221−msgstr ""
225+msgstr "Zum Aktualisieren loslassen …"
222226
223227#: qml/Dash/ScopesOverview.qml:206
224228msgid "Manage Scopes"
@@ -242,7 +246,7 @@ msgstr "Alle"
242246
243247#: qml/Greeter/Greeter.qml:157
244248msgid "Unlock"
245−msgstr ""
249+msgstr "Entsperren"
246250
247251#: qml/Hud/HudParametrizedActionsPage.qml:139
248252msgid "Confirm"
@@ -314,7 +318,7 @@ msgstr "Roaming"
314318
315319#: qml/Shell.qml:346
316320msgid "Enter passphrase"
317−msgstr ""
321+msgstr "Passwort eingeben"
318322
319323#: qml/Shell.qml:347
320324msgid "Sorry, incorrect passphrase"
@@ -322,11 +326,11 @@ msgstr "Das Passwort ist nicht korrekt"
322326
323327#: qml/Shell.qml:348
324328msgid "Please re-enter"
325−msgstr ""
329+msgstr "Bitte erneut eingeben"
326330
327331#: qml/Shell.qml:350
328332msgid "Enter passcode"
329−msgstr ""
333+msgstr "PIN eingeben"
330334
331335#: qml/Shell.qml:351
332336msgid "Sorry, incorrect passcode"
@@ -335,7 +339,7 @@ msgstr "Falsches Passwort"
335339#: qml/Shell.qml:354
336340#, qt-format
337341msgid "Enter %1"
338−msgstr ""
342+msgstr "%1 eingeben"
339343
340344#: qml/Shell.qml:355
341345#, qt-format
@@ -369,9 +373,3 @@ msgid ""
369373msgstr ""
370374"Wird die PIN falsch eingegeben, wird das Telefon auf den Werkszustand "
371375"zurückgesetzt und alle Daten werden gelöscht."
372−
373−#~ msgid "Sorry, incorrect PIN"
374−#~ msgstr "Die PIN ist nicht korrekt"
375−
376−#~ msgid "Enter SIM PIN"
377−#~ msgstr "Geben Sie die SIM-PIN ein"
po/hy.po+63−59
@@ -8,111 +8,115 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-10-07 11:37+0300\n"
11−"PO-Revision-Date: 2013-06-27 04:43+0000\n"
12−"Last-Translator: iAbaS <sosabazyan@yandex.ru>\n"
11+"PO-Revision-Date: 2014-10-28 06:34+0000\n"
12+"Last-Translator: Vahan Harutyunyan <Unknown>\n"
1313"Language-Team: Armenian <hy@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2014-10-09 06:36+0000\n"
19−"X-Generator: Launchpad (build 17196)\n"
18+"X-Launchpad-Export-Date: 2014-10-29 07:27+0000\n"
19+"X-Generator: Launchpad (build 17203)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:130
2222msgid "Password: "
23−msgstr ""
23+msgstr "Գաղտնաբառ: "
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:45
2626#: plugins/Unity/Launcher/launcheritem.cpp:91
2727msgid "Pin shortcut"
28−msgstr ""
28+msgstr "Փակցնել պիտակը"
2929
3030#: plugins/Unity/Launcher/launcheritem.cpp:91
3131msgid "Unpin shortcut"
32−msgstr ""
32+msgstr "Հեռացնել պիտակը"
3333
3434#: qml/Components/DelayedLockscreen.qml:47
3535msgid "Device Locked"
36−msgstr ""
36+msgstr "Սարքը արգելափակված է"
3737
3838#: qml/Components/DelayedLockscreen.qml:62
3939msgid "You have been locked out due to too many failed passphrase attempts."
4040msgstr ""
41+"Գաղտնաբառի բավականին շատ ձախողված փորձերի պատճառով Դուք արգելափակված եք:"
4142
4243#: qml/Components/DelayedLockscreen.qml:63
4344msgid "You have been locked out due to too many failed passcode attempts."
4445msgstr ""
46+"Ծածկագրի բավականին շատ ձախողված փորձերի պատճառով Դուք արգելափակված եք:"
4547
4648#: qml/Components/DelayedLockscreen.qml:72
4749#, qt-format
4850msgid "Please wait %1 minute and then try again…"
4951msgid_plural "Please wait %1 minutes and then try again…"
5052msgstr[0] ""
51−msgstr[1] ""
53+"Խնդրում ենք սպասել %1 րոպե և փորձել կրկին...\r\n"
54+"Խնդրում ենք սպասել %1 րոպե և փորձել կրկին..."
5255
5356#: qml/Components/Dialogs.qml:70
5457msgid "Log out"
55−msgstr ""
58+msgstr "Ելք"
5659
5760#: qml/Components/Dialogs.qml:71
5861msgid "Are you sure you want to log out?"
59−msgstr ""
62+msgstr "Դուք համոզվա՞ծ եք, որ ցանկանում եք դուրս գալ համակարգից"
6063
6164#: qml/Components/Dialogs.qml:73 qml/Components/Dialogs.qml:97
6265#: qml/Components/Dialogs.qml:122
6366msgid "No"
64−msgstr ""
67+msgstr "Ոչ"
6568
6669#: qml/Components/Dialogs.qml:80 qml/Components/Dialogs.qml:104
6770#: qml/Components/Dialogs.qml:129
6871msgid "Yes"
69−msgstr ""
72+msgstr "Այո"
7073
7174#: qml/Components/Dialogs.qml:94
7275msgid "Shut down"
73−msgstr ""
76+msgstr "Անջատել"
7477
7578#: qml/Components/Dialogs.qml:95
7679msgid "Are you sure you want to shut down?"
77−msgstr ""
80+msgstr "Դուք համոզվա՞ծ եք, որ ցանկանում եք անջատել համակարգիչը"
7881
7982#: qml/Components/Dialogs.qml:119
8083msgid "Reboot"
81−msgstr ""
84+msgstr "Վերաբեռնել"
8285
8386#: qml/Components/Dialogs.qml:120
8487msgid "Are you sure you want to reboot?"
85−msgstr ""
88+msgstr "Դուք համոզվա՞ծ եք, որ ցանկանում եք վերաբեռնել համակարգիչը"
8689
8790#: qml/Components/Dialogs.qml:144
8891msgid "Power"
89−msgstr ""
92+msgstr "Էլեկտրասնուցում"
9093
9194#: qml/Components/Dialogs.qml:145
9295msgid ""
9396"Are you sure you would like\n"
9497"to power off?"
9598msgstr ""
99+"Դուք համոզվա՞ծ եք, որ ցանկանում եք անջատել համակարգիչը սնուցման աղբյուրից"
96100
97101#: qml/Components/Dialogs.qml:147
98102msgid "Power off"
99−msgstr ""
103+msgstr "Անջատել սնուցման աղբյուրից"
100104
101105#: qml/Components/Dialogs.qml:157
102106msgid "Restart"
103−msgstr ""
107+msgstr "Վերագործարկել"
104108
105109#: qml/Components/Dialogs.qml:167
106110msgid "Cancel"
107−msgstr ""
111+msgstr "Չեղյալ համարել"
108112
109113#: qml/Components/EdgeDemoOverlay.qml:151
110114msgid "Skip intro"
111−msgstr ""
115+msgstr "Բաց թողնել ներածությունը"
112116
113117#: qml/Components/EdgeDemo.qml:112
114118msgid "Right edge"
115−msgstr ""
119+msgstr "Աջ եզրը"
116120
117121#: qml/Components/EdgeDemo.qml:113
118122msgid "Try swiping from the right edge to unlock the phone"
@@ -120,7 +124,7 @@ msgstr ""
120124
121125#: qml/Components/EdgeDemo.qml:144
122126msgid "Top edge"
123−msgstr ""
127+msgstr "Վերին եզրը"
124128
125129#: qml/Components/EdgeDemo.qml:145
126130msgid "Try swiping from the top edge to access the indicators"
@@ -128,7 +132,7 @@ msgstr ""
128132
129133#: qml/Components/EdgeDemo.qml:170
130134msgid "Close"
131−msgstr ""
135+msgstr "Փակել"
132136
133137#: qml/Components/EdgeDemo.qml:171
134138msgid "Swipe up again to close the settings screen"
@@ -136,7 +140,7 @@ msgstr ""
136140
137141#: qml/Components/EdgeDemo.qml:198
138142msgid "Left edge"
139−msgstr ""
143+msgstr "Ձախ եզրը"
140144
141145#: qml/Components/EdgeDemo.qml:199
142146msgid "Swipe from the left to reveal the launcher for quick access to apps"
@@ -144,7 +148,7 @@ msgstr ""
144148
145149#: qml/Components/EdgeDemo.qml:226
146150msgid "Well done"
147−msgstr ""
151+msgstr "Լավ է"
148152
149153#: qml/Components/EdgeDemo.qml:227
150154msgid ""
@@ -166,7 +170,7 @@ msgstr ""
166170
167171#: qml/Dash/GenericScopeView.qml:434
168172msgid "See all"
169−msgstr ""
173+msgstr "Տեսնել բոլորը"
170174
171175#: qml/Dash/GenericScopeView.qml:496 qml/Dash/PageHeader.qml:258
172176#: qml/Panel/SearchIndicator.qml:27
@@ -175,27 +179,27 @@ msgstr "Որոնում"
175179
176180#: qml/Dash/PageHeader.qml:268
177181msgid "Settings"
178−msgstr ""
182+msgstr "Կարգավորումներ"
179183
180184#: qml/Dash/PageHeader.qml:275
181185msgid "Remove from Favorites"
182−msgstr ""
186+msgstr "Հեռացնել ընտրյալների ցուցակից"
183187
184188#: qml/Dash/PageHeader.qml:275
185189msgid "Add to Favorites"
186−msgstr ""
190+msgstr "Ավելացնել ընտրյալների ցուցակում"
187191
188192#: qml/Dash/Previews/PreviewActionCombo.qml:35
189193msgid "More..."
190−msgstr ""
194+msgstr "Ավելին..."
191195
192196#: qml/Dash/Previews/PreviewActionCombo.qml:35
193197msgid "Less..."
194−msgstr ""
198+msgstr "Ավելի քիչ..."
195199
196200#: qml/Dash/Previews/PreviewRatingInput.qml:81
197201msgid "Rate this"
198−msgstr ""
202+msgstr "Գնահատեք սա"
199203
200204#: qml/Dash/Previews/PreviewRatingInput.qml:126
201205msgid "Add a review"
@@ -203,7 +207,7 @@ msgstr ""
203207
204208#: qml/Dash/Previews/PreviewRatingInput.qml:166
205209msgid "Send"
206−msgstr ""
210+msgstr "Ուղղարկել"
207211
208212#: qml/Dash/PullToRefreshScopeStyle.qml:55
209213msgid "Pull to refresh…"
@@ -219,23 +223,23 @@ msgstr ""
219223
220224#: qml/Dash/ScopesOverview.qml:428
221225msgid "Done"
222−msgstr ""
226+msgstr "Կատարված է"
223227
224228#: qml/Dash/ScopesOverview.qml:454
225229msgid "Store"
226−msgstr ""
230+msgstr "Խանութ"
227231
228232#: qml/Dash/ScopesOverviewTab.qml:36
229233msgid "Favorites"
230−msgstr ""
234+msgstr "Նախընտրելի"
231235
232236#: qml/Dash/ScopesOverviewTab.qml:54
233237msgid "All"
234−msgstr ""
238+msgstr "Բոլորը"
235239
236240#: qml/Greeter/Greeter.qml:157
237241msgid "Unlock"
238−msgstr ""
242+msgstr "Ապաբլոկավորել"
239243
240244#: qml/Hud/HudParametrizedActionsPage.qml:139
241245msgid "Confirm"
@@ -247,11 +251,11 @@ msgstr "Գրիր կամ ասա կոմմենտը"
247251
248252#: qml/Hud/Hud.qml:84
249253msgid "Loading. Please Wait..."
250−msgstr "Խնդրում ենք սպասել..."
254+msgstr "Բեռնվում է: Խնդրում ենք սպասել..."
251255
252256#: qml/Hud/Hud.qml:89
253257msgid "Speak Now..."
254−msgstr "Խոսի՛ր"
258+msgstr "Հիմա խոսեք..."
255259
256260#: qml/Hud/Hud.qml:94
257261msgid "Speaking..."
@@ -259,7 +263,7 @@ msgstr "Խոսում եմ..."
259263
260264#: qml/Notifications/NotificationMenuItemFactory.qml:116
261265msgid "Show password"
262−msgstr ""
266+msgstr "Ցույց տալ գաղտնաբառը"
263267
264268#: qml/Panel/ActiveCallHint.qml:77
265269msgid "Tap to return to call..."
@@ -275,11 +279,11 @@ msgstr ""
275279
276280#: qml/Panel/Indicators/MenuItemFactory.qml:748
277281msgid "In queue…"
278−msgstr ""
282+msgstr "Հերթում..."
279283
280284#: qml/Panel/Indicators/MenuItemFactory.qml:752
281285msgid "Downloading"
282−msgstr ""
286+msgstr "Ներբեռնում"
283287
284288#: qml/Panel/Indicators/MenuItemFactory.qml:754
285289msgid "Paused, tap to resume"
@@ -287,11 +291,11 @@ msgstr ""
287291
288292#: qml/Panel/Indicators/MenuItemFactory.qml:756
289293msgid "Canceled"
290−msgstr ""
294+msgstr "Չեղյալ է համարված"
291295
292296#: qml/Panel/Indicators/MenuItemFactory.qml:758
293297msgid "Finished"
294−msgstr ""
298+msgstr "Ավարտված է"
295299
296300#: qml/Panel/Indicators/MenuItemFactory.qml:760
297301msgid "Failed, tap to retry"
@@ -299,53 +303,53 @@ msgstr ""
299303
300304#: qml/Panel/Indicators/ModemInfoItem.qml:105
301305msgid "Unlock SIM"
302−msgstr ""
306+msgstr "Ապաբլոկավորել SIM-ը"
303307
304308#: qml/Panel/Indicators/RoamingIndication.qml:27
305309msgid "Roaming"
306−msgstr ""
310+msgstr "Ռոումինգ"
307311
308312#: qml/Shell.qml:346
309313msgid "Enter passphrase"
310−msgstr ""
314+msgstr "Մուտքագրե՛ք անցաբառը"
311315
312316#: qml/Shell.qml:347
313317msgid "Sorry, incorrect passphrase"
314−msgstr ""
318+msgstr "Ներեցեք, սխալ անցաբառ է"
315319
316320#: qml/Shell.qml:348
317321msgid "Please re-enter"
318−msgstr ""
322+msgstr "Խնդրում ենք նորից մուտքագրել"
319323
320324#: qml/Shell.qml:350
321325msgid "Enter passcode"
322−msgstr ""
326+msgstr "Մոտքագրեք ծածկագիրը"
323327
324328#: qml/Shell.qml:351
325329msgid "Sorry, incorrect passcode"
326−msgstr ""
330+msgstr "Ներեցեք, սխալ ծածկագիր է"
327331
328332#: qml/Shell.qml:354
329333#, qt-format
330334msgid "Enter %1"
331−msgstr ""
335+msgstr "Մուտքագրեք %1"
332336
333337#: qml/Shell.qml:355
334338#, qt-format
335339msgid "Sorry, incorrect %1"
336−msgstr ""
340+msgstr "Ներեցեք, սխալ %1"
337341
338342#: qml/Shell.qml:390
339343msgid "Sorry, incorrect passphrase."
340−msgstr ""
344+msgstr "Ներեցեք, սխալ անցաբառ է"
341345
342346#: qml/Shell.qml:391
343347msgid "Sorry, incorrect passcode."
344−msgstr ""
348+msgstr "Ներեցեք, սխալ ծածկագիր է:"
345349
346350#: qml/Shell.qml:392
347351msgid "This will be your last attempt."
348−msgstr ""
352+msgstr "Սա կլինի ձեր վերջին փորձը"
349353
350354#: qml/Shell.qml:394
351355msgid ""
qml/Components/Carousel.qml+3−2
@@ -17,7 +17,6 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import "carousel.js" as CarouselJS
20−import "Flickables" as Flickables
2120
2221/*! The Carousel component presents the items of a model in a carousel view. It's similar to a
2322 cover flow. But it stops at it's boundaries (therefore no PathView is used).
@@ -64,7 +63,7 @@ Item {
6463 and footer item to compensate that.
6564
6665 The scaling of the items is controlled by the variable continuousIndex, described below. */
67− Flickables.ListView {
66+ ListView {
6867 id: listView
6968 objectName: "listView"
7069
@@ -136,6 +135,8 @@ Item {
136135
137136 boundsBehavior: Flickable.DragOverBounds
138137 cacheBuffer: carousel.cacheBuffer
138+ flickDeceleration: Math.max(1500 * Math.pow(realWidth / referenceWidth, 1.5), 1500) // 1500 is platform default
139+ maximumFlickVelocity: Math.max(2500 * Math.pow(realWidth / referenceWidth, 1.5), 2500) // 2500 is platform default
139140 orientation: ListView.Horizontal
140141
141142 function getXFromContinuousIndex(index) {
qml/Components/Dialogs.qml+21−17
@@ -24,6 +24,10 @@ import Ubuntu.Components.Popups 0.1
2424Item {
2525 id: root
2626
27+ // Explicitly use the right domain for this widget because it might be used
28+ // in other applications like the welcome wizard.
29+ property string domain: "unity8"
30+
2731 function onPowerKeyPressed() {
2832 // FIXME: event.isAutoRepeat is always false on Nexus 4.
2933 // So we use powerKeyTimer.running to avoid the PowerOff key repeat
@@ -67,17 +71,17 @@ Item {
6771 id: logoutDialog
6872 Dialog {
6973 id: dialogueLogout
70− title: i18n.tr("Log out")
71− text: i18n.tr("Are you sure you want to log out?")
74+ title: i18n.dtr(root.domain, "Log out")
75+ text: i18n.dtr(root.domain, "Are you sure you want to log out?")
7276 Button {
73− text: i18n.tr("No")
77+ text: i18n.dtr(root.domain, "No")
7478 onClicked: {
7579 PopupUtils.close(dialogueLogout);
7680 d.dialogShown = false;
7781 }
7882 }
7983 Button {
80− text: i18n.tr("Yes")
84+ text: i18n.dtr(root.domain, "Yes")
8185 onClicked: {
8286 DBusUnitySessionService.Logout();
8387 PopupUtils.close(dialogueLogout);
@@ -91,17 +95,17 @@ Item {
9195 id: shutdownDialog
9296 Dialog {
9397 id: dialogueShutdown
94− title: i18n.tr("Shut down")
95− text: i18n.tr("Are you sure you want to shut down?")
98+ title: i18n.dtr(root.domain, "Shut down")
99+ text: i18n.dtr(root.domain, "Are you sure you want to shut down?")
96100 Button {
97− text: i18n.tr("No")
101+ text: i18n.dtr(root.domain, "No")
98102 onClicked: {
99103 PopupUtils.close(dialogueShutdown);
100104 d.dialogShown = false;
101105 }
102106 }
103107 Button {
104− text: i18n.tr("Yes")
108+ text: i18n.dtr(root.domain, "Yes")
105109 onClicked: {
106110 dBusUnitySessionServiceConnection.closeAllApps();
107111 DBusUnitySessionService.Shutdown();
@@ -116,17 +120,17 @@ Item {
116120 id: rebootDialog
117121 Dialog {
118122 id: dialogueReboot
119− title: i18n.tr("Reboot")
120− text: i18n.tr("Are you sure you want to reboot?")
123+ title: i18n.dtr(root.domain, "Reboot")
124+ text: i18n.dtr(root.domain, "Are you sure you want to reboot?")
121125 Button {
122− text: i18n.tr("No")
126+ text: i18n.dtr(root.domain, "No")
123127 onClicked: {
124128 PopupUtils.close(dialogueReboot)
125129 d.dialogShown = false;
126130 }
127131 }
128132 Button {
129− text: i18n.tr("Yes")
133+ text: i18n.dtr(root.domain, "Yes")
130134 onClicked: {
131135 dBusUnitySessionServiceConnection.closeAllApps();
132136 DBusUnitySessionService.Reboot();
@@ -141,10 +145,10 @@ Item {
141145 id: powerDialog
142146 Dialog {
143147 id: dialoguePower
144− title: i18n.tr("Power")
145− text: i18n.tr("Are you sure you would like\nto power off?")
148+ title: i18n.dtr(root.domain, "Power")
149+ text: i18n.dtr(root.domain, "Are you sure you would like\nto power off?")
146150 Button {
147− text: i18n.tr("Power off")
151+ text: i18n.dtr(root.domain, "Power off")
148152 onClicked: {
149153 dBusUnitySessionServiceConnection.closeAllApps();
150154 PopupUtils.close(dialoguePower);
@@ -154,7 +158,7 @@ Item {
154158 color: UbuntuColors.red
155159 }
156160 Button {
157− text: i18n.tr("Restart")
161+ text: i18n.dtr(root.domain, "Restart")
158162 onClicked: {
159163 dBusUnitySessionServiceConnection.closeAllApps();
160164 DBusUnitySessionService.Reboot();
@@ -164,7 +168,7 @@ Item {
164168 color: UbuntuColors.green
165169 }
166170 Button {
167− text: i18n.tr("Cancel")
171+ text: i18n.dtr(root.domain, "Cancel")
168172 onClicked: {
169173 PopupUtils.close(dialoguePower);
170174 d.dialogShown = false;
qml/Components/DragHandle.qml+6−1
@@ -68,6 +68,7 @@ EdgeDragArea {
6868 }
6969
7070 property real hintDisplacement: 0
71+ property var overrideStartValue: undefined
7172 SmoothedAnimation {
7273 id: hintingAnimation
7374 target: hintingAnimation
@@ -196,7 +197,11 @@ EdgeDragArea {
196197 } else /* Undecided || Recognized */ {
197198 if (d.previousStatus === DirectionalDragArea.WaitingForTouch) {
198199 dragEvaluator.reset();
199− d.startValue = parent[d.targetProp];
200+ if (overrideStartValue !== undefined) {
201+ d.startValue = overrideStartValue;
202+ } else {
203+ d.startValue = parent[d.targetProp];
204+ }
200205
201206 if (hintDisplacement > 0) {
202207 hintingAnimation.targetValue = d.startValue;
qml/Components/EdgeDemo.qml+13−10
@@ -23,7 +23,7 @@ Item {
2323
2424 property Item greeter
2525 property Item launcher
26− property Item indicators
26+ property Item panel
2727 property Item stages
2828
2929 property bool launcherEnabled: true
@@ -139,11 +139,11 @@ Item {
139139 function startTopEdgeDemo() {
140140 demo.panelEnabled = true;
141141 if (demo.stages) {
142− d.topEdgeDemo = d.overlay.createObject(demo.stages, {
142+ d.topEdgeDemo = d.overlay.createObject(demo.panel, {
143143 "edge": "top",
144144 "title": i18n.tr("Top edge"),
145145 "text": i18n.tr("Try swiping from the top edge to access the indicators"),
146− "anchors.fill": demo.stages,
146+ "anchors.fill": demo.panel,
147147 });
148148 }
149149 if (d.topEdgeDemo) {
@@ -154,9 +154,9 @@ Item {
154154 }
155155
156156 Connections {
157− target: demo.indicators
157+ target: demo.panel.indicators
158158 onFullyOpenedChanged: {
159− if (d.topEdgeDemo && d.topEdgeDemo.available && demo.indicators.fullyOpened) {
159+ if (d.topEdgeDemo && d.topEdgeDemo.available && demo.panel.indicators.fullyOpened) {
160160 d.topEdgeDemo.hideNow()
161161 startBottomEdgeDemo()
162162 }
@@ -164,12 +164,12 @@ Item {
164164 }
165165
166166 function startBottomEdgeDemo() {
167− if (demo.indicators) {
168− d.bottomEdgeDemo = d.overlay.createObject(demo.indicators, {
167+ if (demo.panel.indicators) {
168+ d.bottomEdgeDemo = d.overlay.createObject(demo.panel.indicators, {
169169 "edge": "bottom",
170170 "title": i18n.tr("Close"),
171171 "text": i18n.tr("Swipe up again to close the settings screen"),
172− "anchors.fill": demo.indicators.content,
172+ "anchors.fill": demo.panel.indicators,
173173 });
174174 }
175175 if (d.bottomEdgeDemo) {
@@ -180,9 +180,12 @@ Item {
180180 }
181181
182182 Connections {
183− target: demo.indicators
183+ target: demo.panel.indicators
184184 onPartiallyOpenedChanged: {
185− if (d.bottomEdgeDemo && d.bottomEdgeDemo.available && !demo.indicators.partiallyOpened && !demo.indicators.fullyOpened) {
185+ if (d.bottomEdgeDemo &&
186+ d.bottomEdgeDemo.available &&
187+ !demo.panel.indicators.partiallyOpened &&
188+ !demo.panel.indicators.fullyOpened) {
186189 d.bottomEdgeDemo.hideNow()
187190 startLeftEdgeDemo()
188191 }
qml/Components/Flickables/GridView.qmldeleted−25
@@ -1,25 +0,0 @@
1−/*
2− * Copyright (C) 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.3 as QtQuick
18−import Ubuntu.Components 1.1
19−
20−QtQuick.GridView {
21− // Attached components and usages like GridView.onRemove are known not to work
22− // please use GridView directly from QtQuick if needed
23− flickDeceleration: 1500 * units.gridUnit / 8
24− maximumFlickVelocity: 2500 * units.gridUnit / 8
25−}
qml/Components/Flickables/ListView.qmldeleted−25
@@ -1,25 +0,0 @@
1−/*
2− * Copyright (C) 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.3 as QtQuick
18−import Ubuntu.Components 1.1
19−
20−QtQuick.ListView {
21− // Attached components and usages like ListView.onRemove are known not to work
22− // please use ListView directly from QtQuick if needed
23− flickDeceleration: 1500 * units.gridUnit / 8
24− maximumFlickVelocity: 2500 * units.gridUnit / 8
25−}
qml/Components/Flickables/ListViewWithPageHeader.qmldeleted−23
@@ -1,23 +0,0 @@
1−/*
2− * Copyright (C) 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 Dash 0.1
18−import Ubuntu.Components 1.1
19−
20−ListViewWithPageHeader {
21− flickDeceleration: 1500 * units.gridUnit / 8
22− maximumFlickVelocity: 2500 * units.gridUnit / 8
23−}
qml/Components/ListItems/VerticalThinDivider.qmldeleted−26
@@ -1,26 +0,0 @@
1−/*
2− * Copyright (C) 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 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−
19−Image {
20− anchors {
21− top: (parent) ? parent.top : null
22− bottom: (parent) ? parent.bottom : null
23− }
24− width: (visible) ? units.dp(2) : 0
25− source: "graphics/ListItemDividerVertical.png"
26−}
qml/Components/ListItems/graphics/ListItemDividerVertical@18.pngdeleted

Binary file not shown.

qml/Components/Lockscreen.qml+4−1
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 1.0
1919import Ubuntu.Components.Popups 1.0
20+import Ubuntu.Telephony 0.1 as Telephony
2021
2122Showable {
2223 id: root
@@ -56,6 +57,8 @@ Showable {
5657
5758 property url background: ""
5859
60+ readonly property string passphrase: (pinPadLoader.item && pinPadLoader.item.passphrase) ? pinPadLoader.item.passphrase : ""
61+
5962 signal entered(string passphrase)
6063 signal cancel()
6164 signal emergencyCall()
@@ -217,7 +220,7 @@ Showable {
217220 objectName: "emergencyCallLabel"
218221 anchors.horizontalCenter: parent.horizontalCenter
219222
220− text: i18n.tr("Emergency Call")
223+ text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency Call")
221224 color: "#f3f3e7"
222225 }
223226
qml/Components/PassphraseLockscreen.qml+2
@@ -28,6 +28,8 @@ Item {
2828 property string errorText
2929 property bool entryEnabled: true
3030
31+ readonly property string passphrase: pinentryField.text
32+
3133 signal entered(string passphrase)
3234 signal cancel()
3335
qml/Components/PinLockscreen.qml+2
@@ -35,6 +35,8 @@ Column {
3535 property int maxPinLength: -1
3636 property bool showCancelButton: true
3737
38+ readonly property string passphrase: pinentryField.text
39+
3840 signal entered(string passphrase)
3941 signal cancel()
4042
qml/Components/ResponsiveGridView.qml+1−2
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.3
1818import Ubuntu.Components 0.1
19−import "Flickables" as Flickables
2019
2120/*
2221 Essentially a GridView where you can specify the maximum number of columns it can have.
@@ -56,7 +55,7 @@ Item {
5655 return rows * height
5756 }
5857
59− Flickables.GridView {
58+ GridView {
6059 id: gridView
6160 objectName: "responsiveGridViewGrid"
6261 anchors {
qml/Components/ScrollCalculator.qmlnew file+81
@@ -0,0 +1,81 @@
1+/*
2+ * Copyright (C) 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.2
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: scrollArea
22+
23+ readonly property bool areaActive: lateralPosition >= 0
24+ property real stopScrollThreshold: units.gu(2)
25+ property int direction: Qt.LeftToRight
26+ property real baseScrollAmount: units.dp(3)
27+ property real maximumScrollAmount: units.dp(8)
28+ property real lateralPosition: -1
29+ property real forceScrollingPercentage: 0.4
30+
31+ signal scroll(real scrollAmount)
32+
33+ width: units.gu(5)
34+ rotation: direction === Qt.LeftToRight ? 0 : 180
35+
36+ onAreaActiveChanged: areaActive ? handleEnter() : handleExit()
37+
38+ function handleEnter() {
39+ d.thresholdAreaX = -scrollArea.stopScrollThreshold;
40+ scrollTimer.restart();
41+ }
42+
43+ function handleExit() {
44+ d.thresholdAreaX = -scrollArea.stopScrollThreshold;
45+ scrollTimer.stop();
46+ }
47+
48+ onLateralPositionChanged: {
49+ if (scrollArea.areaActive) {
50+ if (lateralPosition > width * (1 - forceScrollingPercentage)) {
51+ d.thresholdAreaX = width * (1 - forceScrollingPercentage);
52+ if (!scrollTimer.running) scrollTimer.restart();
53+ } else if (lateralPosition > d.thresholdAreaX + scrollArea.stopScrollThreshold) {
54+ d.thresholdAreaX = lateralPosition - scrollArea.stopScrollThreshold;
55+ if (!scrollTimer.running) scrollTimer.restart();
56+ } else if (lateralPosition < d.thresholdAreaX) {
57+ d.thresholdAreaX = lateralPosition;
58+ scrollTimer.stop();
59+ }
60+
61+ d.progression = lateralPosition / width;
62+ }
63+ }
64+
65+ Timer {
66+ id: scrollTimer
67+ interval: 16
68+ repeat: true
69+
70+ onTriggered: {
71+ var scrollAmount = scrollArea.baseScrollAmount + scrollArea.maximumScrollAmount * d.progression;
72+ scrollArea.scroll(scrollAmount);
73+ }
74+ }
75+
76+ QtObject {
77+ id: d
78+ property real progression: 0
79+ property real thresholdAreaX: -scrollArea.stopScrollThreshold
80+ }
81+}
qml/Components/ZoomableImage.qml+19−16
@@ -16,7 +16,7 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19−import "Flickables" as Flickables
19+import "../Components"
2020
2121/*! \brief Zoomable for image.
2222
@@ -26,16 +26,15 @@ import "Flickables" as Flickables
2626
2727Item {
2828 id: root
29− property alias source: lazyImage.source
29+ property alias source: imageRenderer.source
3030 property var zoomable: false
31− property alias imageState: lazyImage.state
32− property alias scaleTo: lazyImage.scaleTo
33− property alias asynchronous: lazyImage.asynchronous
31+ property alias imageStatus: imageRenderer.status
32+ property alias asynchronous: imageRenderer.asynchronous
3433
35− Flickables.Flickable {
34+ Flickable {
3635 id: flickable
3736 objectName: "flickable"
38− clip: true
37+ clip: true // FIXME maybe we can remove this, or just not clip in few cases
3938 contentHeight: imageContainer.height
4039 contentWidth: imageContainer.width
4140
@@ -52,30 +51,34 @@ Item {
5251 Item {
5352 id: image
5453 objectName: "image"
55− property alias imageState: lazyImage.state
54+ property alias imageStatus: imageRenderer.status
5655 property var prevScale
5756 anchors.centerIn: parent
5857
5958 signal imageReloaded
6059
61− LazyImage {
62− id: lazyImage
63− objectName: "lazyImage"
60+ Image {
61+ id: imageRenderer
62+ objectName: "imageRenderer"
6463 smooth: !flickable.movingVertically
6564 anchors.fill: parent
6665 fillMode: Image.PreserveAspectFit
67− scaleTo: "fit"
6866
69− onStateChanged: {
70− if (state == "ready") {
67+ readonly property int sourceSizeMultiplier: 3
68+
69+ sourceSize.width: root.width * sourceSizeMultiplier <= root.height * sourceSizeMultiplier ? root.width * sourceSizeMultiplier : 0
70+ sourceSize.height: root.height * sourceSizeMultiplier <= root.width * sourceSizeMultiplier ? root.height * sourceSizeMultiplier : 0
71+
72+ onStatusChanged: {
73+ if (status === Image.Ready) {
7174 image.imageReloaded();
7275 }
7376 }
7477 }
7578
7679 onImageReloaded: {
77− image.height = lazyImage.sourceImage.implicitHeight
78− image.width = lazyImage.sourceImage.implicitWidth
80+ image.height = imageRenderer.implicitHeight
81+ image.width = imageRenderer.implicitWidth
7982 image.resetScale();
8083 }
8184
qml/Dash/CardHorizontalList.qml+1−2
@@ -17,7 +17,6 @@
1717import QtQuick 2.2
1818import Ubuntu.Components 1.1
1919import "../Components"
20−import "../Components/Flickables" as Flickables
2120
2221DashRenderer {
2322 id: root
@@ -26,7 +25,7 @@ DashRenderer {
2625 collapsedHeight: expandedHeight
2726 clip: true
2827
29− Flickables.ListView {
28+ ListView {
3029 id: listView
3130 anchors {
3231 fill: parent
qml/Dash/CardVerticalJournal.qml+17
@@ -33,6 +33,23 @@ DashRenderer {
3333 // in case the model is non empty
3434 readonly property double minHeight: root.model.count >= 1 ? cardVerticalJournal.rowSpacing + 1 : 0
3535
36+ function clearView() {
37+ cardVerticalJournal.model = null;
38+ cardVerticalJournal.model = root.model;
39+ }
40+
41+ // Clear the view if the cardTool changes its sizes
42+ // means it is still settling. This is
43+ // necessary because the VerticalJournal does
44+ // not support its elements changing height so
45+ // we need to construct all the items with the settled
46+ // size of the cardTool
47+ Connections {
48+ target: cardTool
49+ onArtShapeSizeChanged: root.clearView();
50+ onHeaderHeightChanged: root.clearView();
51+ }
52+
3653 ResponsiveVerticalJournal {
3754 id: cardVerticalJournal
3855
qml/Dash/Dash.qml+1−9
@@ -28,14 +28,12 @@ Showable {
2828
2929 visible: shown
3030
31− property string showScopeOnLoaded: "clickscope"
32−
3331 DashCommunicatorService {
3432 objectName: "dashCommunicatorService"
3533 onSetCurrentScopeRequested: {
3634 if (!isSwipe || !window.active || overviewController.progress != 0) {
3735 if (overviewController.progress != 0 && window.active) animate = false;
38− dash.setCurrentScope(scopeId, animate, isSwipe)
36+ dashContent.setCurrentScopeAtIndex(index, animate, isSwipe)
3937 if (overviewController.progress != 0) {
4038 if (window.active) {
4139 dashContentCache.scheduleUpdate();
@@ -184,12 +182,6 @@ Showable {
184182 scopesOverview.ensureAllScopeVisible(scope.id);
185183 x = -width;
186184 }
187− onScopeLoaded: {
188− if (scopeId == dash.showScopeOnLoaded) {
189− dash.setCurrentScope(scopeId, false, false)
190− dash.showScopeOnLoaded = ""
191− }
192− }
193185 clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
194186 Behavior on x {
195187 UbuntuNumberAnimation {
qml/Dash/DashContent.qml+4−3
@@ -19,7 +19,6 @@ import Ubuntu.Components 0.1
1919import Unity 0.2
2020import Utils 0.1
2121import "../Components"
22−import "../Components/Flickables" as Flickables
2322
2423Item {
2524 id: dashContent
@@ -99,7 +98,7 @@ Item {
9998 anchors.fill: parent
10099 }
101100
102− Flickables.ListView {
101+ ListView {
103102 id: dashContentList
104103 objectName: "dashContentList"
105104
@@ -108,6 +107,8 @@ Item {
108107 anchors.fill: parent
109108 orientation: ListView.Horizontal
110109 boundsBehavior: Flickable.DragAndOvershootBounds
110+ flickDeceleration: units.gu(625)
111+ maximumFlickVelocity: width * 5
111112 snapMode: ListView.SnapOneItem
112113 highlightMoveDuration: 250
113114 highlightRangeMode: ListView.StrictlyEnforceRange
@@ -160,7 +161,7 @@ Item {
160161 }
161162 asynchronous: true
162163 source: "GenericScopeView.qml"
163− objectName: scope.id + " loader"
164+ objectName: "scopeLoader" + index
164165
165166 readonly property bool moving: item ? item.moving : false
166167 readonly property bool navigationShown: item ? item.navigationShown : false
qml/Dash/DashNavigationButton.qml+1−2
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.2
1818import Ubuntu.Components 1.1
19−import "../Components/Flickables" as Flickables
2019
2120AbstractButton {
2221 id: root
@@ -76,7 +75,7 @@ AbstractButton {
7675 }
7776
7877 // navigationListView is outside root
79− Flickables.ListView {
78+ ListView {
8079 id: navigationListView
8180 objectName: "navigationListView"
8281 visible: height > 0
qml/Dash/DashNavigationList.qml+1−2
@@ -17,7 +17,6 @@
1717import QtQuick 2.2
1818import Ubuntu.Components 1.1
1919import "../Components"
20−import "../Components/Flickables" as Flickables
2120
2221Item {
2322 id: root
@@ -46,7 +45,7 @@ Item {
4645 }
4746 }
4847
49− Flickables.Flickable {
48+ Flickable {
5049 id: flickable
5150
5251 anchors.fill: parent
qml/Dash/PageHeader.qml+1−2
@@ -21,7 +21,6 @@ import Ubuntu.Components.Popups 1.0
2121import Ubuntu.Components.ListItems 1.0
2222import "../Components"
2323import "../Components/SearchHistoryModel"
24−import "../Components/Flickables" as Flickables
2524
2625Item {
2726 id: root
@@ -128,7 +127,7 @@ Item {
128127 }
129128 }
130129
131− Flickables.Flickable {
130+ Flickable {
132131 id: headerContainer
133132 objectName: "headerContainer"
134133 clip: contentY < height
qml/Dash/PreviewListView.qml+3−2
@@ -18,7 +18,6 @@ import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.2
2020import "../Components"
21−import "../Components/Flickables" as Flickables
2221import "Previews" as Previews
2322
2423Item {
@@ -53,7 +52,7 @@ Item {
5352 onBackClicked: root.backClicked()
5453 }
5554
56− Flickables.ListView {
55+ ListView {
5756 id: previewListView
5857 objectName: "listView"
5958 anchors {
@@ -67,6 +66,8 @@ Item {
6766 snapMode: ListView.SnapOneItem
6867 boundsBehavior: Flickable.DragAndOvershootBounds
6968 highlightMoveDuration: 250
69+ flickDeceleration: units.gu(625)
70+ maximumFlickVelocity: width * 5
7071 cacheBuffer: 0
7172
7273 property bool open: false
qml/Dash/Previews/Preview.qml+1−2
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19−import "../../Components/Flickables" as Flickables
2019
2120/*! \brief This component constructs the Preview UI.
2221 *
@@ -64,7 +63,7 @@ Item {
6463 Repeater {
6564 model: previewModel
6665
67− delegate: Flickables.ListView {
66+ delegate: ListView {
6867 id: column
6968 anchors { top: parent.top; bottom: parent.bottom }
7069 width: row.columnWidth
qml/Dash/Previews/PreviewImageGallery.qml+14−82
@@ -17,7 +17,6 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 1.1
1919import "../../Components"
20−import "../../Components/Flickables" as Flickables
2120
2221/*! This preview widget shows a horizontal list of images.
2322 * The URIs for the images should be an array in widgetData["sources"].
@@ -29,7 +28,7 @@ PreviewWidget {
2928
3029 property Item rootItem: QuickUtils.rootItem(root)
3130
32− Flickables.ListView {
31+ ListView {
3332 id: previewImageListView
3433 objectName: "previewImageListView"
3534 spacing: units.gu(1)
@@ -66,46 +65,26 @@ PreviewWidget {
6665 id: mouseArea
6766 anchors.fill: parent
6867 onClicked: {
69− slideShowListView.currentIndex = index;
70− slideShow.initialX = rootItem.mapFromItem(parent, 0, 0).x
71− slideShow.initialY = rootItem.mapFromItem(parent, 0, 0).y
72− slideShow.visible = true;
68+ overlay.delegateItem.currentIndex = index;
69+ overlay.initialX = rootItem.mapFromItem(parent, 0, 0).x;
70+ overlay.initialY = rootItem.mapFromItem(parent, 0, 0).y;
71+ overlay.show();
7372 }
7473 }
7574 }
7675 }
7776
78− Rectangle {
79− id: slideShow
80− objectName: "slideShow"
81−
82− readonly property real initialScale: previewImageListView.height / rootItem.height
83− readonly property real scaleProgress: (scale - initialScale) / (1.0 - initialScale)
84− property real initialX: 0
85− property real initialY: 0
86−
77+ PreviewOverlay {
78+ id: overlay
79+ objectName: "overlay"
8780 parent: rootItem
8881 width: parent.width
8982 height: parent.height
90− visible: false
91− clip: visible && scale < 1.0
92− scale: visible ? 1.0 : initialScale
93− transformOrigin: Item.TopLeft
94− transform: Translate {
95− x: slideShow.initialX - slideShow.initialX * slideShow.scaleProgress
96− y: slideShow.initialY - slideShow.initialY * slideShow.scaleProgress
97− }
98− color: "black"
99− radius: units.gu(1) - units.gu(1) * slideShow.scaleProgress
83+ initialScale: previewImageListView.height / rootItem.height
10084
101− Behavior on scale {
102− enabled: !slideShow.visible
103− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
104− }
105−
106− Flickables.ListView {
107− id: slideShowListView
108− objectName: "slideShowListView"
85+ delegate: ListView {
86+ id: overlayListView
87+ objectName: "overlayListView"
10988 anchors.fill: parent
11089 orientation: ListView.Horizontal
11190 highlightRangeMode: ListView.StrictlyEnforceRange
@@ -120,7 +99,7 @@ PreviewWidget {
12099 top: parent.top
121100 bottom: parent.bottom
122101 }
123− width: slideShow.width
102+ width: overlay.width
124103 source: modelData ? modelData : ""
125104 fillMode: Image.PreserveAspectFit
126105 sourceSize { width: screenshot.width; height: screenshot.height }
@@ -128,54 +107,7 @@ PreviewWidget {
128107
129108 MouseArea {
130109 anchors.fill: parent
131− onClicked: slideShowHeader.shown = !slideShowHeader.shown
132− }
133− }
134−
135− Rectangle {
136− id: slideShowHeader
137−
138− property bool shown: true
139−
140− anchors {
141− left: parent.left
142− right: parent.right
143− }
144− height: units.gu(7)
145− visible: opacity > 0
146− opacity: slideShow.scaleProgress > 0.6 && shown ? 0.8 : 0
147− color: "black"
148−
149− Behavior on opacity {
150− UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
151− }
152−
153− AbstractButton {
154− id: slideShowCloseButton
155− objectName: "slideShowCloseButton"
156− anchors {
157− top: parent.top
158− bottom: parent.bottom
159− }
160− width: units.gu(8)
161− height: width
162−
163− onClicked: slideShow.visible = false
164−
165− Rectangle {
166− anchors.fill: parent
167− color: Qt.rgba(1.0, 1.0, 1.0, 0.3)
168− visible: slideShowCloseButton.pressed
169− }
170−
171− Icon {
172− id: icon
173− anchors.centerIn: parent
174− width: units.gu(2.5)
175− height: width
176− color: Theme.palette.normal.foregroundText
177− name: "close"
178− }
110+ onClicked: overlay.headerShown = !overlay.headerShown
179111 }
180112 }
181113 }
qml/Dash/Previews/PreviewOverlay.qmlnew file+115
@@ -0,0 +1,115 @@
1+/*
2+ * Copyright (C) 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 Ubuntu.Components 1.1
19+import "../../Components"
20+
21+Rectangle {
22+ id: overlay
23+ objectName: "overlay"
24+
25+ readonly property real scaleProgress: (scale - initialScale) / (1.0 - initialScale)
26+
27+ property alias delegate: loader.sourceComponent
28+ property alias delegateItem: loader.item
29+ property alias headerShown: overlayHeader.shown
30+ property bool shown: false
31+ property bool opening: false
32+ property real initialX: 0
33+ property real initialY: 0
34+ property real initialScale: 0
35+
36+ visible: scale > initialScale
37+ clip: visible && scale < 1.0
38+ scale: shown ? 1.0 : initialScale
39+ transformOrigin: Item.TopLeft
40+ transform: Translate {
41+ x: overlay.initialX - overlay.initialX * overlay.scaleProgress
42+ y: overlay.initialY - overlay.initialY * overlay.scaleProgress
43+ }
44+ color: Qt.rgba(0, 0, 0, scaleProgress)
45+ radius: units.gu(1) - units.gu(1) * scaleProgress
46+
47+ function show() {
48+ opening = true;
49+ shown = true;
50+ }
51+
52+ function hide() {
53+ opening = false;
54+ shown = false;
55+ }
56+
57+ Behavior on scale {
58+ UbuntuNumberAnimation {
59+ duration: overlay.opening ? UbuntuAnimation.FastDuration :
60+ UbuntuAnimation.FastDuration / 2
61+ }
62+ }
63+
64+ Loader {
65+ id: loader
66+ anchors.fill: parent
67+ }
68+
69+ Rectangle {
70+ id: overlayHeader
71+
72+ property bool shown: true
73+
74+ anchors {
75+ left: parent.left
76+ right: parent.right
77+ }
78+ height: units.gu(7)
79+ visible: opacity > 0
80+ opacity: overlay.scaleProgress > 0.6 && shown ? 0.8 : 0
81+ color: "black"
82+
83+ Behavior on opacity {
84+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
85+ }
86+
87+ AbstractButton {
88+ id: overlayCloseButton
89+ objectName: "overlayCloseButton"
90+ anchors {
91+ top: parent.top
92+ bottom: parent.bottom
93+ }
94+ width: units.gu(8)
95+ height: width
96+
97+ onClicked: overlay.hide()
98+
99+ Rectangle {
100+ anchors.fill: parent
101+ color: Qt.rgba(1.0, 1.0, 1.0, 0.3)
102+ visible: overlayCloseButton.pressed
103+ }
104+
105+ Icon {
106+ id: icon
107+ anchors.centerIn: parent
108+ width: units.gu(2.5)
109+ height: width
110+ color: Theme.palette.normal.foregroundText
111+ name: "close"
112+ }
113+ }
114+ }
115+}
qml/Dash/Previews/PreviewZoomableImage.qml+38−5
@@ -28,11 +28,44 @@ PreviewWidget {
2828 id: root
2929 implicitHeight: units.gu(22)
3030
31− ZoomableImage {
32− id: image
33− objectName: "image"
31+ property Item rootItem: QuickUtils.rootItem(root)
32+
33+ LazyImage {
34+ id: lazyImage
35+ objectName: "lazyImage"
36+ anchors {
37+ top: parent.top
38+ bottom: parent.bottom
39+ horizontalCenter: parent.horizontalCenter
40+ }
41+ scaleTo: "height"
3442 source: widgetData["source"]
35− zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
36− anchors.fill: parent
43+
44+ borderSource: mouseArea.pressed ? "radius_pressed.sci" : "radius_idle.sci"
45+
46+ MouseArea {
47+ id: mouseArea
48+ anchors.fill: parent
49+ onClicked: {
50+ overlay.initialX = rootItem.mapFromItem(parent, 0, 0).x;
51+ overlay.initialY = rootItem.mapFromItem(parent, 0, 0).y;
52+ overlay.show();
53+ }
54+ }
55+ }
56+
57+ PreviewOverlay {
58+ id: overlay
59+ objectName: "overlay"
60+ parent: rootItem
61+ width: parent.width
62+ height: parent.height
63+ initialScale: lazyImage.height / rootItem.height
64+
65+ delegate: ZoomableImage {
66+ anchors.fill: parent
67+ source: widgetData["source"]
68+ zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
69+ }
3770 }
3871}
qml/Dash/ScopeListView.qml+6−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2013 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,8 +14,11 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import "../Components/Flickables" as Flickables
17+import QtQuick 2.0
18+import Dash 0.1
1819
19−Flickables.ListViewWithPageHeader {
20+ListViewWithPageHeader {
21+ maximumFlickVelocity: height * 10
22+ flickDeceleration: height * 2
2023 cacheBuffer: Number.MAX_VALUE
2124}
qml/Dash/ScopeSettingsPage.qml+1−2
@@ -18,7 +18,6 @@ import QtQuick 2.2
1818import Ubuntu.Components 1.1
1919import Unity 0.2
2020import "../Components"
21−import "../Components/Flickables" as Flickables
2221import "ScopeSettings"
2322
2423Item {
@@ -40,7 +39,7 @@ Item {
4039 onBackClicked: root.backClicked()
4140 }
4241
43− Flickables.ListView {
42+ ListView {
4443 id: scopeSettings
4544 objectName: "scopeSettings"
4645 anchors {
qml/Dash/ScopesOverviewAll.qml+1−2
@@ -16,9 +16,8 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19−import "../Components/Flickables" as Flickables
2019
21−Flickables.Flickable {
20+Flickable {
2221 id: root
2322
2423 property alias model: cardGrid.model
qml/Dash/ScopesOverviewFavorites.qml+2−4
@@ -14,11 +14,9 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.3
18−import Ubuntu.Components 1.1
19−import "../Components/Flickables" as Flickables
17+import QtQuick 2.0
2018
21−Flickables.Flickable {
19+Flickable {
2220 id: root
2321
2422 signal clicked(int index, var result, var itemModel)
qml/Greeter/Clock.qml+9−9
@@ -34,21 +34,21 @@ Item {
3434 }
3535 }
3636
37− CachedUnityMenuModel {
37+ Indicators.SharedUnityMenuModel {
3838 id: timeModel
3939 objectName: "timeModel"
4040
4141 busName: "com.canonical.indicator.datetime"
42− actionsObjectPath: "/com/canonical/indicator/datetime"
42+ actions: { "indicator": "/com/canonical/indicator/datetime" }
4343 menuObjectPath: clock.visible ? "/com/canonical/indicator/datetime/phone" : ""
44+ }
4445
45− Indicators.RootActionState {
46− menu: timeModel.model
47− onUpdated: {
48− if (timeLabel.text != rightLabel) {
49− timeLabel.text = rightLabel;
50− clock.currentDate = new Date();
51− }
46+ Indicators.RootActionState {
47+ menu: timeModel.model
48+ onUpdated: {
49+ if (timeLabel.text != rightLabel) {
50+ if (rightLabel != "") timeLabel.text = rightLabel;
51+ clock.currentDate = new Date();
5252 }
5353 }
5454 }
qml/Greeter/Greeter.qml+5
@@ -129,6 +129,11 @@ Showable {
129129 direction: Direction.Leftwards
130130 }
131131 }
132+ TouchGate {
133+ targetItem: dragHandle
134+ anchors.fill: targetItem
135+ enabled: targetITem.enabled
136+ }
132137
133138 Loader {
134139 id: greeterContentLoader
qml/Greeter/LoginList.qml+2−2
@@ -18,7 +18,6 @@ import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import LightDM 0.1 as LightDM
2020import "../Components"
21−import "../Components/Flickables" as Flickables
2221
2322Item {
2423 id: root
@@ -69,7 +68,7 @@ Item {
6968 antialiasing: true
7069 }
7170
72− Flickables.ListView {
71+ ListView {
7372 id: userList
7473 objectName: "userList"
7574
@@ -79,6 +78,7 @@ Item {
7978 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
8079 highlightRangeMode: ListView.StrictlyEnforceRange
8180 highlightMoveDuration: root.moveDuration
81+ flickDeceleration: 10000
8282
8383 readonly property bool movingInternally: moveTimer.running || userList.moving
8484
qml/Hud/Hud.qml+1−2
@@ -19,7 +19,6 @@ import Ubuntu.Components 0.1
1919import HudClient 0.1
2020
2121import "../Components"
22−import "../Components/Flickables" as Flickables
2322
2423Item {
2524 id: hud
@@ -401,7 +400,7 @@ Item {
401400 }
402401 progress: MathUtils.clamp((y - hudShowable.y + height - units.gu(8))/elementsYSliding, 0, 1)
403402
404− Flickables.Flickable {
403+ Flickable {
405404 anchors.left: parent.left
406405 anchors.right: parent.right
407406 contentHeight: resultList.height
qml/Hud/HudParametrizedActionsPage.qml+1−2
@@ -18,7 +18,6 @@ import QtQuick 2.0
1818import Ubuntu.Components 1.1
1919import Ubuntu.Components.Themes.Ambiance 1.1
2020import "../Components"
21−import "../Components/Flickables" as Flickables
2221
2322Item {
2423 property alias header: header.title
@@ -75,7 +74,7 @@ Item {
7574 return values
7675 }
7776
78− Flickables.Flickable {
77+ Flickable {
7978 anchors.top: parent.top
8079 anchors.bottom: buttons.top
8180 anchors.left: parent.left
qml/Launcher/Launcher.qml+1−1
@@ -64,7 +64,7 @@ Item {
6464 }
6565
6666 function tease() {
67− if (available) {
67+ if (available && !dragArea.dragging) {
6868 teaseTimer.start();
6969 }
7070 }
qml/Launcher/LauncherDelegate.qml+2−19
@@ -27,7 +27,6 @@ Item {
2727 property bool itemFocused: false
2828 property real maxAngle: 0
2929 property bool inverted: false
30− property alias pinned: pin.visible
3130
3231 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
3332 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -71,31 +70,15 @@ Item {
7170 height: root.itemHeight - units.gu(1)
7271 }
7372
74− Rectangle {
75− id: pin
76− anchors {
77− left: iconShape.left
78− top: iconShape.top
79− topMargin: -units.dp(2)
80− }
81− width: units.gu(1)
82− height: width
83− radius: width / 2
84− color: "white"
85− }
86−
8773 UbuntuShape {
8874 id: countEmblem
8975 objectName: "countEmblem"
90−
91− readonly property real pinMargin: pin.visible ? pin.width + units.gu(1) - anchors.leftMargin : 0
92−
9376 anchors {
9477 right: parent.right
9578 top: parent.top
9679 margins: units.dp(3)
9780 }
98− width: Math.min(root.itemWidth - pinMargin, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81+ width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
9982 height: units.gu(2)
10083 color: UbuntuColors.orange
10184 visible: root.countVisible
@@ -109,7 +92,7 @@ Item {
10992 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
11093 // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
11194 anchors.verticalCenterOffset: -units.dp(.5)
112− width: root.itemWidth - units.gu(1) - countEmblem.pinMargin
95+ width: root.itemWidth - units.gu(1)
11396 horizontalAlignment: Text.AlignHCenter
11497 elide: Text.ElideRight
11598 color: "white"
qml/Launcher/LauncherPanel.qml+1−7
@@ -21,7 +21,6 @@ import Unity.Launcher 0.1
2121import Ubuntu.Components.Popups 0.1
2222import "../Components/ListItems"
2323import "../Components/"
24−import "../Components/Flickables" as Flickables
2524
2625Rectangle {
2726 id: root
@@ -76,7 +75,7 @@ Rectangle {
7675 anchors.fill: parent
7776 clip: true
7877
79− Flickables.ListView {
78+ ListView {
8079 id: launcherListView
8180 objectName: "launcherListView"
8281 anchors {
@@ -151,7 +150,6 @@ Rectangle {
151150 count: model.count
152151 countVisible: model.countVisible
153152 progress: model.progress
154− pinned: model.pinned
155153 itemFocused: model.focused
156154 inverted: root.inverted
157155 z: -Math.abs(offset)
@@ -462,10 +460,6 @@ Rectangle {
462460 width: itemWidth
463461 rotation: root.rotation
464462 itemOpacity: 0.9
465− pinned: dndArea.draggedIndex > -1 &&
466− LauncherModel.get(dndArea.draggedIndex).pinned &&
467− !dndArea.preDragging &&
468− !dndArea.dragging
469463
470464 function flatten() {
471465 fakeDragItemAnimation.start();
qml/Notifications/Notification.qml+73−10
@@ -20,7 +20,6 @@ import Ubuntu.Components 1.1
2020import Unity.Notifications 1.0
2121import QMenuModel 0.1
2222import Utils 0.1
23−import "../Components/Flickables" as Flickables
2423
2524import Ubuntu.Components.ListItems 0.1 as ListItem
2625
@@ -31,6 +30,7 @@ Item {
3130 property alias secondaryIconSource: secondaryIcon.source
3231 property alias summary: summaryLabel.text
3332 property alias body: bodyLabel.text
33+ property alias value: valueIndicator.value
3434 property var actions
3535 property var notificationId
3636 property var type
@@ -40,6 +40,7 @@ Item {
4040 property bool fullscreen: false
4141 property int maxHeight
4242 property int margins
43+ readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
4344 readonly property color red: "#fc4949"
4445 readonly property color green: "#3fb24f"
4546 readonly property color sdLightGrey: "#eaeaea"
@@ -48,9 +49,9 @@ Item {
4849 readonly property real contentSpacing: units.gu(2)
4950
5051 objectName: "background"
51− implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height + contentSpacing * 2) : 0
52+ implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
5253
53− color: type == Notification.SnapDecision ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
54+ color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
5455 opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
5556
5657 state: {
@@ -78,6 +79,7 @@ Item {
7879 Audio {
7980 id: sound
8081 objectName: "sound"
82+ audioRole: MediaPlayer.alert
8183 source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
8284 }
8385
@@ -88,6 +90,12 @@ Item {
8890 }
8991 }
9092
93+ onHintsChanged: {
94+ if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
95+ sound.play();
96+ }
97+ }
98+
9199 Behavior on height {
92100 id: normalHeightBehavior
93101
@@ -126,6 +134,7 @@ Item {
126134 fill: parent
127135 leftMargin: notification.margins
128136 rightMargin: notification.margins
137+ topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
129138 }
130139 color: parent.color
131140 opacity: parent.opacity
@@ -206,10 +215,10 @@ Item {
206215 right: parent.right
207216 top: parent.top
208217 margins: 0
209− topMargin: fullscreen ? 0 : units.gu(2)
218+ topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
210219 }
211220
212− spacing: units.gu(2)
221+ spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
213222
214223 Row {
215224 id: topRow
@@ -228,7 +237,7 @@ Item {
228237 width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
229238 height: width
230239 shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
231− visible: iconSource !== undefined && iconSource != ""
240+ visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
232241 }
233242
234243 Column {
@@ -245,8 +254,9 @@ Item {
245254 left: parent.left
246255 right: parent.right
247256 }
257+ visible: type !== Notification.Confirmation
248258 fontSize: "medium"
249− color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
259+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
250260 elide: Text.ElideRight
251261 textFormat: Text.PlainText
252262 }
@@ -259,9 +269,9 @@ Item {
259269 left: parent.left
260270 right: parent.right
261271 }
262− visible: body != ""
272+ visible: body != "" && type !== Notification.Confirmation
263273 fontSize: "small"
264− color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
274+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
265275 wrapMode: Text.WordWrap
266276 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
267277 elide: Text.ElideRight
@@ -284,6 +294,59 @@ Item {
284294 visible: type == Notification.SnapDecision
285295 }
286296
297+ ShapedIcon {
298+ id: centeredIcon
299+ objectName: "centeredIcon"
300+ width: units.gu(5)
301+ height: width
302+ shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
303+ fileSource: icon.fileSource
304+ visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
305+ anchors.horizontalCenter: parent.horizontalCenter
306+ }
307+
308+ Label {
309+ id: valueLabel
310+ objectName: "valueLabel"
311+ text: body
312+ anchors.horizontalCenter: parent.horizontalCenter
313+ visible: type === Notification.Confirmation && body !== ""
314+ fontSize: "medium"
315+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
316+ wrapMode: Text.WordWrap
317+ maximumLineCount: 1
318+ elide: Text.ElideRight
319+ textFormat: Text.PlainText
320+ }
321+
322+ UbuntuShape {
323+ id: valueIndicator
324+ objectName: "valueIndicator"
325+ visible: type === Notification.Confirmation
326+ property double value
327+
328+ anchors {
329+ left: parent.left
330+ right: parent.right
331+ margins: contentSpacing
332+ }
333+
334+ height: units.gu(1)
335+ color: darkOnBright ? UbuntuColors.darkGrey : UbuntuColors.lightGrey
336+ borderSource: "none"
337+ radius: "small"
338+
339+ UbuntuShape {
340+ id: innerBar
341+ objectName: "innerBar"
342+ width: valueIndicator.width * valueIndicator.value / 100
343+ height: units.gu(1)
344+ color: notification.hints["x-canonical-value-bar-tint"] === "true" ? UbuntuColors.orange : darkOnBright ? UbuntuColors.lightGrey : "white"
345+ borderSource: "none"
346+ radius: "small"
347+ }
348+ }
349+
287350 Column {
288351 id: dialogColumn
289352 objectName: "dialogListView"
@@ -456,7 +519,7 @@ Item {
456519 onClicked: notification.notification.invokeAction(comboRepeater.itemAt(2).actionId)
457520 expanded: false
458521 expandedHeight: (comboRepeater.count - 2) * units.gu(4) + units.gu(.5)
459− comboList: Flickables.Flickable {
522+ comboList: Flickable {
460523 // this has to be wrapped inside a flickable
461524 // to work around a feature/bug? of the
462525 // ComboButton SDK-element, making a regular
qml/Notifications/Notifications.qml+3−2
@@ -19,9 +19,8 @@ import Ubuntu.Components 0.1
1919import Unity.Notifications 1.0 as UnityNotifications
2020import Utils 0.1
2121import "../Components"
22−import "../Components/Flickables" as Flickables
2322
24−Flickables.ListView {
23+ListView {
2524 id: notificationList
2625
2726 objectName: "notificationList"
@@ -32,6 +31,7 @@ Flickables.ListView {
3231
3332 SortFilterProxyModel {
3433 id: snapDecisionProxyModel
34+ objectName: "snapDecisionProxyModel"
3535
3636 model: notificationList.model
3737 filterRole: UnityNotifications.ModelInterface != undefined ? UnityNotifications.ModelInterface.RoleType : 0
@@ -56,6 +56,7 @@ Flickables.ListView {
5656 secondaryIconSource: model.secondaryIcon
5757 summary: model.summary
5858 body: model.body
59+ value: model.value
5960 actions: model.actions
6061 notificationId: model.id
6162 notification: notificationList.model.getRaw(notificationId)
qml/Panel/ActiveCallHint.qml+3−1
@@ -23,6 +23,8 @@ import "../Components"
2323Item {
2424 id: callHint
2525
26+ property bool greeterShown: false
27+
2628 readonly property bool active: {
2729 var application = ApplicationManager.findApplication("dialer-app");
2830
@@ -33,7 +35,7 @@ Item {
3335 return application.state !== ApplicationInfoInterface.Starting;
3436 }
3537 }
36− if (ApplicationManager.focusedApplicationId !== "dialer-app") {
38+ if (greeterShown || ApplicationManager.focusedApplicationId !== "dialer-app") {
3739 if (application) {
3840 // Don't show if application is still starting; might get a fleeting true/false.
3941 return application.state !== ApplicationInfoInterface.Starting && callManager.hasCalls;
qml/Components/Flickables/Flickable.qml → qml/Panel/Handle.qmlrenamed+24−4
@@ -14,10 +14,30 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.3 as QtQuick
17+import QtQuick 2.2
1818import Ubuntu.Components 1.1
1919
20−QtQuick.Flickable {
21− flickDeceleration: 1500 * units.gridUnit / 8
22− maximumFlickVelocity: 2500 * units.gridUnit / 8
20+Rectangle {
21+ id: handle
22+ color: "#333333"
23+ height: units.gu(2)
24+ property bool active: false
25+
26+ Row {
27+ id: dots
28+ width: childrenRect.width
29+ height: childrenRect.height
30+ anchors.centerIn: parent
31+ spacing: units.gu(0.5)
32+ Repeater {
33+ model: 3
34+ delegate: Rectangle {
35+ id: dot
36+ width: units.dp(3)
37+ height: width
38+ color: handle.active ? "#de4814" : "#717171"
39+ radius: units.dp(1)
40+ }
41+ }
42+ }
2343}
qml/Panel/IndicatorItem.qml+232−27
@@ -1,53 +1,258 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * 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
5+ * it under the terms of the GNU Lesser General Public License as published by
66 * the Free Software Foundation; version 3.
77 *
88 * This program is distributed in the hope that it will be useful,
99 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
11+ * GNU Lesser General Public License for more details.
1212 *
13− * You should have received a copy of the GNU General Public License
13+ * You should have received a copy of the GNU Lesser General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity.Indicators 0.1 as Indicators
20−import "../Components"
17+import QtQuick 2.2
18+import Ubuntu.Components 1.1
19+import Ubuntu.Settings.Components 0.1
20+import "Indicators"
2121
22−Loader {
22+IndicatorDelegate {
2323 id: root
2424
25− property alias widgetSource: root.source
26− property bool dimmed: false
27− property var indicatorProperties: undefined
28− property bool indicatorVisible: item ? item.enabled : false
2925 property string identifier
26+ property string title: indicatorName.text
27+ property alias leftLabel: leftLabelItem.text
28+ property alias rightLabel: rightLabelItem.text
29+ property var icons: undefined
30+ property bool expanded: false
31+ property bool selected: false
32+ property real iconHeight: units.gu(2)
33+ readonly property color color: {
34+ if (!expanded) return "#ededed";
35+ if (!selected) return "#4c4c4c";
36+ return "#ededed";
37+ }
38+
39+ signal clicked()
40+
41+ implicitWidth: mainItems.width
3042
31− opacity: dimmed ? 0.4 : 1
32− Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
43+ MouseArea {
44+ anchors.fill: parent
45+ onClicked: parent.clicked()
46+ }
47+
48+ // FIXME: For now we will enable led indicator support only for messaging indicator
49+ // in the future we should export a led API insted of doing that,
50+ Loader {
51+ id: indicatorLed
52+ // only load source Component if the icons contains the new message icon
53+ source: (root.icons && (String(root.icons).indexOf("indicator-messages-new") != -1)) ? Qt.resolvedUrl("Indicators/IndicatorsLight.qml") : ""
54+ }
3355
34− onLoaded: {
35− for(var pName in indicatorProperties) {
36− if (item.hasOwnProperty(pName)) {
37− item[pName] = indicatorProperties[pName];
56+ Item {
57+ id: mainItems
58+ anchors.centerIn: parent
59+
60+ width: leftLabelItem.width + iconsItem.width + rightLabelItem.width
61+ implicitHeight: units.gu(2)
62+
63+ Label {
64+ id: leftLabelItem
65+ objectName: "leftLabel"
66+
67+ anchors {
68+ left: mainItems.left
69+ verticalCenter: parent.verticalCenter
3870 }
71+ width: contentWidth > 0 ? contentWidth + units.gu(1) : 0
72+ horizontalAlignment: Text.AlignHCenter
73+
74+ opacity: 1.0
75+ font.family: "Ubuntu"
76+ fontSize: "medium"
77+ color: root.color
78+ Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
3979 }
80+
81+ Item {
82+ id: iconsItem
83+ objectName: "icons"
84+
85+ width: iconRow.width > 0 ? iconRow.width + units.gu(1) : 0
86+ anchors {
87+ left: leftLabelItem.right
88+ verticalCenter: parent.verticalCenter
89+ }
90+
91+ Row {
92+ id: iconRow
93+ anchors.centerIn: iconsItem
94+ spacing: units.gu(1)
95+
96+ Repeater {
97+ id: iconRepeater
98+ objectName: "iconRepeater"
99+
100+ model: d.useFallbackIcon ? [ "image://theme/settings" ] : root.icons
101+
102+ StatusIcon {
103+ id: itemImage
104+ objectName: "icon"+index
105+ height: iconHeight
106+ source: modelData
107+ sets: ["status", "actions"]
108+ color: root.color
109+ Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
110+
111+ opacity: {
112+ if (!expanded) return 1.0;
113+ if (!selected) return 0.6;
114+ return 1.0;
115+ }
116+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
117+ }
118+ }
119+ }
120+ }
121+
122+ Label {
123+ id: rightLabelItem
124+ objectName: "rightLabel"
125+
126+ anchors {
127+ left: iconsItem.right
128+ verticalCenter: parent.verticalCenter
129+ }
130+ width: contentWidth > 0 ? contentWidth + units.gu(1) : 0
131+ horizontalAlignment: Text.AlignHCenter
132+
133+ opacity: 1.0
134+ font.family: "Ubuntu"
135+ fontSize: "medium"
136+ color: root.color
137+ Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
138+ }
139+ }
140+
141+ Label {
142+ id: indicatorName
143+ objectName: "indicatorName"
144+
145+ anchors.top: mainItems.bottom
146+ anchors.topMargin: units.gu(0.5)
147+ anchors.horizontalCenter: parent.horizontalCenter
148+ width: contentWidth > 0 ? contentWidth + units.gu(1) : 0
149+
150+ text: title !== "" ? title : identifier
151+ fontSize: "x-small"
152+ horizontalAlignment: Text.AlignHCenter
153+ opacity: 0
154+ color: root.color
155+ Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
40156 }
41157
42− Binding {
43− target: item
44− property: "identifier"
45− value: identifier
158+ StateGroup {
159+ id: d
160+ property bool useFallbackIcon: false
161+
162+ states: [
163+ State {
164+ name: "minimised"
165+ when: !expanded && ((icons && icons.length > 0) || leftLabel !== "" || rightLabel !== "")
166+ PropertyChanges { target: indicatorName; opacity: 0}
167+ },
168+
169+ State {
170+ name: "minimised_fallback"
171+ when: !expanded && (!icons || icons.length === 0) && leftLabel == "" && rightLabel == ""
172+ PropertyChanges { target: indicatorName; opacity: 0}
173+ PropertyChanges { target: d; useFallbackIcon: true }
174+ },
175+
176+ State {
177+ name: "expanded"
178+ PropertyChanges { target: indicatorName; visible: true; opacity: 1}
179+ PropertyChanges { target: mainItems; anchors.verticalCenterOffset: -units.gu(1) }
180+ },
181+
182+ State {
183+ name: "expanded_icon"
184+ extend: "expanded"
185+ when: expanded && (icons && icons.length > 0)
186+ AnchorChanges { target: iconsItem; anchors.left: undefined; anchors.horizontalCenter: parent.horizontalCenter }
187+ AnchorChanges { target: leftLabelItem; anchors.left: undefined; anchors.right: iconsItem.left }
188+ PropertyChanges { target: leftLabelItem; opacity: 0 }
189+ PropertyChanges { target: leftLabelItem; opacity: 0 }
190+ PropertyChanges { target: rightLabelItem; opacity: 0 }
191+ PropertyChanges { target: root; width: Math.max(units.gu(10), Math.max(iconsItem.width, indicatorName.width)) }
192+ },
193+
194+ State {
195+ name: "expanded_fallback"
196+ extend: "expanded"
197+ when: expanded && (!icons || icons.length === 0) && leftLabel == "" && rightLabel == ""
198+ PropertyChanges { target: d; useFallbackIcon: true }
199+ AnchorChanges { target: iconsItem; anchors.left: undefined; anchors.horizontalCenter: parent.horizontalCenter }
200+ AnchorChanges { target: leftLabelItem; anchors.left: undefined; anchors.right: iconsItem.left }
201+ PropertyChanges { target: leftLabelItem; opacity: 0 }
202+ PropertyChanges { target: leftLabelItem; opacity: 0 }
203+ PropertyChanges { target: rightLabelItem; opacity: 0 }
204+ PropertyChanges { target: root; width: Math.max(units.gu(10), Math.max(iconsItem.width, indicatorName.width)) }
205+ },
206+
207+ State {
208+ name: "expanded_rightLabel"
209+ extend: "expanded"
210+ when: expanded && (!icons || icons.length === 0) && rightLabel !== ""
211+ AnchorChanges { target: rightLabelItem; anchors.left: undefined; anchors.horizontalCenter: parent.horizontalCenter }
212+ PropertyChanges { target: iconsItem; opacity: 0 }
213+ PropertyChanges { target: leftLabelItem; opacity: 0 }
214+ PropertyChanges { target: root; width: Math.max(units.gu(10), Math.max(rightLabelItem.width, indicatorName.width)) }
215+ },
216+
217+ State {
218+ name: "expanded_leftLabel"
219+ extend: "expanded"
220+ when: expanded && (!icons || icons.length === 0) && leftLabel !== ""
221+ AnchorChanges { target: leftLabelItem; anchors.left: undefined; anchors.horizontalCenter: parent.horizontalCenter }
222+ PropertyChanges { target: iconsItem; opacity: 0 }
223+ PropertyChanges { target: rightLabelItem; opacity: 0 }
224+ PropertyChanges { target: root; width: Math.max(units.gu(10), Math.max(leftLabelItem.width, indicatorName.width)) }
225+ }
226+ ]
227+
228+ transitions: [
229+ Transition {
230+ PropertyAction { target: d; property: "useFallbackIcon" }
231+ AnchorAnimation {
232+ targets: [ mainItems, iconsItem, leftLabelItem, rightLabelItem ]
233+ duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing
234+ }
235+ PropertyAnimation {
236+ targets: [ root, mainItems, iconsItem, leftLabelItem, rightLabelItem, indicatorName ]
237+ properties: "width, opacity, anchors.verticalCenterOffset";
238+ duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing
239+ }
240+ }
241+ ]
46242 }
47243
48− Binding {
49− target: item
50− property: "objectName"
51− value: identifier + "-widget"
244+ onRootActionStateChanged: {
245+ if (rootActionState == undefined) {
246+ title = "";
247+ leftLabel = "";
248+ rightLabel = "";
249+ icons = undefined;
250+ return;
251+ }
252+
253+ title = rootActionState.title ? rootActionState.title : "";
254+ leftLabel = rootActionState.leftLabel ? rootActionState.leftLabel : "";
255+ rightLabel = rootActionState.rightLabel ? rootActionState.rightLabel : "";
256+ icons = rootActionState.icons;
52257 }
53258}
qml/Panel/IndicatorItemRow.qmlnew file+297
@@ -0,0 +1,297 @@
1+/*
2+ * Copyright (C) 2013-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.2
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+ width: row.width
23+ height: units.gu(3)
24+
25+ property QtObject indicatorsModel: null
26+ property real overFlowWidth: width
27+ property bool expanded: false
28+ property var currentItem
29+ readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
30+
31+ property real unitProgress: 0.0
32+ property real selectionChangeBuffer: units.gu(2)
33+ property bool enableLateralChanges: false
34+ property color hightlightColor: "#ededed"
35+
36+ property real lateralPosition: -1
37+ onLateralPositionChanged: {
38+ updateItemFromLateralPosition();
39+ }
40+
41+ onEnableLateralChangesChanged: {
42+ updateItemFromLateralPosition();
43+ }
44+
45+ function updateItemFromLateralPosition() {
46+ if (currentItem && !enableLateralChanges) return;
47+ if (lateralPosition === -1) return;
48+
49+ if (!currentItem) {
50+ selectItemAt(lateralPosition);
51+ return;
52+ }
53+
54+ var maximumBufferOffset = selectionChangeBuffer * unitProgress;
55+ var proposedItem = indicatorAt(lateralPosition, 0);
56+ if (proposedItem) {
57+ var bufferExceeded = false;
58+
59+ if (proposedItem !== currentItem) {
60+ // Proposed item is not directly adjacent to current?
61+ if (Math.abs(proposedItem.ownIndex - currentItem.ownIndex) > 1) {
62+ bufferExceeded = true;
63+ } else { // no
64+ var currentItemLateralPosition = root.mapToItem(proposedItem, lateralPosition, 0).x;
65+
66+ // Is the distance into proposed item greater than max buffer?
67+ // Proposed item is before current item
68+ if (proposedItem.x < currentItem.x) {
69+ bufferExceeded = (proposedItem.width - currentItemLateralPosition) > maximumBufferOffset;
70+ } else { // After
71+ bufferExceeded = currentItemLateralPosition > maximumBufferOffset;
72+ }
73+ }
74+ if (bufferExceeded) {
75+ selectItemAt(lateralPosition);
76+ }
77+ }
78+ } else {
79+ selectItemAt(lateralPosition);
80+ }
81+ }
82+
83+ function indicatorAt(x, y) {
84+ var item = row.childAt(x, y);
85+ return item && item.hasOwnProperty("ownIndex") ? item : null;
86+ }
87+
88+ function resetCurrentItem() {
89+ d.firstItemSwitch = true;
90+ d.previousItem = undefined;
91+ currentItem = undefined;
92+ }
93+
94+ function setCurrentItemIndex(index) {
95+ for (var i = 0; i < row.children.length; i++) {
96+ var item = row.children[i];
97+ if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
98+ if (currentItem !== item) currentItem = item;
99+ break;
100+ }
101+ }
102+ }
103+
104+ function selectItemAt(lateralPosition) {
105+ var item = indicatorAt(lateralPosition, 0);
106+ if (item && item.opacity > 0) {
107+ currentItem = item;
108+ } else {
109+ // Select default item.
110+ var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
111+
112+ for (var i = 0; i < row.children.length; i++) {
113+ if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
114+ item = row.children[i];
115+ break;
116+ }
117+ }
118+ if (currentItem !== item) currentItem = item;
119+ }
120+ }
121+
122+ QtObject {
123+ id: d
124+ property bool firstItemSwitch: true
125+ property var previousItem
126+ property bool forceAlignmentAnimationDisabled: false
127+ }
128+
129+ onCurrentItemChanged: {
130+ if (d.previousItem) {
131+ d.firstItemSwitch = false;
132+ }
133+ d.previousItem = currentItem;
134+ }
135+
136+ Row {
137+ id: row
138+ anchors {
139+ top: parent.top
140+ bottom: parent.bottom
141+ }
142+
143+ // TODO: make this better
144+ // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
145+ // and adjust the highlight X immediately.
146+ width: implicitWidth
147+ Behavior on width {
148+ SequentialAnimation {
149+ ScriptAction {
150+ script: {
151+ d.forceAlignmentAnimationDisabled = true;
152+ highlight.currentItemX = Qt.binding(function() { return currentItem ? currentItem.x : 0 });
153+ d.forceAlignmentAnimationDisabled = false;
154+ }
155+ }
156+ }
157+ }
158+
159+ Repeater {
160+ id: repeater
161+ model: indicatorsModel
162+ visible: false
163+
164+ onItemRemoved: {
165+ // current item removed.
166+ if (currentItem === item) {
167+ var i = 0;
168+ while (i < row.children.length) {
169+ var childItem = row.children[i];
170+ if (childItem !== item) {
171+ setCurrentItemIndex(i);
172+ break;
173+ }
174+ i++;
175+ }
176+ }
177+ }
178+
179+
180+ delegate: IndicatorItem {
181+ id: indicatorItem
182+ objectName: identifier+"-panelItem"
183+
184+ property int ownIndex: index
185+ property bool overflow: row.width - x > overFlowWidth
186+ property bool hidden: !expanded && (overflow || !indicatorVisible)
187+
188+ height: row.height
189+ expanded: root.expanded
190+ selected: currentItem === this
191+
192+ identifier: model.identifier
193+ busName: indicatorProperties.busName
194+ actionsObjectPath: indicatorProperties.actionsObjectPath
195+ menuObjectPath: indicatorProperties.menuObjectPath
196+
197+ opacity: hidden ? 0.0 : 1.0
198+ Behavior on opacity {
199+ NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
200+ }
201+
202+ width: (expanded || indicatorVisible) ? implicitWidth : 0
203+
204+ Behavior on width {
205+ NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
206+ }
207+
208+ Component.onDestruction: {
209+ // current item removed.
210+ if (currentItem === this) {
211+ var i = 0;
212+ while (i < row.children.length) {
213+ var childItem = row.children[i];
214+ if (childItem !== this) {
215+ setCurrentItemIndex(i);
216+ break;
217+ }
218+ i++;
219+ }
220+ }
221+ }
222+ }
223+ }
224+ }
225+
226+ Rectangle {
227+ id: highlight
228+ objectName: "highlight"
229+
230+ anchors.bottom: row.bottom
231+ height: units.dp(2)
232+ color: root.hightlightColor
233+ visible: currentItem !== undefined
234+ opacity: 0.0
235+
236+ width: currentItem ? currentItem.width : 0
237+ Behavior on width {
238+ enabled: !d.firstItemSwitch && expanded
239+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
240+ }
241+
242+ // micromovements of the highlight line when user moves the finger across the items while pulling
243+ // the handle downwards.
244+ property real highlightCenterOffset: {
245+ if (!currentItem || lateralPosition == -1 || !enableLateralChanges) return 0;
246+
247+ var itemMapped = root.mapToItem(currentItem, lateralPosition, 0);
248+
249+ var distanceFromCenter = itemMapped.x - currentItem.width / 2;
250+ if (distanceFromCenter > 0) {
251+ distanceFromCenter = Math.max(0, distanceFromCenter - currentItem.width / 8);
252+ } else {
253+ distanceFromCenter = Math.min(0, distanceFromCenter + currentItem.width / 8);
254+ }
255+
256+ if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
257+ return 0;
258+ } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
259+ return 0;
260+ }
261+ return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
262+ }
263+ Behavior on highlightCenterOffset {
264+ NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
265+ }
266+
267+ property real currentItemX: currentItem ? currentItem.x : 0
268+ Behavior on currentItemX {
269+ id: currentItemXBehavior
270+ enabled: !d.firstItemSwitch && expanded && !d.forceAlignmentAnimationDisabled
271+ NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
272+ }
273+ x: currentItemX + highlightCenterOffset
274+ }
275+
276+ states: [
277+ State {
278+ name: "minimised"
279+ when: !expanded
280+ },
281+ State {
282+ name: "expanded"
283+ when: expanded
284+ PropertyChanges { target: highlight; opacity: 0.9 }
285+ }
286+ ]
287+
288+ transitions: [
289+ Transition {
290+ PropertyAnimation {
291+ properties: "opacity";
292+ duration: UbuntuAnimation.SnapDuration
293+ easing: UbuntuAnimation.StandardEasing
294+ }
295+ }
296+ ]
297+}
qml/Panel/Indicators/DefaultIndicatorPage.qml → qml/Panel/IndicatorPage.qmlrenamed+11−18
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -12,16 +12,12 @@
1212 *
1313 * You should have received a copy of the GNU Lesser General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Renato Araujo Oliveira Filho <renato@canonical.com>
18− * Nick Dedekind <nick.dedekind@canonical.com>
1915 */
2016
2117import QtQuick 2.0
2218import Ubuntu.Components 0.1 as Components
2319import Unity.Indicators 0.1 as Indicators
24−import "../../Components/Flickables" as Flickables
20+import "Indicators"
2521
2622IndicatorBase {
2723 id: main
@@ -53,13 +49,13 @@ IndicatorBase {
5349
5450 Connections {
5551 target: menuStack.tail
56− onRowsInserted: {
57− if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
58− menuStack.rootMenu = menuStack.tail.submenu(0);
59− menuStack.push(menuStack.rootMenu, 0);
60− }
61− }
62− onModelReset: {
52+
53+ // fix async creation with signal from model before it's finished.
54+ Component.onCompleted: update();
55+ onRowsInserted: update();
56+ onModelReset: update();
57+
58+ function update() {
6359 if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
6460 menuStack.rootMenu = menuStack.tail.submenu(0);
6561 menuStack.push(menuStack.rootMenu, 0);
@@ -67,7 +63,7 @@ IndicatorBase {
6763 }
6864 }
6965
70− Flickables.ListView {
66+ ListView {
7167 id: mainMenu
7268 objectName: "mainMenu"
7369 model: menuStack.rootMenu
@@ -93,8 +89,6 @@ IndicatorBase {
9389
9490 // Only allow flicking if the content doesn't fit on the page
9591 interactive: contentHeight > height
96− // FIXME - https://bugreports.qt-project.org/browse/QTBUG-41207
97− boundsBehavior: Flickable.StopAtBounds
9892
9993 property int selectedIndex: -1
10094 property bool blockCurrentIndexChange: false
@@ -125,7 +119,7 @@ IndicatorBase {
125119 }
126120
127121 Connections {
128− target: mainMenu.model
122+ target: mainMenu.model ? mainMenu.model : null
129123 onRowsAboutToBeRemoved: {
130124 // track current item deletion.
131125 if (mainMenu.selectedIndex >= first && mainMenu.selectedIndex <= last) {
@@ -137,7 +131,6 @@ IndicatorBase {
137131 delegate: Loader {
138132 id: loader
139133 objectName: "menuItem" + index
140− asynchronous: true
141134 width: ListView.view.width
142135 visible: status == Loader.Ready
143136
qml/Panel/IndicatorRow.qmldeleted−163
@@ -1,163 +0,0 @@
1−/*
2− * Copyright (C) 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−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity.Indicators 0.1 as Indicators
20−import "../Components"
21−import "../Components/Flickables" as Flickables
22−
23−Item {
24− id: indicatorRow
25−
26− readonly property alias currentItem : itemView.currentItem
27− readonly property alias currentItemIndex: itemView.currentIndex
28− readonly property alias row: itemView
29− property QtObject indicatorsModel: null
30− property int overFlowWidth: width
31− property bool showAll: false
32− property real currentItemOffset: 0.0
33− property real unitProgress: 0.0
34−
35− width: units.gu(40)
36− height: units.gu(3)
37−
38− function setDefaultItem() {
39− // The leftmost indicator
40− setCurrentItemIndex(0);
41− }
42−
43− function setCurrentItemIndex(index) {
44− itemView.currentIndex = index;
45− }
46−
47− function setCurrentItem(item) {
48− if (item && item.hasOwnProperty("ownIndex")) {
49− itemView.currentIndex = item.ownIndex;
50− } else {
51− itemView.currentIndex = -1;
52− }
53− }
54−
55− Timer {
56− id: allVisible
57− interval: 1000
58−
59− onTriggered: {
60− showAll = false;
61− }
62− }
63−
64− Flickables.ListView {
65− id: itemView
66− objectName: "indicatorRowItems"
67− interactive: false
68− model: indicatorsModel ? indicatorsModel : null
69−
70− width: childrenRect.width
71− height: indicatorRow.height
72− anchors.right: parent.right
73− orientation: ListView.Horizontal
74−
75− property int lastCount: 0
76− onCountChanged: {
77− if (lastCount < count) {
78− showAll = true;
79− allVisible.start();
80− }
81− lastCount = count;
82− }
83−
84− delegate: Item {
85− id: itemWrapper
86− objectName: "item" + index
87− height: indicatorRow.height
88− width: indicatorItem.width
89− opacity: 1 - indicatorRow.unitProgress
90− y: 0
91− state: "standard"
92−
93− property int ownIndex: index
94− property bool highlighted: indicatorRow.unitProgress > 0 ? ListView.isCurrentItem : false
95− property bool dimmed: indicatorRow.unitProgress > 0 ? !ListView.isCurrentItem : false
96−
97− property bool hidden: !showAll && !highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")
98− property bool overflow: row.width - itemWrapper.x > overFlowWidth
99−
100− IndicatorItem {
101− id: indicatorItem
102− identifier: model.identifier
103− height: parent.height
104−
105− dimmed: itemWrapper.dimmed
106−
107− widgetSource: model.widgetSource
108− indicatorProperties : model.indicatorProperties
109− }
110−
111− states: [
112− State {
113− name: "standard"
114− when: !hidden && !overflow && !highlighted
115− },
116− State {
117− name: "highlighted"
118− when: highlighted
119− PropertyChanges { target: itemWrapper; opacity: 1.0 }
120− },
121− State {
122− name: "hidden"
123− when: hidden || overflow
124− PropertyChanges { target: itemWrapper; opacity: 0.0 }
125− }
126− ]
127−
128− Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
129− }
130− }
131−
132−
133− Rectangle {
134− id: highlight
135− color: Theme.palette.selected.foreground
136− objectName: "highlight"
137− height: units.dp(2)
138− anchors.top: row.bottom
139− visible: indicatorRow.currentItem != null
140−
141− property real intendedX: row.x + (indicatorRow.currentItem != null ? (indicatorRow.currentItem.x - row.originX) + centerOffset : 0)
142− x: intendedX >= row.x ? (intendedX + width <= row.x + row.width ? intendedX : row.x + row.width - width) : row.x // listview boundaries
143− width: indicatorRow.currentItem != null ? indicatorRow.currentItem.width : 0
144−
145− property real centerOffset: {
146− if (indicatorRow.currentItemOffset > 0.1) {
147− return (indicatorRow.currentItemOffset - 0.1) * units.gu(0.4);
148− } else if (indicatorRow.currentItemOffset < -0.1) {
149− return (indicatorRow.currentItemOffset + 0.1) * units.gu(0.4);
150− }
151− return 0.0;
152− }
153−
154− Behavior on width {
155− enabled: unitProgress > 0;
156− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
157− }
158− Behavior on x {
159− enabled: unitProgress > 0;
160− UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
161− }
162− }
163−}
qml/Panel/Indicators.qmldeleted−415
@@ -1,415 +0,0 @@
1−/*
2− * Copyright (C) 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−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Ubuntu.Gestures 0.1
20−import Unity.Indicators 0.1 as Indicators
21−
22−import "../Components"
23−import "../Components/ListItems"
24−import "Indicators"
25−
26−Showable {
27− id: indicators
28−
29− property real openedHeight: units.gu(71)
30− property int panelHeight: units.gu(3)
31− property alias overFlowWidth: indicatorRow.overFlowWidth
32− property alias showAll: indicatorRow.showAll
33− // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
34− property string profile: indicatorProfile
35−
36− readonly property real hintValue: panelHeight + menuContent.headerHeight
37− readonly property int lockThreshold: openedHeight / 2
38− property bool fullyOpened: height == openedHeight
39− property bool partiallyOpened: height > panelHeight && !fullyOpened
40− property bool fullyClosed: height <= panelHeight
41− property bool contentEnabled: true
42− property bool initalizeItem: true
43− readonly property alias content: menuContent
44− property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
45− property bool enableHint: true
46− property real showHintBottomMargin: 0
47−
48− signal showTapped(point position)
49−
50− // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
51− // use its own values. Need to ask design about this.
52− showAnimation: StandardAnimation {
53− property: "height"
54− to: openedHeight
55− }
56−
57− hideAnimation: StandardAnimation {
58− property: "height"
59− duration: 350
60− to: panelHeight
61− easing.type: Easing.OutCubic
62− }
63−
64− onOpenedHeightChanged: {
65− if (showAnimation.running) {
66− showAnimation.restart();
67− } else if (indicators.shown) {
68− height = openedHeight;
69− }
70− }
71−
72− height: panelHeight
73− onHeightChanged: updateRevealProgressState(indicators.height - panelHeight - showHintBottomMargin, true)
74−
75− function updateRevealProgressState(revealProgress, enableRelease) {
76− if (!showAnimation.running && !hideAnimation.running) {
77− if (revealProgress === 0) {
78− indicators.state = "initial";
79− } else if (enableHint && revealProgress > 0 && revealProgress <= hintValue) {
80− indicators.state = "hint";
81− } else if ((!enableHint || revealProgress > hintValue) && revealProgress < lockThreshold) {
82− indicators.state = "reveal";
83− } else if (revealProgress >= lockThreshold && lockThreshold > 0) {
84− indicators.state = "locked";
85− }
86− }
87− }
88−
89− function calculateCurrentItem(xValue, useBuffer) {
90− var rowCoordinates;
91− var itemCoordinates;
92− var currentItem;
93− var distanceFromRightEdge;
94− var bufferExceeded = false;
95−
96− if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
97−
98− /*
99− If user drags the indicator handle bar down a distance hintValue or less, this is 0.
100− If bar is dragged down a distance greater than or equal to lockThreshold, this is 1.
101− Otherwise it contains the bar's location as a fraction of the distance between hintValue (is 0) and lockThreshold (is 1).
102− */
103− var verticalProgress =
104− MathUtils.clamp((indicators.height - handle.height - hintValue) /
105− (lockThreshold - hintValue), 0, 1);
106−
107− /*
108− Vertical velocity check. Don't change the indicator if we're moving too quickly.
109− */
110− var verticalSpeed = Math.abs(yVelocityCalculator.calculate());
111− if (verticalSpeed >= 0.05 && !initalizeItem) {
112− return;
113− }
114−
115− /*
116− Percentage of an indicator icon's width the user's press can stray horizontally from the
117− focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
118− go right a distance of half an icon's width before focus moves to the icon on the right
119− */
120− var maxBufferThreshold = 0.5;
121−
122− /*
123− To help users find the indicator of their choice while opening the indicators, we add logic to add a
124− left/right buffer to each icon so it is harder for the focus to be moved accidentally to another icon,
125− as the user moves their finger down, but yet allows them to switch indicator if they want.
126− This buffer is wider the further the user's finger is from the top of the screen.
127− */
128− var effectiveBufferThreshold = maxBufferThreshold * verticalProgress;
129−
130− rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
131− // get the current delegate
132− currentItem = indicatorRow.row.itemAt(rowCoordinates.x, 0);
133− if (currentItem) {
134− itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
135− distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
136− if (currentItem != indicatorRow.currentItem) {
137− if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
138− bufferExceeded = true;
139− } else {
140− if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
141− bufferExceeded = true;
142− } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
143− bufferExceeded = true;
144− }
145− }
146− if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItemIndex < 0 || indicatorRow.currentItem == null) {
147− indicatorRow.setCurrentItem(currentItem);
148− }
149−
150− // need to re-init the distanceFromRightEdge for offset calculation
151− itemCoordinates = indicatorRow.row.mapToItem(indicatorRow.currentItem, rowCoordinates.x, 0);
152− distanceFromRightEdge = (indicatorRow.currentItem.width - itemCoordinates.x) / (indicatorRow.currentItem.width);
153− }
154− indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
155− } else if (initalizeItem) {
156− indicatorRow.setDefaultItem();
157− indicatorRow.currentItemOffset = 0;
158− }
159− initalizeItem = indicatorRow.currentItem == null;
160− }
161−
162− // eater
163− MouseArea {
164− anchors {
165− top: parent.top
166− bottom: handle.bottom
167− left: parent.left
168− right: parent.right
169− }
170− }
171−
172− VisibleIndicators {
173− id: visibleIndicators
174− }
175−
176− MenuContent {
177− id: menuContent
178− objectName: "menuContent"
179−
180− anchors {
181− left: parent.left
182− right: parent.right
183− top: indicatorRow.bottom
184− bottom: handle.top
185− }
186− indicatorsModel: visibleIndicators.model
187− visible: indicators.partiallyOpened || indicators.fullyOpened
188− clip: indicators.partiallyOpened
189− enabled: contentEnabled
190−
191− //small shadow gradient at bottom of menu
192− Rectangle {
193− anchors {
194− left: parent.left
195− right: parent.right
196− bottom: parent.bottom
197− }
198− height: units.gu(0.5)
199− gradient: Gradient {
200− GradientStop { position: 0.0; color: "transparent" }
201− GradientStop { position: 1.0; color: "black" }
202− }
203− opacity: 0.4
204− }
205− }
206−
207− Rectangle {
208− id: handle
209−
210− color: menuContent.color
211−
212− anchors {
213− left: parent.left
214− right: parent.right
215− bottom: parent.bottom
216− }
217− height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
218− clip: height < handleImage.height
219− visible: menuContent.visible
220−
221− BorderImage {
222− id: handleImage
223− source: "graphics/handle.sci"
224− height: panelHeight
225− anchors {
226− left: parent.left
227− right: parent.right
228− bottom: parent.bottom
229− }
230− }
231− MouseArea { //prevent clicks passing through
232− anchors.fill: parent
233− }
234− }
235−
236− IndicatorRow {
237− id: indicatorRow
238− objectName: "indicatorRow"
239− anchors {
240− left: parent.left
241− right: parent.right
242− }
243− height: indicators.panelHeight
244− indicatorsModel: visibleIndicators.model
245− state: indicators.state
246− unitProgress: indicators.unitProgress
247−
248− EdgeDragArea {
249− id: rowDragArea
250− anchors.fill: indicatorRow
251− direction: Direction.Downwards
252− maxSilenceTime: 2000
253− distanceThreshold: 0
254−
255− enabled: fullyOpened
256− onDraggingChanged: {
257− if (dragging) {
258− initalizeItem = true;
259− updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
260− indicators.calculateCurrentItem(touchX, false);
261− } else {
262− indicators.state = "commit";
263− indicatorRow.currentItemOffset = 0;
264− }
265− }
266−
267− onTouchXChanged: {
268− indicators.calculateCurrentItem(touchX, true);
269− }
270− onTouchSceneYChanged: {
271− updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
272− yVelocityCalculator.trackedPosition = touchSceneY;
273− }
274− }
275− }
276−
277− Connections {
278− target: showAnimation
279− onRunningChanged: {
280− if (showAnimation.running) {
281− indicators.state = "commit";
282− indicatorRow.currentItemOffset = 0;
283− }
284− }
285− }
286−
287− Connections {
288− target: hideAnimation
289− onRunningChanged: {
290− if (hideAnimation.running) {
291− indicators.state = "initial";
292− initalizeItem = true;
293− indicatorRow.currentItemOffset = 0;
294− }
295− }
296− }
297−
298− QtObject {
299− id: d
300− property bool enableIndexChangeSignal: true
301− property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
302− }
303−
304− Connections {
305− target: menuContent
306− onCurrentMenuIndexChanged: {
307− var oldActive = d.enableIndexChangeSignal;
308− if (!oldActive) return;
309− d.enableIndexChangeSignal = false;
310−
311− indicatorRow.setCurrentItemIndex(menuContent.currentMenuIndex);
312−
313− d.enableIndexChangeSignal = oldActive;
314− }
315− }
316−
317− Connections {
318− target: indicatorRow
319− onCurrentItemIndexChanged: {
320− var oldActive = d.enableIndexChangeSignal;
321− if (!oldActive) return;
322− d.enableIndexChangeSignal = false;
323−
324− menuContent.setCurrentMenuIndex(indicatorRow.currentItemIndex, fullyOpened || partiallyOpened);
325−
326− d.enableIndexChangeSignal = oldActive;
327− }
328− }
329− // connections to the active drag handle
330− Connections {
331− target: d.activeDragHandle
332− onTouchXChanged: {
333− indicators.calculateCurrentItem(d.activeDragHandle.touchX, true);
334− }
335− onTouchSceneYChanged: {
336− yVelocityCalculator.trackedPosition = d.activeDragHandle.touchSceneY;
337− }
338− }
339−
340− DragHandle {
341− id: showDragHandle
342− anchors.bottom: parent.bottom
343− // go beyond parent so that it stays reachable, at the top of the screen.
344− anchors.bottomMargin: showHintBottomMargin
345− anchors.left: parent.left
346− anchors.right: parent.right
347− height: panelHeight
348− direction: Direction.Downwards
349− enabled: !indicators.shown && indicators.available
350− hintDisplacement: enableHint ? indicators.hintValue : 0
351− autoCompleteDragThreshold: maxTotalDragDistance / 2
352− stretch: true
353− maxTotalDragDistance: openedHeight - panelHeight
354− distanceThreshold: panelHeight
355−
356− onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
357− }
358−
359− DragHandle {
360− id: hideDragHandle
361− anchors.fill: handle
362− direction: Direction.Upwards
363− enabled: indicators.shown && indicators.available
364− hintDisplacement: indicators.hintValue
365− autoCompleteDragThreshold: maxTotalDragDistance / 6
366− stretch: true
367− maxTotalDragDistance: openedHeight - panelHeight
368− distanceThreshold: 0
369− }
370−
371− AxisVelocityCalculator {
372− id: yVelocityCalculator
373− }
374−
375− states: [
376− State {
377− name: "initial"
378− },
379− State {
380− name: "hint"
381− StateChangeScript {
382− script: {
383− if (d.activeDragHandle) {
384− calculateCurrentItem(d.activeDragHandle.touchX, false);
385− }
386− }
387− }
388− },
389− State {
390− name: "reveal"
391− extend: "hint"
392− },
393− State {
394− name: "locked"
395− extend: "hint"
396− },
397− State {
398− name: "commit"
399− extend: "hint"
400− }
401− ]
402− state: "initial"
403−
404− transitions: [
405− Transition {
406− NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
407− }
408− ]
409−
410− Component.onCompleted: initialise();
411− function initialise() {
412− visibleIndicators.load(profile);
413− indicatorRow.setDefaultItem();
414− }
415−}
qml/Panel/Indicators/CachedUnityMenuModel.qmldeleted−39
@@ -1,39 +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
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− readonly property bool ready: busName!=="" && actionsObjectPath!=="" && menuObjectPath!==""
31−
32− property var model: {
33− if (!ready) return null;
34−
35− return Indicators.UnityMenuModelCache.model(cachedModel.busName,
36− cachedModel.menuObjectPath,
37− { "indicator": cachedModel.actionsObjectPath });
38− }
39−}
qml/Panel/Indicators/DefaultIndicatorWidget.qmldeleted−119
@@ -1,119 +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 Ubuntu.Components 0.1
22−import Ubuntu.Settings.Components 0.1
23−
24−IndicatorBase {
25− id: indicatorWidget
26−
27− property int iconSize: units.gu(2)
28− property alias leftLabel: itemLeftLabel.text
29− property alias rightLabel: itemRightLabel.text
30− property var icons: undefined
31−
32− width: itemRow.width
33− enabled: false
34−
35− // FIXME: For now we will enable led indicator support only for messaging indicator
36− // in the future we should export a led API insted of doing that,
37− Loader {
38− id: indicatorLed
39− // only load source Component if the icons contains the new message icon
40− source: (indicatorWidget.icons && (String(indicatorWidget.icons).indexOf("indicator-messages-new") != -1)) ? Qt.resolvedUrl("IndicatorsLight.qml") : ""
41− }
42−
43− Row {
44− id: itemRow
45− objectName: "itemRow"
46− anchors {
47− top: parent.top
48− bottom: parent.bottom
49− horizontalCenter: parent.horizontalCenter
50− }
51−
52− Label {
53− id: itemLeftLabel
54− width: contentWidth + units.gu(1)
55− objectName: "leftLabel"
56− color: Theme.palette.selected.backgroundText
57− opacity: 0.8
58− font.family: "Ubuntu"
59− fontSize: "medium"
60− anchors.verticalCenter: parent.verticalCenter
61− visible: text != ""
62− horizontalAlignment: Text.AlignHCenter
63− }
64−
65− Row {
66− id: iconRow
67− anchors {
68− top: parent.top
69− bottom: parent.bottom
70− }
71−
72− Repeater {
73− model: indicatorWidget.icons
74−
75− Item {
76− width: itemImage.width + units.gu(1)
77− height: iconRow.height
78−
79− StatusIcon {
80− id: itemImage
81− height: indicatorWidget.iconSize
82− anchors.centerIn: parent
83− source: modelData
84− sets: ["status", "actions"]
85− color: "#CCCCCC"
86− }
87− }
88− }
89− }
90−
91− Label {
92− id: itemRightLabel
93− width: contentWidth + units.gu(1)
94− objectName: "rightLabel"
95− color: Theme.palette.selected.backgroundText
96− opacity: 0.8
97− font.family: "Ubuntu"
98− fontSize: "medium"
99− anchors.verticalCenter: parent.verticalCenter
100− visible: text != ""
101− horizontalAlignment: Text.AlignHCenter
102− }
103− }
104−
105− onRootActionStateChanged: {
106− if (rootActionState == undefined) {
107− leftLabel = "";
108− rightLabel = "";
109− icons = undefined;
110− enabled = false;
111− return;
112− }
113−
114− leftLabel = rootActionState.leftLabel ? rootActionState.leftLabel : "";
115− rightLabel = rootActionState.rightLabel ? rootActionState.rightLabel : "";
116− icons = rootActionState.icons;
117− enabled = rootActionState.visible;
118− }
119−}
qml/Panel/Indicators/IndicatorBase.qml+3−7
@@ -23,8 +23,6 @@ import Unity.Indicators 0.1
2323Item {
2424 id: indicatorItem
2525
26− enabled: menuObjectPath != ""
27−
2826 //const
2927 property string identifier
3028 property string busName
@@ -32,16 +30,14 @@ Item {
3230 property string menuObjectPath
3331 property string rootMenuType: "com.canonical.indicator.root"
3432
35− property string deviceMenuObjectPath: menuObjectPath
36−
3733 property alias menuModel: cachedModel.model
3834 property alias rootActionState: rootAction
3935
40− CachedUnityMenuModel {
36+ SharedUnityMenuModel {
4137 id: cachedModel
4238 busName: indicatorItem.busName
43− actionsObjectPath: indicatorItem.actionsObjectPath
44− menuObjectPath: indicatorItem.deviceMenuObjectPath
39+ actions: { "indicator": indicatorItem.actionsObjectPath }
40+ menuObjectPath: indicatorItem.menuObjectPath
4541 }
4642
4743 RootActionState {
qml/Panel/Indicators/IndicatorDelegate.qml+2−2
@@ -20,6 +20,6 @@
2020import QtQuick 2.0
2121
2222IndicatorBase {
23− enabled: rootActionState.visible
24− property string title: rootActionState.title
23+ readonly property bool indicatorVisible: rootActionState.indicatorVisible
24+ readonly property string title: rootActionState.title
2525}
qml/Panel/Indicators/VisibleIndicators.qml+4−5
@@ -24,6 +24,10 @@ import Utils 0.1
2424Item {
2525 property SortFilterProxyModel model: filterModel
2626
27+ function initialise(profile) {
28+ indicatorsModel.load(profile);
29+ }
30+
2731 SortFilterProxyModel {
2832 id: filterModel
2933 filterRole: Indicators.IndicatorsModelRole.IsVisible
@@ -36,7 +40,6 @@ Item {
3640 }
3741 }
3842
39−
4043 Indicators.IndicatorsModel {
4144 id: indicatorsModel
4245 }
@@ -78,8 +81,4 @@ Item {
7881 }
7982 }
8083 }
81−
82− function load(profile) {
83− indicatorsModel.load(profile);
84− }
8584}
qml/Panel/Indicators/client/IndicatorsPage.qml → qml/Panel/Indicators/client/IndicatorRepresentation.qmlrenamed+21−10
@@ -21,13 +21,13 @@
2121import QtQuick 2.0
2222import Ubuntu.Components 0.1
2323import Ubuntu.Components.ListItems 0.1 as ListItem
24+import "../.."
2425
2526Page {
26− id: page
27+ id: root
2728
2829 title: indicatorProperties && indicatorProperties.title ? indicatorProperties.title : ""
2930 property variant indicatorProperties
30− property string pageSource : pageLoader.source
3131
3232 anchors.fill: parent
3333
@@ -44,6 +44,7 @@ Page {
4444 id: pageLoader
4545 objectName: "pageLoader"
4646 clip:true
47+ asynchronous: true
4748
4849 Rectangle {
4950 anchors.fill: pageLoader
@@ -58,15 +59,25 @@ Page {
5859 topMargin: units.gu(2)
5960 bottomMargin: units.gu(2)
6061 }
61− source : visualCheck.checked ? page.pageSource : "IndicatorsTree.qml"
62+ sourceComponent: visualCheck.checked ? page : tree
6263
63− onLoaded: {
64− for(var pName in indicatorProperties) {
65− if (item.hasOwnProperty(pName)) {
66− item[pName] = indicatorProperties[pName];
67− }
64+ Component {
65+ id: page
66+ IndicatorPage {
67+ identifier: model.identifier
68+ busName: indicatorProperties.busName
69+ actionsObjectPath: indicatorProperties.actionsObjectPath
70+ menuObjectPath: indicatorProperties.menuObjectPath
71+ }
72+ }
73+ Component {
74+ id: tree
75+ IndicatorsTree {
76+ identifier: model.identifier
77+ busName: indicatorProperties.busName
78+ actionsObjectPath: indicatorProperties.actionsObjectPath
79+ menuObjectPath: indicatorProperties.menuObjectPath
6880 }
69− item.start();
7081 }
7182 }
7283
@@ -85,7 +96,7 @@ Page {
8596 left: parent.left
8697 }
8798 text: "Back"
88− onClicked: page.pageStack.reset()
99+ onClicked: root.pageStack.reset()
89100 }
90101 }
91102}
qml/Panel/Indicators/client/IndicatorsList.qml+3−4
@@ -22,7 +22,6 @@ import Ubuntu.Components 0.1
2222import Ubuntu.Components.ListItems 0.1 as ListItem
2323import Unity.Indicators 0.1 as Indicators
2424import "../.."
25−import "../../../Components/Flickables" as Flickables
2625
2726Page {
2827 id: page
@@ -36,7 +35,7 @@ Page {
3635 Component.onCompleted: load(profile)
3736 }
3837
39− Flickables.ListView {
38+ ListView {
4039 id: mainMenu
4140 objectName: "mainMenu"
4241 anchors.fill: parent
@@ -58,8 +57,8 @@ Page {
5857 text: identifier
5958
6059 onClicked: {
61− var new_page = Qt.createComponent("IndicatorsPage.qml");
62− page.pageStack.push(new_page.createObject(pages), {"indicatorProperties" : model.indicatorProperties, "pageSource" : model.pageSource});
60+ var new_page = Qt.createComponent("IndicatorRepresentation.qml");
61+ page.pageStack.push(new_page.createObject(pages), {"indicatorProperties" : model.indicatorProperties });
6362 }
6463 }
6564 }
qml/Panel/Indicators/client/IndicatorsTree.qml+5−28
@@ -20,40 +20,17 @@
2020import QtQuick 2.0
2121import Ubuntu.Components 0.1
2222import Unity.Indicators 0.1 as Indicators
23−import QMenuModel 0.1
24−import Ubuntu.Components.ListItems 0.1 as ListItem
25−import "../../../Components/Flickables" as Flickables
23+import ".."
2624
27−Page {
28− id: page
29− anchors.fill: parent
30−
31− property string busName: unityModel.busName
32− property string actionsObjectPath
33− property string menuObjectPath
34−
35− property string deviceMenuObjectPath: menuObjectPath
36−
37− function start() {
38− }
39−
40− UnityMenuModel {
41− id: unityModel
42− busName: page.busName
43− actions: { "indicator": page.actionsObjectPath }
44− menuObjectPath: page.deviceMenuObjectPath
45− }
46−
47− Indicators.RootActionState {
48− menu: unityModel
49− }
25+IndicatorBase {
26+ id: root
5027
5128 Indicators.ModelPrinter {
5229 id: printer
53− model: unityModel
30+ model: root.menuModel
5431 }
5532
56− Flickables.Flickable {
33+ Flickable {
5734 anchors.fill: parent
5835 contentHeight: all_data.height
5936 clip:true
qml/Panel/IndicatorsBar.qmlnew file+269
@@ -0,0 +1,269 @@
1+/*
2+ * Copyright (C) 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.2
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+Item {
22+ id: root
23+ property alias expanded: row.expanded
24+ property alias interactive: flickable.interactive
25+ property alias indicatorsModel: row.indicatorsModel
26+ property alias unitProgress: row.unitProgress
27+ property alias enableLateralChanges: row.enableLateralChanges
28+ property alias overFlowWidth: row.overFlowWidth
29+ readonly property alias currentItemIndex: row.currentItemIndex
30+ property real lateralPosition: -1
31+
32+ function selectItemAt(lateralPosition) {
33+ if (!expanded) {
34+ row.resetCurrentItem();
35+ }
36+ var mapped = root.mapToItem(row, lateralPosition, 0);
37+ row.selectItemAt(mapped.x);
38+ }
39+
40+ function setCurrentItemIndex(index) {
41+ if (!expanded) {
42+ row.resetCurrentItem();
43+ }
44+ row.setCurrentItemIndex(index);
45+ }
46+
47+ function addScrollOffset(scrollAmmout) {
48+ if (scrollAmmout < 0) { // left scroll
49+ if (flickable.contentX < 0) return; // already off the left.
50+ if (flickable.contentX + scrollAmmout < 0) scrollAmmout = -flickable.contentX; // going to be off the left
51+ } else { // right scroll
52+ if (flickable.contentX + flickable.width > row.width) return; // already off the right.
53+ if (flickable.contentX + flickable.width + scrollAmmout > row.width) { // going to be off the right
54+ scrollAmmout = row.width - (flickable.contentX + flickable.width);
55+ }
56+ }
57+ d.scrollOffset = d.scrollOffset + scrollAmmout;
58+ }
59+
60+ QtObject {
61+ id: d
62+ property var initialItem
63+ // the non-expanded distance from row offset to center of initial item
64+ property real originalDistanceFromRight: -1
65+
66+ // calculate the distance from row offset to center of initial item
67+ property real distanceFromRight: {
68+ if (originalDistanceFromRight == -1) return 0;
69+ if (!initialItem) return 0;
70+ return row.width - initialItem.x - initialItem.width /2;
71+ }
72+
73+ // offset to the intially selected expanded item
74+ property real rowOffset: 0
75+ property real scrollOffset: 0
76+ property real alignmentAdjustment: 0
77+ property real combinedOffset: 0
78+
79+ // when the scroll offset changes, we need to reclaculate the relative lateral position
80+ onScrollOffsetChanged: root.lateralPositionChanged()
81+
82+ onInitialItemChanged: {
83+ originalDistanceFromRight = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
84+ }
85+
86+ Behavior on alignmentAdjustment {
87+ NumberAnimation { duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing}
88+ }
89+
90+ function alignIndicators() {
91+ flickable.resetContentXComponents();
92+
93+ if (expanded && !flickable.moving) {
94+
95+ // gap between left and row?
96+ if (flickable.contentX < 0) {
97+ d.alignmentAdjustment += flickable.contentX;
98+ // gap between right and row?
99+ } else if (flickable.contentX + flickable.width > row.width) {
100+ // row width is less than flickable
101+ if (row.width < flickable.width) {
102+ d.alignmentAdjustment += flickable.contentX;
103+ } else {
104+ d.alignmentAdjustment += ((flickable.contentX + flickable.width) - row.width);
105+ }
106+ // current item overlap on left
107+ } else if (row.currentItem && row.currentItem.x < flickable.contentX) {
108+ d.alignmentAdjustment -= (row.currentItem.x - flickable.contentX);
109+ // current item overlap on right
110+ } else if (row.currentItem && row.currentItem.x + row.currentItem.width > flickable.contentX + flickable.width) {
111+ d.alignmentAdjustment -= ((row.currentItem.x + row.currentItem.width) - (flickable.contentX + flickable.width));
112+ }
113+ }
114+ }
115+ }
116+
117+ Rectangle {
118+ id: grayLine
119+ height: units.dp(2)
120+ width: parent.width
121+ anchors.bottom: parent.bottom
122+
123+ color: "#4c4c4c"
124+ opacity: expanded ? 1.0 : 0.0
125+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
126+ }
127+
128+ Item {
129+ id: rowContainer
130+ anchors.fill: parent
131+ clip: expanded || row.width > rowContainer.width
132+
133+ Flickable {
134+ id: flickable
135+ objectName: "flickable"
136+
137+ anchors.fill: parent
138+ contentWidth: row.width
139+ interactive: false
140+ // align right + offset from row selection + scrolling
141+ contentX: row.width - flickable.width - d.combinedOffset
142+
143+ // contentX can change by user interaction as well as user offset changes
144+ // This function re-aligns the offsets so that the offsets match the contentX
145+ function resetContentXComponents() {
146+ d.scrollOffset += (flickable.contentX - (row.width - flickable.width - d.combinedOffset));
147+ }
148+
149+ rebound: Transition {
150+ NumberAnimation {
151+ properties: "x"
152+ duration: 600
153+ easing.type: Easing.OutCubic
154+ }
155+ }
156+
157+ IndicatorItemRow {
158+ id: row
159+ objectName: "indicatorItemRow"
160+ anchors {
161+ top: parent.top
162+ bottom: parent.bottom
163+ }
164+
165+ lateralPosition: {
166+ if (root.lateralPosition == -1) return -1;
167+
168+ var mapped = root.mapToItem(row, root.lateralPosition, 0);
169+ return Math.min(Math.max(mapped.x, 0), row.width);
170+ }
171+
172+ onCurrentItemChanged: {
173+ if (!currentItem) d.initialItem = undefined;
174+ else if (!d.initialItem) d.initialItem = currentItem;
175+ }
176+
177+ MouseArea {
178+ anchors.fill: parent
179+ enabled: root.expanded
180+ onClicked: {
181+ row.selectItemAt(mouse.x);
182+ d.alignIndicators();
183+ }
184+ }
185+ }
186+
187+ }
188+ }
189+
190+ Timer {
191+ id: alignmentTimer
192+ interval: UbuntuAnimation.FastDuration // enough for row animation.
193+ repeat: false
194+
195+ onTriggered: d.alignIndicators();
196+ }
197+
198+ states: [
199+ State {
200+ name: "minimized"
201+ when: !expanded
202+ PropertyChanges {
203+ target: d
204+ rowOffset: 0
205+ scrollOffset: 0
206+ alignmentAdjustment: 0
207+ combinedOffset: 0
208+ restoreEntryValues: false
209+ }
210+ },
211+ State {
212+ name: "expanded"
213+ when: expanded && !interactive
214+
215+ PropertyChanges {
216+ target: d
217+ combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
218+ }
219+ PropertyChanges {
220+ target: d
221+ rowOffset: {
222+ if (!initialItem) return 0;
223+ if (distanceFromRight - initialItem.width <= 0) return 0;
224+
225+ var rowOffset = distanceFromRight - originalDistanceFromRight;
226+ return rowOffset;
227+ }
228+ restoreEntryValues: false
229+ }
230+ }
231+ , State {
232+ name: "interactive"
233+ when: expanded && interactive
234+
235+ StateChangeScript {
236+ script: {
237+ // don't use row offset anymore.
238+ d.scrollOffset -= d.rowOffset;
239+ d.rowOffset = 0;
240+ d.initialItem = undefined;
241+ alignmentTimer.start();
242+ }
243+ }
244+ PropertyChanges {
245+ target: d
246+ combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
247+ restoreEntryValues: false
248+ }
249+ }
250+ ]
251+
252+ transitions: [
253+ Transition {
254+ from: "expanded"
255+ to: "minimized"
256+ PropertyAction {
257+ target: d
258+ properties: "rowOffset, scrollOffset, alignmentAdjustment"
259+ value: 0
260+ }
261+ PropertyAnimation {
262+ target: d
263+ properties: "combinedOffset"
264+ duration: UbuntuAnimation.SnapDuration
265+ easing: UbuntuAnimation.StandardEasing
266+ }
267+ }
268+ ]
269+}
qml/Panel/IndicatorsMenu.qmlnew file+318
@@ -0,0 +1,318 @@
1+/*
2+ * Copyright (C) 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.2
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import "../Components"
21+
22+Showable {
23+ id: root
24+ property alias indicatorsModel: bar.indicatorsModel
25+ property alias showDragHandle: __showDragHandle
26+ property alias hideDragHandle: __hideDragHandle
27+ property alias overFlowWidth: bar.overFlowWidth
28+ property alias verticalVelocityThreshold: yVelocityCalculator.velocityThreshold
29+ property int minimizedPanelHeight: units.gu(3)
30+ property int expandedPanelHeight: units.gu(7)
31+ property real openedHeight: units.gu(71)
32+ readonly property real unitProgress: Math.max(0, (height - minimizedPanelHeight) / (openedHeight - minimizedPanelHeight))
33+ readonly property bool fullyOpened: unitProgress >= 1
34+ readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
35+ readonly property bool fullyClosed: unitProgress == 0
36+ property bool enableHint: true
37+ property bool contentEnabled: true
38+ property color panelColor: "black"
39+
40+ signal showTapped(point position)
41+
42+ // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
43+ // use its own values. Need to ask design about this.
44+ showAnimation: StandardAnimation {
45+ property: "height"
46+ to: openedHeight
47+ duration: UbuntuAnimation.BriskDuration
48+ easing.type: Easing.OutCubic
49+ }
50+
51+ hideAnimation: StandardAnimation {
52+ property: "height"
53+ to: minimizedPanelHeight
54+ duration: UbuntuAnimation.BriskDuration
55+ easing.type: Easing.OutCubic
56+ }
57+
58+ height: minimizedPanelHeight
59+
60+ onUnitProgressChanged: d.updateState()
61+ clip: root.partiallyOpened
62+
63+ // eater
64+ MouseArea {
65+ anchors.fill: parent
66+ }
67+
68+ MenuContent {
69+ id: content
70+ objectName: "menuContent"
71+
72+ anchors {
73+ left: parent.left
74+ right: parent.right
75+ top: bar.bottom
76+ }
77+ height: openedHeight - bar.height - handle.height
78+ indicatorsModel: root.indicatorsModel
79+ visible: root.unitProgress > 0
80+ enabled: contentEnabled
81+ currentMenuIndex: bar.currentItemIndex
82+ }
83+
84+ Handle {
85+ id: handle
86+ anchors {
87+ left: parent.left
88+ right: parent.right
89+ bottom: parent.bottom
90+ }
91+ height: units.gu(2)
92+ active: d.activeDragHandle ? true : false
93+
94+ //small shadow gradient at bottom of menu
95+ Rectangle {
96+ anchors {
97+ left: parent.left
98+ right: parent.right
99+ bottom: parent.top
100+ }
101+ height: units.gu(0.5)
102+ gradient: Gradient {
103+ GradientStop { position: 0.0; color: "transparent" }
104+ GradientStop { position: 1.0; color: "black" }
105+ }
106+ opacity: 0.3
107+ }
108+ }
109+
110+ Rectangle {
111+ anchors.fill: bar
112+ color: panelColor
113+ }
114+
115+ IndicatorsBar {
116+ id: bar
117+ objectName: "indicatorsBar"
118+
119+ anchors {
120+ left: parent.left
121+ right: parent.right
122+ }
123+ expanded: false
124+ enableLateralChanges: false
125+ lateralPosition: -1
126+ unitProgress: root.unitProgress
127+
128+ height: expanded ? expandedPanelHeight : minimizedPanelHeight
129+ Behavior on height { NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing } }
130+ }
131+
132+ ScrollCalculator {
133+ id: leftScroller
134+ width: units.gu(5)
135+ anchors.left: bar.left
136+ height: bar.height
137+
138+ forceScrollingPercentage: 0.33
139+ stopScrollThreshold: units.gu(0.75)
140+ direction: Qt.RightToLeft
141+ lateralPosition: -1
142+
143+ onScroll: bar.addScrollOffset(-scrollAmount);
144+ }
145+
146+ ScrollCalculator {
147+ id: rightScroller
148+ width: units.gu(5)
149+ anchors.right: bar.right
150+ height: bar.height
151+
152+ forceScrollingPercentage: 0.33
153+ stopScrollThreshold: units.gu(0.75)
154+ direction: Qt.LeftToRight
155+ lateralPosition: -1
156+
157+ onScroll: bar.addScrollOffset(scrollAmount);
158+ }
159+
160+ DragHandle {
161+ id: __showDragHandle
162+ anchors.bottom: parent.bottom
163+ anchors.left: parent.left
164+ anchors.right: parent.right
165+ height: minimizedPanelHeight
166+ direction: Direction.Downwards
167+ enabled: !root.shown && root.available
168+ autoCompleteDragThreshold: maxTotalDragDistance / 2
169+ stretch: true
170+ distanceThreshold: enableHint ? 0 : minimizedPanelHeight
171+
172+ onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
173+
174+ // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
175+ overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
176+ maxTotalDragDistance: openedHeight - (enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height)
177+ hintDisplacement: enableHint ? expandedPanelHeight - minimizedPanelHeight + handle.height : 0
178+ }
179+
180+ DragHandle {
181+ id: __hideDragHandle
182+ anchors.fill: handle
183+ direction: Direction.Upwards
184+ enabled: root.shown && root.available
185+ hintDisplacement: units.gu(3)
186+ autoCompleteDragThreshold: maxTotalDragDistance / 6
187+ stretch: true
188+ maxTotalDragDistance: openedHeight - expandedPanelHeight - handle.height
189+
190+ onTouchSceneXChanged: {
191+ if (root.state === "locked") {
192+ d.xDisplacementSinceLock += (touchSceneX - d.lastHideTouchSceneX)
193+ d.lastHideTouchSceneX = touchSceneX;
194+ }
195+ }
196+ }
197+
198+ PanelVelocityCalculator {
199+ id: yVelocityCalculator
200+ velocityThreshold: d.hasCommitted ? 0.1 : 0.3
201+ trackedValue: d.activeDragHandle ? d.activeDragHandle.touchSceneY : 0
202+
203+ onVelocityAboveThresholdChanged: d.updateState()
204+ }
205+
206+ Connections {
207+ target: showAnimation
208+ onRunningChanged: {
209+ if (showAnimation.running) {
210+ root.state = "commit";
211+ }
212+ }
213+ }
214+
215+ Connections {
216+ target: hideAnimation
217+ onRunningChanged: {
218+ if (hideAnimation.running) {
219+ root.state = "initial";
220+ }
221+ }
222+ }
223+
224+ QtObject {
225+ id: d
226+ property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
227+ property bool hasCommitted: false
228+ property real lastHideTouchSceneX: 0
229+ property real xDisplacementSinceLock: 0
230+ onXDisplacementSinceLockChanged: d.updateState()
231+
232+ property real rowMappedLateralPosition: {
233+ if (!d.activeDragHandle) return -1;
234+ return d.activeDragHandle.mapToItem(bar, d.activeDragHandle.touchX, 0).x;
235+ }
236+
237+ function updateState() {
238+ if (!showAnimation.running && !hideAnimation.running) {
239+ if (unitProgress <= 0) {
240+ root.state = "initial";
241+ // lock indicator if we've been committed and aren't moving too much laterally or too fast up.
242+ } else if (d.hasCommitted && (Math.abs(d.xDisplacementSinceLock) < units.gu(2) || yVelocityCalculator.velocityAboveThreshold)) {
243+ root.state = "locked";
244+ } else {
245+ root.state = "reveal";
246+ }
247+ }
248+ }
249+ }
250+
251+ states: [
252+ State {
253+ name: "initial"
254+ PropertyChanges { target: d; hasCommitted: false; restoreEntryValues: false }
255+ },
256+ State {
257+ name: "reveal"
258+ StateChangeScript {
259+ script: {
260+ yVelocityCalculator.reset();
261+ // initial item selection
262+ if (!d.hasCommitted) bar.selectItemAt(d.activeDragHandle ? d.activeDragHandle.touchX : -1);
263+ d.hasCommitted = false;
264+ }
265+ }
266+ PropertyChanges {
267+ target: bar
268+ expanded: true
269+ // changes to lateral touch position effect which indicator is selected
270+ lateralPosition: d.rowMappedLateralPosition
271+ // vertical velocity determines if changes in lateral position has an effect
272+ enableLateralChanges: d.activeDragHandle &&
273+ !yVelocityCalculator.velocityAboveThreshold
274+ }
275+ // left scroll bar handling
276+ PropertyChanges {
277+ target: leftScroller
278+ lateralPosition: {
279+ if (!d.activeDragHandle) return -1;
280+ var mapped = d.activeDragHandle.mapToItem(leftScroller, d.activeDragHandle.touchX, 0);
281+ return mapped.x;
282+ }
283+ }
284+ // right scroll bar handling
285+ PropertyChanges {
286+ target: rightScroller
287+ lateralPosition: {
288+ if (!d.activeDragHandle) return -1;
289+ var mapped = d.activeDragHandle.mapToItem(rightScroller, d.activeDragHandle.touchX, 0);
290+ return mapped.x;
291+ }
292+ }
293+ },
294+ State {
295+ name: "locked"
296+ StateChangeScript {
297+ script: {
298+ d.xDisplacementSinceLock = 0;
299+ d.lastHideTouchSceneX = hideDragHandle.touchSceneX;
300+ }
301+ }
302+ PropertyChanges { target: bar; expanded: true }
303+ },
304+ State {
305+ name: "commit"
306+ extend: "locked"
307+ PropertyChanges { target: bar; interactive: true }
308+ PropertyChanges {
309+ target: d;
310+ hasCommitted: true
311+ lastHideTouchSceneX: 0
312+ xDisplacementSinceLock: 0
313+ restoreEntryValues: false
314+ }
315+ }
316+ ]
317+ state: "initial"
318+}
qml/Panel/MenuContent.qml+26−83
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,126 +14,69 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
17+import QtQuick 2.2
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.1
2020import Unity.Indicators 0.1 as Indicators
2121import Utils 0.1
2222import "../Components"
23−import "../Components/Flickables" as Flickables
2423import "Indicators"
2524
2625Rectangle {
2726 id: content
2827
2928 property QtObject indicatorsModel: null
30− readonly property alias currentMenuIndex: listViewHeader.currentIndex
29+ property int currentMenuIndex: -1
3130 color: "#221e1c" // FIXME not in palette yet
32− property real headerHeight: listViewHeader.height
3331
3432 width: units.gu(40)
3533 height: units.gu(42)
3634
37− function setCurrentMenuIndex(index, animate) {
38− // FIXME - https://bugreports.qt-project.org/browse/QTBUG-41229
39− listViewHeader.currentIndex = -1;
40− listViewHeader.currentIndex = index;
35+ onCurrentMenuIndexChanged: {
36+ listViewContent.currentIndex = currentMenuIndex;
4137 }
4238
43− Flickables.ListView {
44− id: listViewHeader
45− objectName: "indicatorsHeaderListView"
46− model: content.indicatorsModel
47− clip: true
48−
49− anchors {
50− left: parent.left
51− right: parent.right
52− }
53− height: units.gu(8.5)
54−
55− highlightFollowsCurrentItem: true
56− highlightMoveDuration: 0
57−
58− orientation: ListView.Horizontal
59− snapMode: ListView.SnapOneItem
60− highlightRangeMode: ListView.StrictlyEnforceRange
61− boundsBehavior: Flickable.StopAtBounds
62− // Load all the indicator menus (a big number)
63− cacheBuffer: 1073741823
64−
65− delegate: Header {
66− width: ListView.view.width
67− height: implicitHeight
68−
69− title: indicatorDelegate.title !== "" ? indicatorDelegate.title : identifier
70−
71− IndicatorDelegate {
72− id: indicatorDelegate
73− Component.onCompleted: {
74− for(var pName in indicatorProperties) {
75− if (indicatorDelegate.hasOwnProperty(pName)) {
76− indicatorDelegate[pName] = indicatorProperties[pName];
77− }
78− }
79− }
80− }
81− }
82− }
83−
84− Flickables.ListView {
39+ ListView {
8540 id: listViewContent
8641 objectName: "indicatorsContentListView"
87− anchors {
88− left: parent.left
89− right: parent.right
90− top: listViewHeader.bottom
91− bottom: parent.bottom
92− }
42+ anchors.fill: parent
9343 model: content.indicatorsModel
94− clip: true
9544
96− currentIndex: listViewHeader.currentIndex
45+ highlightFollowsCurrentItem: true
9746 interactive: false
9847 highlightMoveDuration: 0
9948 orientation: ListView.Horizontal
10049 // Load all the indicator menus (a big number)
10150 cacheBuffer: 1073741823
10251
52+ // for additions/removals.
53+ onCountChanged: {
54+ listViewContent.currentIndex = content.currentMenuIndex;
55+ }
56+
10357 delegate: Loader {
10458 id: loader
59+
10560 width: ListView.view.width
10661 height: ListView.view.height
10762 objectName: identifier
108−
109− source: pageSource
11063 asynchronous: true
64+ visible: ListView.isCurrentItem
11165
112− onVisibleChanged: {
113− // Reset the indicator states
114− if (!visible && item && item["reset"]) {
115− item.reset()
116− }
117− }
118−
119− onLoaded: {
120− for(var pName in indicatorProperties) {
121− if (item.hasOwnProperty(pName)) {
122− item[pName] = indicatorProperties[pName]
123− }
124− }
125− }
66+ sourceComponent: IndicatorPage {
67+ objectName: identifier + "-page"
12668
127− Binding {
128− target: loader.item
129− property: "identifier"
130− value: identifier
69+ identifier: model.identifier
70+ busName: indicatorProperties.busName
71+ actionsObjectPath: indicatorProperties.actionsObjectPath
72+ menuObjectPath: indicatorProperties.menuObjectPath
13173 }
13274
133− Binding {
134− target: loader.item
135− property: "objectName"
136− value: identifier + "-page"
75+ onVisibleChanged: {
76+ // Reset the indicator states
77+ if (!visible && status == Loader.Ready) {
78+ item.reset();
79+ }
13780 }
13881 }
13982 }
qml/Panel/Panel.qml+48−41
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,11 +14,11 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
17+import QtQuick 2.2
18+import Ubuntu.Components 1.1
1919import Unity.Application 0.1
2020import "../Components"
21−import "../Components/ListItems"
21+import ".."
2222
2323Item {
2424 id: root
@@ -53,7 +53,9 @@ Item {
5353
5454 anchors.fill: parent
5555
56− Behavior on anchors.topMargin { StandardAnimation {} }
56+ Behavior on anchors.topMargin {
57+ NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
58+ }
5759
5860 BorderImage {
5961 id: dropShadow
@@ -62,24 +64,10 @@ Item {
6264 leftMargin: -units.gu(1)
6365 bottomMargin: -units.gu(1)
6466 }
65− visible: indicators.height > indicators.panelHeight
67+ visible: !indicators.fullyClosed
6668 source: "graphics/rectangular_dropshadow.sci"
6769 }
6870
69− VerticalThinDivider {
70− id: indicatorDividor
71− anchors {
72− top: indicators.top
73− bottom: indicators.bottom
74− right: indicators.left
75−
76− topMargin: indicatorArea.anchors.topMargin + indicators.panelHeight
77− }
78−
79− width: units.dp(2)
80− source: "graphics/VerticalDivider.png"
81− }
82−
8371 Rectangle {
8472 id: indicatorAreaBackground
8573 color: callHint.visible ? "green" : "black"
@@ -88,13 +76,13 @@ Item {
8876 left: parent.left
8977 right: parent.right
9078 }
91− height: indicators.panelHeight
79+ height: indicators.minimizedPanelHeight
9280
9381 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
9482 }
9583
9684 PanelSeparatorLine {
97− id: nonIndicatorAreaSeparatorLine
85+ id: orangeLine
9886 anchors {
9987 top: indicatorAreaBackground.bottom
10088 left: parent.left
@@ -103,18 +91,29 @@ Item {
10391 saturation: 1 - indicators.unitProgress
10492 }
10593
94+ Image {
95+ anchors {
96+ top: indicators.top
97+ bottom: indicators.bottom
98+ right: indicators.left
99+ topMargin: indicatorArea.anchors.topMargin + indicators.minimizedPanelHeight
100+ }
101+ width: units.dp(2)
102+ source: "graphics/VerticalDivider.png"
103+ }
104+
106105 MouseArea {
107106 anchors {
108107 top: parent.top
109108 left: parent.left
110109 right: indicators.left
111110 }
112− height: indicators.panelHeight
111+ height: indicators.minimizedPanelHeight
113112 enabled: callHint.visible
114113 onClicked: callHint.showLiveCall()
115114 }
116115
117− Indicators {
116+ IndicatorsMenu {
118117 id: __indicators
119118 objectName: "indicators"
120119
@@ -123,51 +122,55 @@ Item {
123122 right: parent.right
124123 }
125124
126− width: root.width
127125 shown: false
128− panelHeight: units.gu(3)
129− openedHeight: root.height
126+ width: root.width
127+ minimizedPanelHeight: units.gu(3)
128+ expandedPanelHeight: units.gu(7)
129+ openedHeight: root.height - indicatorOrangeLine.height
130+
130131 overFlowWidth: {
131132 if (callHint.visible) {
132133 return Math.max(root.width - (callHint.width + units.gu(2)), 0)
133134 }
134135 return root.width
135136 }
136−
137137 enableHint: !callHint.active && !fullscreenMode
138− showHintBottomMargin: fullscreenMode ? -panelHeight : 0
138+ panelColor: indicatorAreaBackground.color
139139
140140 onShowTapped: {
141141 if (callHint.active) {
142142 callHint.showLiveCall();
143143 }
144144 }
145− }
146145
147− ActiveCallHint {
148− id: __callHint
149− anchors {
150− top: parent.top
151− left: parent.left
146+ hideDragHandle {
147+ anchors.bottomMargin: -indicatorOrangeLine.height
152148 }
153− height: indicators.panelHeight
154− visible: active && indicators.state == "initial"
155149 }
156150
157151 PanelSeparatorLine {
158− id: indicatorsSeparatorLine
159− visible: true
152+ id: indicatorOrangeLine
160153 anchors {
161154 top: indicators.bottom
162− left: indicatorDividor.left
155+ left: indicators.left
163156 right: indicators.right
164157 }
165158 }
159+
160+ ActiveCallHint {
161+ id: __callHint
162+ anchors {
163+ top: parent.top
164+ left: parent.left
165+ }
166+ height: indicators.minimizedPanelHeight
167+ visible: active && indicators.state == "initial"
168+ }
166169 }
167170
168171 QtObject {
169172 id: d
170− readonly property real indicatorHeight: indicators.panelHeight + indicatorsSeparatorLine.height
173+ readonly property real indicatorHeight: indicators.minimizedPanelHeight + indicatorOrangeLine.height
171174 }
172175
173176 states: [
@@ -186,6 +189,10 @@ Item {
186189 target: indicatorArea;
187190 anchors.topMargin: indicators.state === "initial" ? -d.indicatorHeight : 0
188191 }
192+ PropertyChanges {
193+ target: indicators.showDragHandle;
194+ anchors.bottomMargin: -units.gu(1)
195+ }
189196 }
190197 ]
191198}
qml/Panel/PanelVelocityCalculator.qmlnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 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.2
18+import Ubuntu.Gestures 0.1
19+
20+Item {
21+ id: root
22+ property real trackedValue: 0
23+ property bool velocityAboveThreshold: false
24+ property real velocityThreshold: 0.4
25+
26+ function reset() {
27+ velocityTimer.stop();
28+ velocityAboveThreshold = false;
29+ calc.reset();
30+ }
31+
32+ function update() {
33+ calc.trackedPosition = trackedValue;
34+ velocityAboveThreshold = Math.abs(calc.calculate()) > velocityThreshold;
35+
36+ if (velocityAboveThreshold) { // only start timer if we're above the threshold.
37+ velocityTimer.start();
38+ } else {
39+ velocityTimer.stop();
40+ }
41+ }
42+
43+ onTrackedValueChanged: update();
44+
45+ AxisVelocityCalculator {
46+ id: calc
47+ }
48+
49+ Timer {
50+ id: velocityTimer
51+ interval: 50
52+ onTriggered: update();
53+ }
54+}
qml/Shell.qml+39−12
@@ -36,6 +36,7 @@ import "Panel"
3636import "Components"
3737import "Notifications"
3838import "Stages"
39+import "Panel/Indicators"
3940import Unity.Notifications 1.0 as NotificationBackend
4041import Unity.Session 0.1
4142import Unity.DashCommunicator 0.1
@@ -243,12 +244,12 @@ Item {
243244 target: applicationsDisplayLoader.item
244245 property: "maximizedAppTopMargin"
245246 // Not just using panel.panelHeight as that changes depending on the focused app.
246− value: panel.indicators.panelHeight
247+ value: panel.indicators.minimizedPanelHeight + units.dp(2) // dp(2) for orange line
247248 }
248249 Binding {
249250 target: applicationsDisplayLoader.item
250251 property: "interactive"
251− value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
252+ value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
252253 }
253254 Binding {
254255 target: applicationsDisplayLoader.item
@@ -370,6 +371,10 @@ Item {
370371 onHideGreeter: greeter.login()
371372
372373 onShowPrompt: {
374+ shell.enabled = true;
375+ if (!LightDM.Greeter.active) {
376+ return; // could happen if hideGreeter() comes in before we prompt
377+ }
373378 if (greeter.narrowMode) {
374379 if (isDefaultPrompt) {
375380 if (lockscreen.alphaNumeric) {
@@ -390,6 +395,9 @@ Item {
390395 }
391396
392397 onPromptlessChanged: {
398+ if (!LightDM.Greeter.active) {
399+ return; // could happen if hideGreeter() comes in before we prompt
400+ }
393401 if (greeter.narrowMode) {
394402 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
395403 lockscreen.hide()
@@ -401,6 +409,7 @@ Item {
401409 }
402410
403411 onAuthenticationComplete: {
412+ shell.enabled = true;
404413 if (LightDM.Greeter.authenticated) {
405414 AccountsService.failedLogins = 0
406415 }
@@ -537,6 +546,11 @@ Item {
537546
538547 onShownChanged: {
539548 if (shown) {
549+ // Disable everything so that user can't swipe greeter or
550+ // launcher until we get first prompt/authenticate, which
551+ // will re-enable the shell.
552+ shell.enabled = false;
553+
540554 if (greeter.narrowMode) {
541555 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
542556 } else {
@@ -547,10 +561,9 @@ Item {
547561 }
548562 }
549563
550− /* TODO re-enable when the corresponding changes in the service land (LP: #1361074)
551564 Component.onCompleted: {
552565 Connectivity.unlockAllModems()
553− } */
566+ }
554567
555568 onUnlocked: greeter.hide()
556569 onSelected: {
@@ -594,7 +607,8 @@ Item {
594607 target: Powerd
595608
596609 onStatusChanged: {
597− if (Powerd.status === Powerd.Off && !callManager.hasCalls && !edgeDemo.running) {
610+ if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
611+ !callManager.hasCalls && !edgeDemo.running) {
598612 greeter.showNow()
599613 }
600614 }
@@ -610,7 +624,7 @@ Item {
610624 }
611625
612626 var animate = !LightDM.Greeter.active && !stages.shown
613− dash.setCurrentScope("clickscope", animate, false)
627+ dash.setCurrentScope(0, animate, false)
614628 ApplicationManager.requestFocusApplication("unity8-dash")
615629 }
616630
@@ -646,7 +660,20 @@ Item {
646660 available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
647661 contentEnabled: edgeDemo.panelContentEnabled
648662 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
649− panelHeight: units.gu(3)
663+
664+ minimizedPanelHeight: units.gu(3)
665+ expandedPanelHeight: units.gu(7)
666+
667+ indicatorsModel: visibleIndicators.model
668+ }
669+
670+ VisibleIndicators {
671+ id: visibleIndicators
672+ // TODO: This should be sourced by device type (eg "desktop", "tablet", "phone"...)
673+ Component.onCompleted: initialise(indicatorProfile)
674+ }
675+ callHint {
676+ greeterShown: greeter.shown || lockscreen.shown
650677 }
651678
652679 property bool topmostApplicationIsFullscreen:
@@ -673,7 +700,7 @@ Item {
673700 onDash: showDash()
674701 onDashSwipeChanged: {
675702 if (dashSwipe) {
676− dash.setCurrentScope("clickscope", false, true)
703+ dash.setCurrentScope(0, false, true)
677704 }
678705 }
679706 onLauncherApplicationSelected: {
@@ -693,7 +720,7 @@ Item {
693720 Rectangle {
694721 id: modalNotificationBackground
695722
696− visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
723+ visible: notifications.useModal && (notifications.state == "narrow")
697724 color: "#000000"
698725 anchors.fill: parent
699726 opacity: 0.9
@@ -709,9 +736,9 @@ Item {
709736 model: NotificationBackend.Model
710737 margin: units.gu(1)
711738
712− y: panel.panelHeight
739+ y: topmostIsFullscreen ? 0 : panel.panelHeight
713740 width: parent.width
714− height: parent.height - panel.panelHeight
741+ height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
715742
716743 states: [
717744 State {
@@ -763,7 +790,7 @@ Item {
763790 paused: Powerd.status === Powerd.Off // Saves power
764791 greeter: greeter
765792 launcher: launcher
766− indicators: panel.indicators
793+ panel: panel
767794 stages: stages
768795 }
769796
qml/Stages/PhoneStage.qml+7−3
@@ -21,7 +21,6 @@ import Unity.Application 0.1
2121import Unity.Session 0.1
2222import Utils 0.1
2323import "../Components"
24−import "../Components/Flickables" as Flickables
2524
2625Rectangle {
2726 id: root
@@ -121,7 +120,7 @@ Rectangle {
121120
122121 }
123122
124− Flickables.Flickable {
123+ Flickable {
125124 id: spreadView
126125 objectName: "spreadView"
127126 anchors.fill: parent
@@ -246,12 +245,13 @@ Rectangle {
246245 }
247246 }
248247
249− Item {
248+ MouseArea {
250249 id: spreadRow
251250 // This width controls how much the spread can be flicked left/right. It's composed of:
252251 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
253252 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
254253 width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
254+ height: parent.height
255255 Behavior on width {
256256 enabled: spreadView.closingIndex >= 0
257257 UbuntuNumberAnimation {}
@@ -264,6 +264,10 @@ Rectangle {
264264
265265 x: spreadView.contentX
266266
267+ onClicked: {
268+ spreadView.snapTo(0);
269+ }
270+
267271 Repeater {
268272 id: spreadRepeater
269273 model: ApplicationManager
qml/Stages/TabletStage.qml+6−3
@@ -20,7 +20,6 @@ import Ubuntu.Gestures 0.1
2020import Unity.Application 0.1
2121import Utils 0.1
2222import "../Components"
23−import "../Components/Flickables" as Flickables
2423
2524Rectangle {
2625 id: root
@@ -154,7 +153,7 @@ Rectangle {
154153 }
155154 }
156155
157− Flickables.Flickable {
156+ Flickable {
158157 id: spreadView
159158 anchors.fill: parent
160159 interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
@@ -386,12 +385,16 @@ Rectangle {
386385 }
387386 }
388387
389− Item {
388+ MouseArea {
390389 id: spreadRow
391390 x: spreadView.contentX
392391 height: root.height
393392 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
394393
394+ onClicked: {
395+ spreadView.snapTo(0);
396+ }
397+
395398 Rectangle {
396399 id: sideStageBackground
397400 color: "black"
src/libunity8-private/CMakeLists.txt+2
@@ -6,6 +6,8 @@ project(lib${LIB_NAME})
66
77set(lib${LIB_NAME}_SRCS
88 abstractdbusservicemonitor.cpp
9+ unitydbusobject.cpp
10+ unitydbusvirtualobject.cpp
911 )
1012
1113add_library(${LIB_NAME} SHARED
src/libunity8-private/unitydbusobject.cppnew file+86
@@ -0,0 +1,86 @@
1+/*
2+ * Copyright (C) 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+#include "unitydbusobject.h"
18+
19+#include <QDebug>
20+#include <QDBusMessage>
21+#include <QMetaClassInfo>
22+#include <QTimer>
23+
24+UnityDBusObject::UnityDBusObject(const QString &path, const QString &service, bool async, QObject *parent)
25+ : QObject(parent)
26+ , m_connection(QDBusConnection::sessionBus())
27+ , m_path(path)
28+ , m_service(service)
29+{
30+ if (async) {
31+ // Use a zero-timer to let Qml finish loading before we announce on DBus
32+ QTimer::singleShot(0, this, SLOT(registerObject()));
33+ } else {
34+ registerObject();
35+ }
36+}
37+
38+UnityDBusObject::~UnityDBusObject()
39+{
40+ // Leave service in place because multiple objects may be registered with
41+ // the same service. But we know we own the object path and can unregister it.
42+ m_connection.unregisterObject(path());
43+}
44+
45+QDBusConnection UnityDBusObject::connection() const
46+{
47+ return m_connection;
48+}
49+
50+QString UnityDBusObject::path() const
51+{
52+ return m_path;
53+}
54+
55+// Manually emit a PropertiesChanged signal over DBus, because QtDBus
56+// doesn't do it for us on Q_PROPERTIES, oddly enough.
57+void UnityDBusObject::notifyPropertyChanged(const QString& propertyName, const QVariant &value)
58+{
59+ QDBusMessage message;
60+ QString interface;
61+ QVariantMap changedProps;
62+
63+ interface = metaObject()->classInfo(metaObject()->indexOfClassInfo("D-Bus Interface")).value();
64+ changedProps.insert(propertyName, value);
65+
66+ message = QDBusMessage::createSignal(path(),
67+ "org.freedesktop.DBus.Properties",
68+ "PropertiesChanged");
69+ message << interface;
70+ message << changedProps;
71+ message << QStringList();
72+
73+ connection().send(message);
74+}
75+
76+void UnityDBusObject::registerObject()
77+{
78+ if (!m_connection.registerObject(m_path, this, QDBusConnection::ExportScriptableContents)) {
79+ qWarning() << "Unable to register DBus object" << m_path;
80+ }
81+ if (!m_service.isEmpty()) {
82+ if (!m_connection.registerService(m_service)) {
83+ qWarning() << "Unable to register DBus service" << m_service;
84+ }
85+ }
86+}
src/libunity8-private/unitydbusobject.hnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 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+#ifndef UNITYDBUSOBJECT_H
18+#define UNITYDBUSOBJECT_H
19+
20+#include <QDBusConnection>
21+#include <QObject>
22+
23+class Q_DECL_EXPORT UnityDBusObject : public QObject
24+{
25+ Q_OBJECT
26+
27+public:
28+ explicit UnityDBusObject(const QString &path, const QString &service = QString(), bool async = true, QObject *parent = 0);
29+ ~UnityDBusObject();
30+
31+ QDBusConnection connection() const;
32+ QString path() const;
33+
34+protected:
35+ void notifyPropertyChanged(const QString& propertyName, const QVariant &value);
36+
37+private Q_SLOTS:
38+ void registerObject();
39+
40+private:
41+ QDBusConnection m_connection;
42+ QString m_path;
43+ QString m_service;
44+};
45+
46+#endif // UNITYDBUSOBJECT_H
src/libunity8-private/unitydbusvirtualobject.cppnew file+83
@@ -0,0 +1,83 @@
1+/*
2+ * Copyright (C) 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+#include "unitydbusvirtualobject.h"
18+
19+#include <QDebug>
20+#include <QDBusMessage>
21+#include <QTimer>
22+
23+UnityDBusVirtualObject::UnityDBusVirtualObject(const QString &path, const QString &service, bool async, QObject *parent)
24+ : QDBusVirtualObject(parent)
25+ , m_connection(QDBusConnection::sessionBus())
26+ , m_path(path)
27+ , m_service(service)
28+{
29+ if (async) {
30+ // Use a zero-timer to let Qml finish loading before we announce on DBus
31+ QTimer::singleShot(0, this, SLOT(registerObject()));
32+ } else {
33+ registerObject();
34+ }
35+}
36+
37+UnityDBusVirtualObject::~UnityDBusVirtualObject()
38+{
39+ // Leave service in place because multiple objects may be registered with
40+ // the same service. But we know we own the object path and can unregister it.
41+ m_connection.unregisterObject(path());
42+}
43+
44+QDBusConnection UnityDBusVirtualObject::connection() const
45+{
46+ return m_connection;
47+}
48+
49+QString UnityDBusVirtualObject::path() const
50+{
51+ return m_path;
52+}
53+
54+// Manually emit a PropertiesChanged signal over DBus, because QtDBus
55+// doesn't do it for us on Q_PROPERTIES, oddly enough.
56+void UnityDBusVirtualObject::notifyPropertyChanged(const QString& interface, const QString& node, const QString& propertyName, const QVariant &value)
57+{
58+ QDBusMessage message;
59+ QVariantMap changedProps;
60+
61+ changedProps.insert(propertyName, value);
62+
63+ message = QDBusMessage::createSignal(path() + "/" + node,
64+ "org.freedesktop.DBus.Properties",
65+ "PropertiesChanged");
66+ message << interface;
67+ message << changedProps;
68+ message << QStringList();
69+
70+ connection().send(message);
71+}
72+
73+void UnityDBusVirtualObject::registerObject()
74+{
75+ if (!m_connection.registerVirtualObject(m_path, this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
76+ qWarning() << "Unable to register DBus object" << m_path;
77+ }
78+ if (!m_service.isEmpty()) {
79+ if (!m_connection.registerService(m_service)) {
80+ qWarning() << "Unable to register DBus service" << m_service;
81+ }
82+ }
83+}
src/libunity8-private/unitydbusvirtualobject.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 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+#ifndef UNITYDBUSVIRTUALOBJECT_H
18+#define UNITYDBUSVIRTUALOBJECT_H
19+
20+#include <QDBusConnection>
21+#include <QDBusVirtualObject>
22+#include <QObject>
23+
24+class Q_DECL_EXPORT UnityDBusVirtualObject : public QDBusVirtualObject
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit UnityDBusVirtualObject(const QString &path, const QString &service = QString(), bool async = true, QObject *parent = 0);
30+ ~UnityDBusVirtualObject();
31+
32+ QDBusConnection connection() const;
33+ QString path() const;
34+
35+protected:
36+ void notifyPropertyChanged(const QString& interface, const QString& node, const QString& propertyName, const QVariant &value);
37+
38+private Q_SLOTS:
39+ void registerObject();
40+
41+private:
42+ QDBusConnection m_connection;
43+ QString m_path;
44+ QString m_service;
45+};
46+
47+#endif // UNITYDBUSVIRTUALOBJECT_H
tests/autopilot/unity8/indicators/tests/test_indicators.py+1−1
@@ -61,7 +61,7 @@ class IndicatorExistsTestCase(IndicatorTestCase):
6161 self.skipTest('Nexus 10 does not have bluetooth at the moment.')
6262
6363 def test_indicator_exists(self):
64− self.main_window._get_indicator_widget(
64+ self.main_window._get_indicator_panel_item(
6565 self.indicator_name
6666 )
6767
tests/autopilot/unity8/shell/emulators/dash.py+4
@@ -69,6 +69,10 @@ class Dash(emulators.UnityEmulatorBase):
6969 return self.dash_content_list.wait_select_single(
7070 'QQuickLoader', scopeId=scope_name)
7171
72+ def get_scope_by_index(self, scope_index=0):
73+ return self.dash_content_list.wait_select_single(
74+ 'QQuickLoader', objectName=("scopeLoader%i" % scope_index))
75+
7276 @autopilot_logging.log_action(logger.info)
7377 def open_scope(self, scope_id):
7478 """Open a dash scope.
tests/autopilot/unity8/shell/emulators/main_window.py+6−6
@@ -75,15 +75,15 @@ class QQuickView(emulators.UnityEmulatorBase):
7575 def get_pinentryField(self):
7676 return self.select_single(objectName="pinentryField")
7777
78− def _get_indicator_widget(self, indicator_name):
78+ def _get_indicator_panel_item(self, indicator_name):
7979 return self.select_single(
80− 'DefaultIndicatorWidget',
81− objectName=indicator_name+'-widget'
80+ 'IndicatorItem',
81+ objectName=indicator_name+'-panelItem'
8282 )
8383
8484 def _get_indicator_page(self, indicator_name):
8585 return self.select_single(
86− 'DefaultIndicatorPage',
86+ 'IndicatorPage',
8787 objectName=indicator_name+'-page'
8888 )
8989
@@ -93,12 +93,12 @@ class QQuickView(emulators.UnityEmulatorBase):
9393
9494 :returns: The indicator page.
9595 """
96− widget = self._get_indicator_widget(indicator_name)
96+ widget = self._get_indicator_panel_item(indicator_name)
9797 start_x, start_y = input.get_center_point(widget)
9898 end_x = start_x
9999 end_y = self.height
100100 self.pointing_device.drag(start_x, start_y, end_x, end_y)
101− self.wait_select_single('Indicators', fullyOpened=True)
101+ self.wait_select_single('IndicatorsMenu', fullyOpened=True)
102102 return self._get_indicator_page(indicator_name)
103103
104104 @autopilot_logging.log_action(logger.info)
tests/autopilot/unity8/shell/tests/__init__.py+1−1
@@ -447,7 +447,7 @@ class DashBaseTestCase(AutopilotTestCase):
447447 return launch_dash_app_fixture.application_proxy
448448
449449 def wait_for_dash(self):
450− home_scope = self.dash.get_scope('clickscope')
450+ home_scope = self.dash.get_scope_by_index(0)
451451 # FIXME! There is a huge timeout here for when we're doing CI on
452452 # VMs. See lp:1203715
453453 self.assertThat(
tests/autopilot/unity8/shell/tests/test_notifications.py+6−5
@@ -248,7 +248,7 @@ class InteractiveNotificationBase(NotificationsBase):
248248 self.assert_notification_action_id_was_called("action_accept")
249249
250250 def test_modal_sd_without_greeter(self):
251− """Snap-decision should block input to shell without greeter."""
251+ """Snap-decision should block input to shell without greeter/lockscreen."""
252252 unity_proxy = self.launch_unity()
253253 unlock_unity(unity_proxy)
254254
@@ -295,7 +295,7 @@ class InteractiveNotificationBase(NotificationsBase):
295295 self.assert_notification_action_id_was_called("action_accept")
296296
297297 def test_modal_sd_with_greeter(self):
298− """A snap-decision should not block input to the greeter beneath it."""
298+ """A snap-decision should block input to the greeter/lockscreen beneath it."""
299299 self.launch_unity()
300300
301301 summary = "Incoming file"
@@ -322,11 +322,12 @@ class InteractiveNotificationBase(NotificationsBase):
322322 hints
323323 )
324324
325− # verify that we can swipe away the greeter (interact with the "shell")
325+ # verify that we cannot reveal the launcher (no longer interact with
326+ # the shell)
326327 time.sleep(1)
327328 self.main_window.show_dash_swiping()
328− greeter = self.main_window.get_greeter()
329− self.assertThat(greeter.shown, Eventually(Equals(False)))
329+ self.assertThat(
330+ self.main_window.is_launcher_open, Eventually(Equals(False)))
330331
331332 # verify and interact with the triggered snap-decision notification
332333 notify_list = self._get_notifications_list()
tests/mocks/AccountsService/AccountsService.qmltypes+2
@@ -23,6 +23,8 @@ Module {
2323 }
2424 Property { name: "user"; type: "string" }
2525 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "enableLauncherWhileLocked"; type: "bool" }
27+ Property { name: "enableIndicatorsWhileLocked"; type: "bool" }
2628 Property { name: "backgroundFile"; type: "string" }
2729 Property { name: "statsWelcomeScreen"; type: "bool" }
2830 Property { name: "passwordDisplayHint"; type: "PasswordDisplayHint"; isReadonly: true }
tests/mocks/LightDM/CMakeLists.txt+2
@@ -15,6 +15,7 @@ include_directories(
1515 ${CMAKE_CURRENT_BINARY_DIR}
1616 ${CMAKE_SOURCE_DIR}/plugins/Utils
1717 ${CMAKE_SOURCE_DIR}/tests/mocks/libusermetrics
18+ ${libunity8-private_SOURCE_DIR}
1819)
1920
2021set(QMLPLUGIN_SRC
@@ -40,6 +41,7 @@ target_link_libraries(MockLightDM-qml
4041 -llightdm-qt5-2
4142 -L${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics
4243 -lusermetricsoutput
44+ unity8-private
4345 )
4446
4547qt5_use_modules(MockLightDM-qml DBus Gui Qml)
tests/mocks/LightDM/LightDM.qmltypes+1
@@ -37,6 +37,7 @@ Module {
3737 Signal { name: "isActiveChanged" }
3838 Signal { name: "isAuthenticatedChanged" }
3939 Signal { name: "showGreeter" }
40+ Signal { name: "hideGreeter" }
4041 Signal {
4142 name: "requestAuthenticationUser"
4243 Parameter { name: "user"; type: "string" }
tests/mocks/QMenuModel/QMenuModel.qmltypes+1−1
@@ -18,12 +18,12 @@ Module {
1818 prototype: "QAbstractListModel"
1919 exports: ["QMenuModel/UnityMenuModel 0.1"]
2020 exportMetaObjectRevisions: [0]
21− Property { name: "modelData"; type: "QVariant" }
2221 Property { name: "busName"; type: "QByteArray" }
2322 Property { name: "actions"; type: "QVariantMap" }
2423 Property { name: "menuObjectPath"; type: "QByteArray" }
2524 Property { name: "actionStateParser"; type: "ActionStateParser"; isPointer: true }
2625 Property { name: "nameOwner"; type: "string"; isReadonly: true }
26+ Property { name: "modelData"; type: "QVariant" }
2727 Method {
2828 name: "insertRow"
2929 Parameter { name: "row"; type: "int" }
tests/mocks/QMenuModel/unitymenumodel.cpp+1−2
@@ -50,13 +50,12 @@ void UnityMenuModel::setModelData(const QVariant& data)
5050{
5151 beginResetModel();
5252
53− m_modelData.clear();
5453 m_modelData = data.toList();
54+ Q_EMIT modelDataChanged();
5555
5656 endResetModel();
5757}
5858
59−
6059void UnityMenuModel::insertRow(int row, const QVariant& data)
6160{
6261 row = qMin(row, rowCount());
tests/mocks/QMenuModel/unitymenumodel.h+6−2
@@ -27,13 +27,15 @@ class ActionStateParser;
2727class Q_DECL_EXPORT UnityMenuModel : public QAbstractListModel
2828{
2929 Q_OBJECT
30− Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
3130 Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged)
3231 Q_PROPERTY(QVariantMap actions READ actions WRITE setActions NOTIFY actionsChanged)
3332 Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged)
3433 Q_PROPERTY(ActionStateParser* actionStateParser READ actionStateParser WRITE setActionStateParser NOTIFY actionStateParserChanged)
3534 Q_PROPERTY(QString nameOwner READ nameOwner NOTIFY nameOwnerChanged)
3635
36+ // internal mock properties
37+ Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
38+
3739public:
3840 UnityMenuModel(QObject *parent = nullptr);
3941 virtual ~UnityMenuModel();
@@ -81,9 +83,11 @@ Q_SIGNALS:
8183 void actionsChanged();
8284 void menuObjectPathChanged();
8385 void actionStateParserChanged();
84− void modelDataChanged();
8586 void nameOwnerChanged();
8687
88+ // Internal mock usage
89+ void modelDataChanged();
90+
8791private:
8892 QVariantMap rowData(int row) const;
8993 QVariant subMenuData(int row) const;
tests/mocks/Ubuntu/Payments/Payments.qmltypes+2
@@ -37,6 +37,8 @@ Module {
3737 Parameter { name: "error"; type: "string" }
3838 }
3939 Signal { name: "purchaseCompleted" }
40+ Signal { name: "purchaseCancelled" }
4041 Method { name: "start" }
42+ Method { name: "process" }
4143 }
4244}
tests/mocks/Ubuntu/Telephony/Telephony.qmltypes+2−1
@@ -22,7 +22,8 @@ Module {
2222 exportMetaObjectRevisions: [0]
2323 Property { name: "phoneNumber"; type: "string" }
2424 Property { name: "isConference"; type: "bool" }
25− Property { name: "elapsedTime"; type: "int"; isReadonly: true }
25+ Property { name: "elapsedTime"; type: "int" }
26+ Property { name: "elapsedTimerRunning"; type: "bool" }
2627 }
2728 Component {
2829 name: "MockCallManager"
tests/mocks/Unity/Application/Application.qmltypes+13
@@ -238,6 +238,7 @@ Module {
238238 Property { name: "state"; type: "State"; isReadonly: true }
239239 Property { name: "name"; type: "string"; isReadonly: true }
240240 Property { name: "live"; type: "bool"; isReadonly: true }
241+ Property { name: "orientation"; type: "Qt::ScreenOrientation" }
241242 Signal {
242243 name: "typeChanged"
243244 Parameter { type: "Type" }
@@ -268,9 +269,14 @@ Module {
268269 exportMetaObjectRevisions: [0]
269270 Property { name: "name"; type: "string"; isReadonly: true }
270271 Property { name: "surface"; type: "MirSurfaceItem"; isReadonly: true; isPointer: true }
272+ Property { name: "application"; type: "ApplicationInfo"; isReadonly: true; isPointer: true }
271273 Property { name: "parentSession"; type: "Session"; isReadonly: true; isPointer: true }
272274 Property { name: "childSessions"; type: "SessionModel"; isReadonly: true; isPointer: true }
273275 Property { name: "live"; type: "bool"; isReadonly: true }
276+ Signal {
277+ name: "applicationChanged"
278+ Parameter { type: "ApplicationInfo"; isPointer: true }
279+ }
274280 Signal {
275281 name: "surfaceChanged"
276282 Parameter { type: "MirSurfaceItem"; isPointer: true }
@@ -368,6 +374,12 @@ Module {
368374 Property { name: "stage"; type: "Stage"; isReadonly: true }
369375 Property { name: "state"; type: "State"; isReadonly: true }
370376 Property { name: "focused"; type: "bool"; isReadonly: true }
377+ Property { name: "splashTitle"; type: "string"; isReadonly: true }
378+ Property { name: "splashImage"; type: "QUrl"; isReadonly: true }
379+ Property { name: "splashShowHeader"; type: "bool"; isReadonly: true }
380+ Property { name: "splashColor"; type: "QColor"; isReadonly: true }
381+ Property { name: "splashColorHeader"; type: "QColor"; isReadonly: true }
382+ Property { name: "splashColorFooter"; type: "QColor"; isReadonly: true }
371383 Signal {
372384 name: "nameChanged"
373385 Parameter { name: "name"; type: "string" }
@@ -414,6 +426,7 @@ Module {
414426 Property { name: "count"; type: "int"; isReadonly: true }
415427 Property { name: "focusedApplicationId"; type: "string"; isReadonly: true }
416428 Property { name: "suspended"; type: "bool" }
429+ Property { name: "forceDashActive"; type: "bool" }
417430 Signal {
418431 name: "focusRequested"
419432 Parameter { name: "appId"; type: "string" }
tests/mocks/Unity/DashCommunicator/DashCommunicator.qmltypes+6−6
@@ -14,13 +14,13 @@ Module {
1414 exportMetaObjectRevisions: [0]
1515 Signal {
1616 name: "setCurrentScopeCalled"
17− Parameter { name: "scopeId"; type: "string" }
17+ Parameter { name: "index"; type: "int" }
1818 Parameter { name: "animate"; type: "bool" }
1919 Parameter { name: "reset"; type: "bool" }
2020 }
2121 Method {
2222 name: "setCurrentScope"
23− Parameter { name: "scopeId"; type: "string" }
23+ Parameter { name: "index"; type: "int" }
2424 Parameter { name: "animate"; type: "bool" }
2525 Parameter { name: "reset"; type: "bool" }
2626 }
@@ -32,15 +32,15 @@ Module {
3232 exportMetaObjectRevisions: [0]
3333 Signal {
3434 name: "setCurrentScopeRequested"
35− Parameter { name: "scopeId"; type: "string" }
35+ Parameter { name: "index"; type: "int" }
3636 Parameter { name: "animate"; type: "bool" }
37− Parameter { name: "reset"; type: "bool" }
37+ Parameter { name: "isSwipe"; type: "bool" }
3838 }
3939 Method {
4040 name: "mockSetCurrentScope"
41− Parameter { name: "scopeId"; type: "string" }
41+ Parameter { name: "index"; type: "int" }
4242 Parameter { name: "animate"; type: "bool" }
43− Parameter { name: "reset"; type: "bool" }
43+ Parameter { name: "isSwipe"; type: "bool" }
4444 }
4545 }
4646}
tests/mocks/Unity/DashCommunicator/dashcommunicator.cpp+2−2
@@ -31,7 +31,7 @@ DashCommunicator::~DashCommunicator()
3131}
3232
3333
34−void DashCommunicator::setCurrentScope(const QString &scopeId, bool animate, bool reset)
34+void DashCommunicator::setCurrentScope(int index, bool animate, bool reset)
3535{
36− Q_EMIT setCurrentScopeCalled(scopeId, animate, reset);
36+ Q_EMIT setCurrentScopeCalled(index, animate, reset);
3737}
tests/mocks/Unity/DashCommunicator/dashcommunicator.h+2−2
@@ -29,11 +29,11 @@ public:
2929 ~DashCommunicator();
3030
3131public Q_SLOTS:
32− void setCurrentScope(const QString &scopeId, bool animate, bool reset);
32+ void setCurrentScope(int index, bool animate, bool reset);
3333
3434Q_SIGNALS:
3535 // This mock just emits calls back to the QML api for the plugin to verify calls
36− void setCurrentScopeCalled(const QString &scopeId, bool animate, bool reset);
36+ void setCurrentScopeCalled(int index, bool animate, bool reset);
3737};
3838
3939#endif
tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.cpp+2−2
@@ -27,7 +27,7 @@ DashCommunicatorService::~DashCommunicatorService()
2727
2828}
2929
30−void DashCommunicatorService::mockSetCurrentScope(const QString &scopeId, bool animate, bool isSwipe)
30+void DashCommunicatorService::mockSetCurrentScope(int index, bool animate, bool isSwipe)
3131{
32− Q_EMIT setCurrentScopeRequested(scopeId, animate, isSwipe);
32+ Q_EMIT setCurrentScopeRequested(index, animate, isSwipe);
3333}
tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.h+2−2
@@ -27,10 +27,10 @@ public:
2727 ~DashCommunicatorService();
2828
2929Q_SIGNALS:
30− void setCurrentScopeRequested(const QString &scopeId, bool animate, bool isSwipe);
30+ void setCurrentScopeRequested(int index, bool animate, bool isSwipe);
3131
3232public Q_SLOTS:
33− void mockSetCurrentScope(const QString &scopeId, bool animate, bool isSwipe);
33+ void mockSetCurrentScope(int index, bool animate, bool isSwipe);
3434};
3535
3636#endif // DBUSUNITYSESSIONSERVICE_H
tests/mocks/Unity/Indicators/CMakeLists.txt+3−10
@@ -12,24 +12,17 @@ include_directories(
1212
1313set(IndicatorsFakeQml_SOURCES
1414 fakeplugin.cpp
15+ fakeindicatorsmodel.cpp
16+ fakeunitymenumodelcache.cpp
1517 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
1618 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/sharedunitymenumodel.cpp
1720 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelcache.cpp
1821 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelstack.cpp
1922 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/visibleindicatorsmodel.cpp
20− fakeindicatorsmodel.cpp
21−)
22−
23−set(IndicatorsFakeQml_RESOURCES
24− indicators_fake.qrc
25−)
26−
27−qt5_add_resources(IndicatorsFakeQml_RESOURCES_RCC
28− ${IndicatorsFakeQml_RESOURCES}
2923)
3024
3125add_library(IndicatorsFakeQml SHARED
32− ${IndicatorsFakeQml_RESOURCES_RCC}
3326 ${IndicatorsFakeQml_SOURCES}
3427)
3528add_definitions(-DUNITYINDICATORS_LIBRARY)
tests/mocks/Unity/Indicators/FakeMenuPage.qmldeleted−27
@@ -1,27 +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 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−
19−Flickable {
20− objectName: "fakeMenuPlugin"
21− // Make it compatible with the PluginItem interface
22− function reset() {
23− if (shell != undefined && shell.indicator_status != undefined) {
24− shell.indicator_status[objectName].reset++;
25− }
26− }
27−}
tests/mocks/Unity/Indicators/Indicators.qmltypes+43−15
@@ -29,6 +29,7 @@ Module {
2929 exports: ["Unity.Indicators/FakeIndicatorsModel 0.1"]
3030 exportMetaObjectRevisions: [0]
3131 Property { name: "count"; type: "int"; isReadonly: true }
32+ Property { name: "modelData"; type: "QVariant" }
3233 Method {
3334 name: "load"
3435 Parameter { name: "profile"; type: "string" }
@@ -38,6 +39,15 @@ Module {
3839 name: "append"
3940 Parameter { name: "row"; type: "QVariantMap" }
4041 }
42+ Method {
43+ name: "insert"
44+ Parameter { name: "row"; type: "int" }
45+ Parameter { name: "data"; type: "QVariantMap" }
46+ }
47+ Method {
48+ name: "remove"
49+ Parameter { name: "row"; type: "int" }
50+ }
4151 Method {
4252 name: "data"
4353 type: "QVariant"
@@ -45,6 +55,27 @@ Module {
4555 Parameter { name: "role"; type: "int" }
4656 }
4757 }
58+ Component {
59+ name: "FakeUnityMenuModelCache"
60+ prototype: "UnityMenuModelCache"
61+ exports: ["Unity.Indicators/UnityMenuModelCache 0.1"]
62+ isCreatable: false
63+ isSingleton: true
64+ exportMetaObjectRevisions: [0]
65+ Method {
66+ name: "setCachedModelData"
67+ Parameter { name: "bus"; type: "QByteArray" }
68+ Parameter { name: "path"; type: "QByteArray" }
69+ Parameter { name: "actions"; type: "QVariantMap" }
70+ Parameter { name: "data"; type: "QVariant" }
71+ }
72+ Method {
73+ name: "setCachedModelData"
74+ Parameter { name: "bus"; type: "QByteArray" }
75+ Parameter { name: "path"; type: "QByteArray" }
76+ Parameter { name: "actions"; type: "QVariantMap" }
77+ }
78+ }
4879 Component {
4980 name: "FlatMenuProxyModelRole"
5081 prototype: "QObject"
@@ -74,10 +105,8 @@ Module {
74105 values: {
75106 "Identifier": 0,
76107 "Position": 1,
77− "WidgetSource": 2,
78− "PageSource": 3,
79− "IndicatorProperties": 4,
80− "IsVisible": 5
108+ "IndicatorProperties": 2,
109+ "IsVisible": 3
81110 }
82111 }
83112 }
@@ -160,19 +189,18 @@ Module {
160189 }
161190 Component { name: "QIdentityProxyModel"; prototype: "QAbstractProxyModel" }
162191 Component {
163− name: "UnityMenuModelCache"
192+ name: "SharedUnityMenuModel"
164193 prototype: "QObject"
165− exports: ["Unity.Indicators/UnityMenuModelCache 0.1"]
166− isCreatable: false
167− isSingleton: true
194+ exports: ["Unity.Indicators/SharedUnityMenuModel 0.1"]
168195 exportMetaObjectRevisions: [0]
169− Method {
170− name: "model"
171− type: "UnityMenuModel*"
172− Parameter { name: "bus"; type: "QByteArray" }
173− Parameter { name: "path"; type: "QByteArray" }
174− Parameter { name: "actions"; type: "QVariantMap" }
175− }
196+ Property { name: "busName"; type: "QByteArray" }
197+ Property { name: "menuObjectPath"; type: "QByteArray" }
198+ Property { name: "actions"; type: "QVariantMap" }
199+ Property { name: "model"; type: "UnityMenuModel"; isReadonly: true; isPointer: true }
200+ }
201+ Component {
202+ name: "UnityMenuModelCache"
203+ prototype: "QObject"
176204 Method {
177205 name: "contains"
178206 type: "bool"
tests/mocks/Unity/Indicators/IndicatorsModel.qml+152−34
@@ -18,43 +18,161 @@ import QtQuick 2.0
1818import Unity.Indicators 0.1 as Indicators
1919
2020Indicators.FakeIndicatorsModel {
21+ id: root
22+
23+ property var originalModelData: [
24+ {
25+ "identifier": "fake-indicator-bluetooth",
26+ "indicatorProperties": {
27+ "enabled": true,
28+ "busName": "com.canonical.indicators.fake1",
29+ "menuObjectPath": "/com/canonical/indicators/fake1",
30+ "actionsObjectPath": "/com/canonical/indicators/fake1"
31+ }
32+ },
33+ {
34+ "identifier": "fake-indicator-network",
35+ "indicatorProperties": {
36+ "enabled": true,
37+ "busName": "com.canonical.indicators.fake2",
38+ "menuObjectPath": "/com/canonical/indicators/fake2",
39+ "actionsObjectPath": "/com/canonical/indicators/fake2"
40+ }
41+ },
42+ {
43+ "identifier": "fake-indicator-messages",
44+ "indicatorProperties": {
45+ "enabled": true,
46+ "busName": "com.canonical.indicators.fake3",
47+ "menuObjectPath": "/com/canonical/indicators/fake3",
48+ "actionsObjectPath": "/com/canonical/indicators/fake3"
49+ }
50+ },
51+ {
52+ "identifier": "fake-indicator-sound",
53+ "indicatorProperties": {
54+ "enabled": true,
55+ "busName": "com.canonical.indicators.fake4",
56+ "menuObjectPath": "/com/canonical/indicators/fake4",
57+ "actionsObjectPath": "/com/canonical/indicators/fake4"
58+ }
59+ },
60+ {
61+ "identifier": "fake-indicator-power",
62+ "indicatorProperties": {
63+ "enabled": true,
64+ "busName": "com.canonical.indicators.fake5",
65+ "menuObjectPath": "/com/canonical/indicators/fake5",
66+ "actionsObjectPath": "/com/canonical/indicators/fake5"
67+ }
68+ },
69+ {
70+ "identifier": "fake-indicator-datetime",
71+ "indicatorProperties": {
72+ "enabled": true,
73+ "busName": "com.canonical.indicators.fake6",
74+ "menuObjectPath": "/com/canonical/indicators/fake6",
75+ "actionsObjectPath": "/com/canonical/indicators/fake6"
76+ }
77+ }
78+ ]
2179
2280 function load(profile) {
2381 unload();
82+ root.modelData = originalModelData;
83+
84+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake1",
85+ getUnityMenuModelData("fake-indicator-bluetooth",
86+ "Bluetooth (F)",
87+ "",
88+ [ "image://theme/bluetooth-active" ]));
89+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake2",
90+ getUnityMenuModelData("fake-indicator-network",
91+ "Network (F)",
92+ "",
93+ [ "image://theme/simcard-error", "image://theme/wifi-high" ]));
94+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake3",
95+ getUnityMenuModelData("fake-indicator-messages",
96+ "Messages (F)",
97+ "",
98+ [ "image://theme/messages-new" ]));
99+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake4",
100+ getUnityMenuModelData("fake-indicator-sound",
101+ "Sound (F)",
102+ "",
103+ [ "image://theme/audio-volume-high" ]));
104+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake5",
105+ getUnityMenuModelData("fake-indicator-power",
106+ "Battery (F)",
107+ "",
108+ [ "image://theme/battery-020" ]));
109+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake6",
110+ getUnityMenuModelData("fake-indicator-datetime",
111+ "Upcoming Events (F)",
112+ "12:04",
113+ []));
114+ }
115+
116+ function getUnityMenuModelData(identifier, title, label, icons) {
117+ var root = [{
118+ "rowData": { // 1
119+ "label": "",
120+ "sensitive": true,
121+ "isSeparator": false,
122+ "icon": "",
123+ "type": "com.canonical.indicator.root",
124+ "ext": {},
125+ "action": "",
126+ "actionState": {
127+ "title": title,
128+ "label": label,
129+ "icons": icons,
130+ "visible": true
131+ },
132+ "isCheck": false,
133+ "isRadio": false,
134+ "isToggled": false
135+ },
136+ "submenu": []
137+ }];
138+
139+ var submenus = [];
140+ for (var i = 0; i < 8; i++) {
141+ var submenu = {
142+ "rowData": { // 1.1
143+ "label": identifier,
144+ "sensitive": true,
145+ "isSeparator": false,
146+ "icon": "",
147+ "type": undefined,
148+ "ext": {},
149+ "action": "",
150+ "actionState": {},
151+ "isCheck": false,
152+ "isRadio": false,
153+ "isToggled": false
154+ }};
155+ submenus.push(submenu);
156+ }
157+ root[0]["submenu"] = submenus;
158+
159+ return root;
160+ }
161+
162+ function setIndicatorVisible(identifier, visible) {
163+ for (var i = 0; i < originalModelData.length; i++) {
164+ if (originalModelData[i]["identifier"] === identifier) {
165+ var data = Indicators.UnityMenuModelCache.getCachedModelData(
166+ originalModelData[i]["indicatorProperties"]["menuObjectPath"]);
167+
168+ data[0]["rowData"]["actionState"]["visible"] = visible;
169+
170+ Indicators.UnityMenuModelCache.setCachedModelData(
171+ originalModelData[i]["indicatorProperties"]["menuObjectPath"],
172+ data);
173+ break;
174+ }
175+ }
24176
25− append({ "identifier": "indicator-fake1",
26− "position": 0,
27− "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget1.qml",
28− "pageSource": "qrc:/tests/indciators/qml/fake_menu_page1.qml",
29− "indicatorProperties": { enabled: true }
30− });
31−
32− append({ "identifier": "indicator-fake2",
33− "position": 1,
34− "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget2.qml",
35− "pageSource": "qrc:/tests/indciators/qml/fake_menu_page2.qml",
36− "indicatorProperties": { enabled: true }
37− });
38−
39− append({ "identifier": "indicator-fake3",
40− "position": 2,
41− "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget3.qml",
42− "pageSource": "qrc:/tests/indciators/qml/fake_menu_page3.qml",
43− "indicatorProperties": { enabled: true }
44− });
45−
46− append({ "identifier": "indicator-fake4",
47− "position": 3,
48− "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget4.qml",
49− "pageSource": "qrc:/tests/indciators/qml/fake_menu_page4.qml",
50− "indicatorProperties": { enabled: true }
51− });
52−
53− append({ "identifier": "indicator-fake5",
54− "position": 4,
55− "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget5.qml",
56− "pageSource": "qrc:/tests/indciators/qml/fake_menu_page5.qml",
57− "indicatorProperties": { enabled: true }
58− });
59177 }
60178}
tests/mocks/Unity/Indicators/RootActionState.qml+18−10
@@ -19,15 +19,23 @@
1919
2020import QtQuick 2.0
2121
22−QtObject {
23− property var menu
24− property bool valid: false
25− property string title
26− property string leftLabel
27− property string rightLabel
28− property var icons
29− property string accessibleName
30− property bool visible: true
22+Item {
23+ property var menu: null
24+ property bool valid: cachedState !== undefined
25+ property string title: cachedState && cachedState.hasOwnProperty("title") ? cachedState["title"] : ""
26+ property string leftLabel: cachedState && cachedState.hasOwnProperty("pre-label") ? cachedState["pre-label"] : ""
27+ property string rightLabel: cachedState && cachedState.hasOwnProperty("label") ? cachedState["label"] : ""
28+ property var icons: cachedState && cachedState.hasOwnProperty("icons") ? cachedState["icons"] : []
29+ property string accessibleName: cachedState && cachedState.hasOwnProperty("accessible-desc") ? cachedState["accessible-desc"] : ""
30+ property bool indicatorVisible: cachedState && cachedState.hasOwnProperty("visible") ? cachedState["visible"] : true
31+
32+ property var cachedState: menu ? menu.get(0, "actionState") : undefined
33+ Connections {
34+ target: menu
35+ onModelDataChanged: {
36+ cachedState = menu.get(0, "actionState");
37+ }
38+ }
3139
3240 signal updated
3341
@@ -37,5 +45,5 @@ QtObject {
3745 onRightLabelChanged: updated()
3846 onIconsChanged: updated()
3947 onAccessibleNameChanged: updated()
40− onVisibleChanged: updated()
48+ onIndicatorVisibleChanged: updated()
4149}
tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp+50−21
@@ -31,7 +31,6 @@ FakeIndicatorsModel::FakeIndicatorsModel(QObject *parent)
3131/*! \internal */
3232FakeIndicatorsModel::~FakeIndicatorsModel()
3333{
34− qDeleteAll(m_indicators);
3534}
3635
3736int FakeIndicatorsModel::count() const
@@ -47,31 +46,63 @@ void FakeIndicatorsModel::unload()
4746{
4847 beginResetModel();
4948
50− qDeleteAll(m_indicators);
51− m_indicators.clear();
49+ m_modelData.clear();
50+ Q_EMIT modelDataChanged();
5251
5352 endResetModel();
5453}
5554
5655
57−void FakeIndicatorsModel::append(const QVariantMap& row)
56+void FakeIndicatorsModel::append(const QVariantMap& data)
5857{
59− Indicator* new_row = new QHash<int, QVariant>();
60− for (auto iter = row.begin(); iter != row.end(); ++iter )
61− {
62− int key = roleNames().key(iter.key().toUtf8(), -1);
63− if (key != -1) {
64− new_row->insert(key, iter.value());
65− }
66− }
58+ QList<QVariant> allData = m_modelData.toList();
59+ beginInsertRows(QModelIndex(), allData.count(), allData.count());
60+
61+ allData.append(data);
62+ m_modelData = allData;
63+ Q_EMIT modelDataChanged();
6764
68− beginInsertRows(QModelIndex(), m_indicators.count(), m_indicators.count());
65+ endInsertRows();
66+}
6967
70− m_indicators.append(new_row);
68+void FakeIndicatorsModel::insert(int row, const QVariantMap& data)
69+{
70+ QList<QVariant> allData = m_modelData.toList();
71+ row = qMax(0, qMin(row, allData.count()));
72+
73+ beginInsertRows(QModelIndex(), row, row);
74+
75+ allData.insert(row, data);
76+ m_modelData = allData;
77+ Q_EMIT modelDataChanged();
7178
7279 endInsertRows();
7380}
7481
82+void FakeIndicatorsModel::remove(int row)
83+{
84+ QList<QVariant> allData = m_modelData.toList();
85+ row = qMax(0, qMin(row, allData.count()));
86+
87+ beginRemoveRows(QModelIndex(), row, row);
88+
89+ allData.removeAt(row);
90+ m_modelData = allData;
91+ Q_EMIT modelDataChanged();
92+
93+ endRemoveRows();
94+}
95+
96+void FakeIndicatorsModel::setModelData(const QVariant& modelData)
97+{
98+ beginResetModel();
99+
100+ m_modelData = modelData;
101+ Q_EMIT modelDataChanged();
102+
103+ endResetModel();
104+}
105+
75106QHash<int, QByteArray> FakeIndicatorsModel::roleNames() const
76107{
77108 static QHash<int, QByteArray> roles;
@@ -79,8 +110,6 @@ QHash<int, QByteArray> FakeIndicatorsModel::roleNames() const
79110 {
80111 roles[IndicatorsModelRole::Identifier] = "identifier";
81112 roles[IndicatorsModelRole::Position] = "position";
82− roles[IndicatorsModelRole::WidgetSource] = "widgetSource";
83− roles[IndicatorsModelRole::PageSource] = "pageSource";
84113 roles[IndicatorsModelRole::IndicatorProperties] = "indicatorProperties";
85114 }
86115 return roles;
@@ -91,18 +120,18 @@ int FakeIndicatorsModel::columnCount(const QModelIndex &) const
91120 return 1;
92121}
93122
94−Q_INVOKABLE QVariant FakeIndicatorsModel::data(int row, int role) const
123+QVariant FakeIndicatorsModel::data(int row, int role) const
95124{
96125 return data(index(row, 0), role);
97126}
98127
99128QVariant FakeIndicatorsModel::data(const QModelIndex &index, int role) const
100129{
101− if (!index.isValid() || index.row() >= m_indicators.size())
130+ QList<QVariant> dataList = m_modelData.toList();
131+ if (!index.isValid() || index.row() >= dataList.size())
102132 return QVariant();
103133
104− Indicator* indicator = m_indicators[index.row()];
105− return indicator->value(role, QVariant());
134+ return dataList[index.row()].toMap()[roleNames()[role]];
106135}
107136
108137QModelIndex FakeIndicatorsModel::parent(const QModelIndex&) const
@@ -112,5 +141,5 @@ QModelIndex FakeIndicatorsModel::parent(const QModelIndex&) const
112141
113142int FakeIndicatorsModel::rowCount(const QModelIndex&) const
114143{
115− return m_indicators.count();
144+ return m_modelData.toList().count();
116145}
tests/mocks/Unity/Indicators/fakeindicatorsmodel.h+8−2
@@ -27,6 +27,7 @@ class FakeIndicatorsModel : public QAbstractListModel
2727 Q_OBJECT
2828 Q_ENUMS(Roles)
2929 Q_PROPERTY(int count READ count NOTIFY countChanged)
30+ Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
3031public:
3132
3233 FakeIndicatorsModel(QObject *parent=0);
@@ -36,9 +37,14 @@ public:
3637 Q_INVOKABLE void unload();
3738
3839 Q_INVOKABLE void append(const QVariantMap& row);
40+ Q_INVOKABLE void insert(int row, const QVariantMap& data);
41+ Q_INVOKABLE void remove(int row);
3942
4043 Q_INVOKABLE QVariant data(int row, int role) const;
4144
45+ void setModelData(const QVariant& data);
46+ QVariant modelData() const { return m_modelData; }
47+
4248 /* QAbstractItemModel */
4349 QHash<int, QByteArray> roleNames() const;
4450 int columnCount(const QModelIndex &parent = QModelIndex()) const;
@@ -48,12 +54,12 @@ public:
4854
4955Q_SIGNALS:
5056 void countChanged();
57+ void modelDataChanged();
5158
5259private:
5360 int count() const;
5461
55− typedef QHash<int, QVariant> Indicator;
56− QList<Indicator*> m_indicators;
62+ QVariant m_modelData;
5763};
5864
5965#endif // FAKE_INDICATORSMODEL_H
tests/mocks/Unity/Indicators/fakeplugin.cpp+8−4
@@ -26,19 +26,22 @@
2626#include "fakeindicatorsmodel.h"
2727#include "indicators.h"
2828#include "menucontentactivator.h"
29−#include "unitymenumodelcache.h"
29+#include "sharedunitymenumodel.h"
30+#include "fakeunitymenumodelcache.h"
3031#include "unitymenumodelstack.h"
3132#include "visibleindicatorsmodel.h"
3233
34+#include <unitymenumodel.h>
35+
3336static QObject* menuModelCacheSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
3437 Q_UNUSED(engine);
3538 Q_UNUSED(scriptEngine);
36− return new UnityMenuModelCache;
39+ return FakeUnityMenuModelCache::singleton();
3740}
3841
3942void IndicatorsFakePlugin::registerTypes(const char * uri)
4043{
41− Q_INIT_RESOURCE(indicators_fake);
44+ qRegisterMetaType<UnityMenuModel*>("UnityMenuModel*");
4245
4346 // internal
4447 qmlRegisterType<FakeIndicatorsModel>(uri, 0, 1, "FakeIndicatorsModel");
@@ -47,8 +50,9 @@ void IndicatorsFakePlugin::registerTypes(const char * uri)
4750 qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
4851 qmlRegisterType<UnityMenuModelStack>(uri, 0, 1, "UnityMenuModelStack");
4952 qmlRegisterType<VisibleIndicatorsModel>(uri, 0, 1, "VisibleIndicatorsModel");
53+ qmlRegisterType<SharedUnityMenuModel>(uri, 0, 1, "SharedUnityMenuModel");
5054
51− qmlRegisterSingletonType<UnityMenuModelCache>(uri, 0, 1, "UnityMenuModelCache", menuModelCacheSingleton);
55+ qmlRegisterSingletonType<FakeUnityMenuModelCache>(uri, 0, 1, "UnityMenuModelCache", menuModelCacheSingleton);
5256
5357 // external uncreatables
5458 qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
tests/mocks/Unity/Indicators/fakeunitymenumodelcache.cppnew file+59
@@ -0,0 +1,59 @@
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 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+
17+#include "fakeunitymenumodelcache.h"
18+#include <unitymenumodel.h>
19+
20+QPointer<FakeUnityMenuModelCache> FakeUnityMenuModelCache::theFakeCache = nullptr;
21+
22+FakeUnityMenuModelCache* FakeUnityMenuModelCache::singleton()
23+{
24+ if (theFakeCache.isNull()) {
25+ theFakeCache = new FakeUnityMenuModelCache();
26+ }
27+ return theFakeCache.data();
28+}
29+
30+FakeUnityMenuModelCache::FakeUnityMenuModelCache(QObject* parent)
31+ : UnityMenuModelCache(parent)
32+{
33+}
34+
35+QSharedPointer<UnityMenuModel> FakeUnityMenuModelCache::model(const QByteArray& path)
36+{
37+ return UnityMenuModelCache::singleton()->model(path);
38+}
39+
40+bool FakeUnityMenuModelCache::contains(const QByteArray& path)
41+{
42+ return UnityMenuModelCache::singleton()->contains(path);
43+}
44+
45+void FakeUnityMenuModelCache::setCachedModelData(const QByteArray& path,
46+ const QVariant& data)
47+{
48+ // keep a ref forever!
49+ if (!m_models.contains(path)) {
50+ m_models[path] = model(path);
51+ }
52+ m_models[path]->setModelData(data);
53+}
54+
55+QVariant FakeUnityMenuModelCache::getCachedModelData(const QByteArray& path)
56+{
57+ QSharedPointer<UnityMenuModel> model = this->model(path);
58+ return model.isNull() ? QVariant() : model->modelData();
59+}
tests/mocks/Unity/Indicators/fakeunitymenumodelcache.hnew file+45
@@ -0,0 +1,45 @@
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 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+
17+#ifndef FAKEUNITYMENUMODELCACHE_H
18+#define FAKEUNITYMENUMODELCACHE_H
19+
20+#include "unitymenumodelcache.h"
21+
22+#include <QVariantMap>
23+
24+class FakeUnityMenuModelCache : public UnityMenuModelCache
25+{
26+ Q_OBJECT
27+public:
28+ FakeUnityMenuModelCache(QObject* parent = nullptr);
29+
30+ static FakeUnityMenuModelCache* singleton();
31+
32+ QSharedPointer<UnityMenuModel> model(const QByteArray& path) override;
33+ bool contains(const QByteArray& path) override;
34+
35+ Q_INVOKABLE void setCachedModelData(const QByteArray& path,
36+ const QVariant& data = QVariant());
37+
38+ Q_INVOKABLE QVariant getCachedModelData(const QByteArray& path);
39+
40+private:
41+ static QPointer<FakeUnityMenuModelCache> theFakeCache;
42+ QHash<QByteArray, QSharedPointer<UnityMenuModel>> m_models;
43+};
44+
45+#endif // FAKEUNITYMENUMODELCACHE_H
tests/mocks/Unity/Indicators/indicators_fake.qrcdeleted−14
@@ -1,14 +0,0 @@
1−<RCC>
2− <qresource prefix="/tests/indciators">
3− <file>qml/fake_menu_widget1.qml</file>
4− <file>qml/fake_menu_widget2.qml</file>
5− <file>qml/fake_menu_widget3.qml</file>
6− <file>qml/fake_menu_widget4.qml</file>
7− <file>qml/fake_menu_widget5.qml</file>
8− <file>qml/fake_menu_page1.qml</file>
9− <file>qml/fake_menu_page2.qml</file>
10− <file>qml/fake_menu_page3.qml</file>
11− <file>qml/fake_menu_page4.qml</file>
12− <file>qml/fake_menu_page5.qml</file>
13− </qresource>
14−</RCC>
tests/mocks/Unity/Indicators/qml/fake_menu_page1.qmldeleted−35
@@ -1,35 +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 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 Unity.Indicators 0.1 as Indicators
19−
20−Indicators.FakeMenuPage {
21− id: menu_plugin1
22−
23− Rectangle {
24− id: contents
25− color: "red"
26−
27− height: 150
28− width: 150
29−
30− anchors {
31− horizontalCenter: parent.horizontalCenter
32− verticalCenter: parent.verticalCenter
33− }
34− }
35−}
tests/mocks/Unity/Indicators/qml/fake_menu_page2.qmldeleted−35
@@ -1,35 +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 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 Unity.Indicators 0.1 as Indicators
19−
20−Indicators.FakeMenuPage {
21− id: menu_plugin2
22−
23− Rectangle {
24− id: contents
25− color: "blue"
26−
27− height: 150
28− width: 150
29−
30− anchors {
31− horizontalCenter: parent.horizontalCenter
32− verticalCenter: parent.verticalCenter
33− }
34− }
35−}
tests/mocks/Unity/Indicators/qml/fake_menu_page3.qmldeleted−35
@@ -1,35 +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 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 Unity.Indicators 0.1 as Indicators
19−
20−Indicators.FakeMenuPage {
21− id: menu_plugin3
22−
23− Rectangle {
24− id: contents
25− color: "yellow"
26−
27− height: 150
28− width: 150
29−
30− anchors {
31− horizontalCenter: parent.horizontalCenter
32− verticalCenter: parent.verticalCenter
33− }
34− }
35−}
tests/mocks/Unity/Indicators/qml/fake_menu_page4.qmldeleted−35
@@ -1,35 +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 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 Unity.Indicators 0.1 as Indicators
19−
20−Indicators.FakeMenuPage {
21− id: menu_plugin4
22−
23− Rectangle {
24− id: contents
25− color: "green"
26−
27− height: 150
28− width: 150
29−
30− anchors {
31− horizontalCenter: parent.horizontalCenter
32− verticalCenter: parent.verticalCenter
33− }
34− }
35−}
tests/mocks/Unity/Indicators/qml/fake_menu_page5.qmldeleted−35
@@ -1,35 +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 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 Unity.Indicators 0.1 as Indicators
19−
20−Indicators.FakeMenuPage {
21− id: menu_plugin5
22−
23− Rectangle {
24− id: contents
25− color: "orange"
26−
27− height: 150
28− width: 150
29−
30− anchors {
31− horizontalCenter: parent.horizontalCenter
32− verticalCenter: parent.verticalCenter
33− }
34− }
35−}
tests/mocks/Unity/Indicators/qml/fake_menu_widget1.qmldeleted−23
@@ -1,23 +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 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−
19−Rectangle {
20− width: 40
21− height: 40
22− color : "red"
23−}
tests/mocks/Unity/Indicators/qml/fake_menu_widget2.qmldeleted−23
@@ -1,23 +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 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−
19−Rectangle {
20− width: 40
21− height: 40
22− color : "blue"
23−}
tests/mocks/Unity/Indicators/qml/fake_menu_widget4.qmldeleted−23
@@ -1,23 +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 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−
19−Rectangle {
20− width: 40
21− height: 40
22− color : "green"
23−}
tests/mocks/Unity/Indicators/qml/fake_menu_widget5.qmldeleted−23
@@ -1,23 +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 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−
19−Rectangle {
20− width: 40
21− height: 40
22− color : "orange"
23−}
tests/mocks/Unity/Indicators/qmldir−1
@@ -3,5 +3,4 @@ plugin IndicatorsFakeQml
33typeinfo Indicators.qmltypes
44
55IndicatorsModel 0.1 IndicatorsModel.qml
6−FakeMenuPage 0.1 FakeMenuPage.qml
76RootActionState 0.1 RootActionState.qml
tests/mocks/Unity/Launcher/Launcher.qmltypes+13
@@ -85,6 +85,7 @@ Module {
8585 Property { name: "recent"; type: "bool"; isReadonly: true }
8686 Property { name: "progress"; type: "int"; isReadonly: true }
8787 Property { name: "count"; type: "int"; isReadonly: true }
88+ Property { name: "countVisible"; type: "bool"; isReadonly: true }
8889 Property { name: "focused"; type: "bool"; isReadonly: true }
8990 Property {
9091 name: "quickList"
@@ -92,6 +93,14 @@ Module {
9293 isReadonly: true
9394 isPointer: true
9495 }
96+ Signal {
97+ name: "nameChanged"
98+ Parameter { name: "name"; type: "string" }
99+ }
100+ Signal {
101+ name: "iconChanged"
102+ Parameter { name: "icon"; type: "string" }
103+ }
95104 Signal {
96105 name: "pinnedChanged"
97106 Parameter { name: "pinned"; type: "bool" }
@@ -112,6 +121,10 @@ Module {
112121 name: "countChanged"
113122 Parameter { name: "count"; type: "int" }
114123 }
124+ Signal {
125+ name: "countVisibleChanged"
126+ Parameter { name: "countVisible"; type: "bool" }
127+ }
115128 Signal {
116129 name: "focusedChanged"
117130 Parameter { name: "focused"; type: "bool" }
tests/mocks/Unity/Unity.qmltypes+32
@@ -30,6 +30,25 @@ Module {
3030 Parameter { name: "row"; type: "int" }
3131 Parameter { name: "role"; type: "int" }
3232 }
33+ Method {
34+ name: "setCount"
35+ Parameter { name: "count"; type: "int" }
36+ }
37+ Method {
38+ name: "resultModel"
39+ type: "ResultsModel*"
40+ Parameter { name: "row"; type: "int" }
41+ }
42+ Method {
43+ name: "setLayout"
44+ Parameter { name: "row"; type: "int" }
45+ Parameter { name: "layout"; type: "string" }
46+ }
47+ Method {
48+ name: "setHeaderLink"
49+ Parameter { name: "row"; type: "int" }
50+ Parameter { name: "headerLink"; type: "string" }
51+ }
3352 }
3453 Component {
3554 name: "PreviewModel"
@@ -66,6 +85,10 @@ Module {
6685 exports: ["Unity/FakeResultsModel 0.2", "Unity/ResultsModel 0.2"]
6786 isCreatable: false
6887 exportMetaObjectRevisions: [0, 0]
88+ Method {
89+ name: "setResultCount"
90+ Parameter { name: "result_count"; type: "int" }
91+ }
6992 }
7093 Component {
7194 name: "Scope"
@@ -76,6 +99,15 @@ Module {
7699 name: "performQuery"
77100 Parameter { name: "query"; type: "string" }
78101 }
102+ Signal { name: "refreshed" }
103+ Method {
104+ name: "setId"
105+ Parameter { name: "id"; type: "string" }
106+ }
107+ Method {
108+ name: "setName"
109+ Parameter { name: "name"; type: "string" }
110+ }
79111 Method {
80112 name: "setSearchInProgress"
81113 Parameter { name: "inProg"; type: "bool" }
tests/plugins/Dash/cardcreator/1.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1719readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1820Item {
tests/plugins/Dash/cardcreator/2.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618Loader {
1719 id: backgroundLoader;
1820 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/3.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1719readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1820Item {
tests/plugins/Dash/cardcreator/4.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618readonly property size artShapeSize: Qt.size(-1, -1);
1719readonly property int headerHeight: row.height;
1820Row {
tests/plugins/Dash/cardcreator/5.res+3−1
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1719readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1820Item {
@@ -143,7 +145,7 @@ UbuntuShape {
143145 id: touchdown;
144146 objectName: "touchdown";
145147 anchors { fill: artShapeHolder }
146− visible: root.pressed;
148+ visible: root.pressed;
147149 radius: "medium";
148150 borderSource: "radius_pressed.sci"
149151}
tests/plugins/Dash/cardcreator/6.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618Loader {
1719 id: backgroundLoader;
1820 objectName: "backgroundLoader";
tests/plugins/Dash/cardcreator/7.res+2
@@ -13,6 +13,8 @@ AbstractButton {
1313 property bool asynchronous: true;
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
16+ enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17+
1618Loader {
1719 id: backgroundLoader;
1820 objectName: "backgroundLoader";
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -45,6 +45,7 @@ endmacro(add_gesture_test)
4545
4646add_gesture_ui_test(DirectionalDragArea)
4747add_gesture_ui_test(PressedOutsideNotifier)
48+add_gesture_ui_test(TouchDispatcher)
4849add_gesture_ui_test(TouchGate)
4950add_gesture_test(Damper)
5051add_gesture_test(AxisVelocityCalculator)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+70
@@ -21,6 +21,8 @@
2121#include <QQuickView>
2222#include <QtTest>
2323
24+#include <TouchRegistry.h>
25+
2426GestureTest::GestureTest(const QString &qmlFilename)
2527 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
2628{
@@ -44,11 +46,79 @@ void GestureTest::init()
4446 m_view->show();
4547 QVERIFY(QTest::qWaitForWindowExposed(m_view));
4648 QVERIFY(m_view->rootObject() != 0);
49+
50+ m_touchRegistry = new TouchRegistry;
51+ m_view->installEventFilter(m_touchRegistry);
52+
4753 qApp->processEvents();
4854}
4955
5056void GestureTest::cleanup()
5157{
58+ m_view->removeEventFilter(m_touchRegistry);
59+ delete m_touchRegistry;
60+ m_touchRegistry = nullptr;
61+
5262 delete m_view;
5363 m_view = nullptr;
5464}
65+
66+////////////////////////// TouchMemento /////////////////////////////
67+
68+TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
69+ : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
70+{
71+
72+}
73+
74+bool TouchMemento::containsTouchWithId(int touchId) const
75+{
76+ for (int i = 0; i < touchPoints.count(); ++i) {
77+ if (touchPoints.at(i).id() == touchId) {
78+ return true;
79+ }
80+ }
81+ return false;
82+}
83+
84+////////////////////////// DummyItem /////////////////////////////
85+
86+DummyItem::DummyItem(QQuickItem *parent)
87+ : QQuickItem(parent)
88+{
89+ touchEventHandler = defaultTouchEventHandler;
90+ mousePressEventHandler = defaultMouseEventHandler;
91+ mouseMoveEventHandler = defaultMouseEventHandler;
92+ mouseReleaseEventHandler = defaultMouseEventHandler;
93+}
94+
95+void DummyItem::touchEvent(QTouchEvent *event)
96+{
97+ touchEvents.append(TouchMemento(event));
98+ touchEventHandler(event);
99+}
100+
101+void DummyItem::mousePressEvent(QMouseEvent *event)
102+{
103+ mousePressEventHandler(event);
104+}
105+
106+void DummyItem::mouseMoveEvent(QMouseEvent *event)
107+{
108+ mouseMoveEventHandler(event);
109+}
110+
111+void DummyItem::mouseReleaseEvent(QMouseEvent *event)
112+{
113+ mouseReleaseEventHandler(event);
114+}
115+
116+void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
117+{
118+ event->accept();
119+}
120+
121+void DummyItem::defaultMouseEventHandler(QMouseEvent *event)
122+{
123+ event->accept();
124+}
tests/plugins/Ubuntu/Gestures/GestureTest.h+40−1
@@ -17,14 +17,52 @@
1717#ifndef GESTURETEST_H
1818#endif // GESTURETEST_H
1919
20−#include <QObject>
20+#include <QQuickItem>
21+#include <QTouchEvent>
2122
2223class QQuickView;
2324class QTouchDevice;
2425
26+class TouchRegistry;
27+
28+// C++ std lib
29+#include <functional>
30+
2531/*
2632 The common stuff among tests come here
2733 */
34+
35+class TouchMemento {
36+public:
37+ TouchMemento(const QTouchEvent *touchEvent);
38+ Qt::TouchPointStates touchPointStates;
39+ QList<QTouchEvent::TouchPoint> touchPoints;
40+
41+ bool containsTouchWithId(int touchId) const;
42+};
43+
44+class DummyItem : public QQuickItem
45+{
46+ Q_OBJECT
47+public:
48+ DummyItem(QQuickItem *parent = 0);
49+
50+ QList<TouchMemento> touchEvents;
51+ std::function<void(QTouchEvent*)> touchEventHandler;
52+ std::function<void(QMouseEvent*)> mousePressEventHandler;
53+ std::function<void(QMouseEvent*)> mouseMoveEventHandler;
54+ std::function<void(QMouseEvent*)> mouseReleaseEventHandler;
55+protected:
56+ void touchEvent(QTouchEvent *event) override;
57+
58+ void mousePressEvent(QMouseEvent *event) override;
59+ void mouseMoveEvent(QMouseEvent *event) override;
60+ void mouseReleaseEvent(QMouseEvent *event) override;
61+private:
62+ static void defaultTouchEventHandler(QTouchEvent *event);
63+ static void defaultMouseEventHandler(QMouseEvent *event);
64+};
65+
2866class GestureTest : public QObject
2967{
3068 Q_OBJECT
@@ -40,6 +78,7 @@ protected Q_SLOTS:
4078protected:
4179 QTouchDevice *m_device;
4280 QQuickView *m_view;
81+ TouchRegistry *m_touchRegistry;
4382 QString m_qmlFilename;
4483};
4584
tests/mocks/Unity/Indicators/qml/fake_menu_widget3.qml → tests/plugins/Ubuntu/Gestures/empty.qmlrenamed+4−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright (C) 2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,9 +15,8 @@
1515 */
1616
1717import QtQuick 2.0
18+import Ubuntu.Gestures 0.1
1819
19−Rectangle {
20− width: 40
21− height: 40
22− color : "yellow"
20+Item {
21+ width: 720; height: 720
2322}
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+4−73
@@ -21,8 +21,6 @@
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
2323
24−// C++ std lib
25−#include <functional>
2624
2725#include <DirectionalDragArea.h>
2826#include <TouchRegistry.h>
@@ -31,28 +29,6 @@
3129
3230using namespace UbuntuGestures;
3331
34−class TouchMemento {
35−public:
36− TouchMemento(const QTouchEvent *touchEvent);
37− Qt::TouchPointStates touchPointStates;
38− QList<QTouchEvent::TouchPoint> touchPoints;
39−
40− bool containsTouchWithId(int touchId) const;
41−};
42−
43−class DummyItem : public QQuickItem
44−{
45− Q_OBJECT
46−public:
47− DummyItem(QQuickItem *parent = 0);
48−
49− QList<TouchMemento> touchEvents;
50− std::function<void(QTouchEvent*)> touchEventHandler;
51−protected:
52− void touchEvent(QTouchEvent *event) override;
53−private:
54− static void defaultTouchEventHandler(QTouchEvent *event);
55−};
5632
5733class ComplexFakeTimer : public FakeTimer
5834{
@@ -149,7 +125,6 @@ private Q_SLOTS:
149125
150126private:
151127 void passTime(qint64 timeSpanMs);
152− TouchRegistry *touchRegistry;
153128 ComplexFakeTimer *fakeTimer;
154129 QSharedPointer<FakeTimeSource> fakeTimeSource;
155130};
@@ -171,19 +146,12 @@ void tst_DirectionalDragArea::init()
171146 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
172147 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
173148
174− touchRegistry = new TouchRegistry;
175− m_view->installEventFilter(touchRegistry);
176−
177149 fakeTimeSource.reset(new FakeTimeSource);
178150 fakeTimer = new ComplexFakeTimer(fakeTimeSource);
179151}
180152
181153void tst_DirectionalDragArea::cleanup()
182154{
183− m_view->removeEventFilter(touchRegistry);
184− delete touchRegistry;
185− touchRegistry = nullptr;
186−
187155 delete fakeTimer;
188156 fakeTimer = 0;
189157
@@ -1015,7 +983,7 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1015983 QPoint touchPos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1016984
1017985 dummyItem->touchEventHandler = [&](QTouchEvent *event) {
1018− touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
986+ m_touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
1019987 event->ignore();
1020988 };
1021989
@@ -1023,7 +991,7 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1023991
1024992 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1025993
1026− touchRegistry->requestTouchOwnership(0, dummyItem);
994+ m_touchRegistry->requestTouchOwnership(0, dummyItem);
1027995
1028996 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1029997
@@ -1172,7 +1140,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
11721140
11731141 // edgeDragArea should be an undecided candidate
11741142 {
1175− auto touchInfo = touchRegistry->findTouchInfo(0);
1143+ auto touchInfo = m_touchRegistry->findTouchInfo(0);
11761144 QCOMPARE(touchInfo->candidates.size(), 1);
11771145 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
11781146 QCOMPARE(touchInfo->candidates.at(0).undecided, true);
@@ -1188,7 +1156,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
11881156
11891157 // edgeDragArea should no longer be a candidate
11901158 {
1191− auto touchInfo = touchRegistry->findTouchInfo(0);
1159+ auto touchInfo = m_touchRegistry->findTouchInfo(0);
11921160 QCOMPARE(touchInfo->candidates.size(), 0);
11931161 }
11941162
@@ -1289,43 +1257,6 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
12891257 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
12901258}
12911259
1292−////////////////////////// TouchMemento /////////////////////////////
1293−
1294−TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
1295− : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
1296−{
1297−
1298−}
1299−
1300−bool TouchMemento::containsTouchWithId(int touchId) const
1301−{
1302− for (int i = 0; i < touchPoints.count(); ++i) {
1303− if (touchPoints.at(i).id() == touchId) {
1304− return true;
1305− }
1306− }
1307− return false;
1308−}
1309−
1310−////////////////////////// DummyItem /////////////////////////////
1311−
1312−DummyItem::DummyItem(QQuickItem *parent)
1313− : QQuickItem(parent)
1314−{
1315− touchEventHandler = defaultTouchEventHandler;
1316−}
1317−
1318−void DummyItem::touchEvent(QTouchEvent *event)
1319−{
1320− touchEvents.append(TouchMemento(event));
1321− touchEventHandler(event);
1322−}
1323−
1324−void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
1325−{
1326− event->accept();
1327−}
1328−
13291260QTEST_MAIN(tst_DirectionalDragArea)
13301261
13311262#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cppnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 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+#include <QtTest>
18+#include <QQuickView>
19+
20+#include "GestureTest.h"
21+
22+#include <TouchDispatcher.h>
23+
24+class tst_TouchDispatcher : public GestureTest
25+{
26+ Q_OBJECT
27+public:
28+ tst_TouchDispatcher();
29+private Q_SLOTS:
30+ void sendMouseEventIfTouchIgnored_data();
31+ void sendMouseEventIfTouchIgnored();
32+};
33+
34+tst_TouchDispatcher::tst_TouchDispatcher()
35+ : GestureTest(QStringLiteral("empty.qml"))
36+{
37+}
38+
39+void tst_TouchDispatcher::sendMouseEventIfTouchIgnored_data()
40+{
41+ QTest::addColumn<bool>("itemAcceptsTouch");
42+ QTest::addColumn<int>("acceptedMouseButtons");
43+ QTest::addColumn<bool>("shouldGetMousePress");
44+
45+ QTest::newRow("accepts touch and doesn't accept mouse buttons") << true << (int)Qt::NoButton << false;
46+ QTest::newRow("ignores touch and accepts mouse buttons") << false << (int)Qt::LeftButton << true;
47+ QTest::newRow("ignores touch and doesn't accept mouse buttons") << false << (int)Qt::NoButton << false;
48+}
49+
50+void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
51+{
52+ QFETCH(bool, itemAcceptsTouch);
53+ QFETCH(int, acceptedMouseButtons);
54+ QFETCH(bool, shouldGetMousePress);
55+ DummyItem *dummyItem = new DummyItem(m_view->rootObject());
56+
57+ TouchDispatcher touchDispatcher;
58+ touchDispatcher.setTargetItem(dummyItem);
59+
60+ QList<QTouchEvent::TouchPoint> touchPoints;
61+ QTouchEvent::TouchPoint touchPoint;
62+ touchPoint.setId(0);
63+ touchPoint.setState(Qt::TouchPointPressed);
64+ touchPoints.append(touchPoint);
65+
66+ bool gotTouchEvent = false;
67+ bool gotMousePressEvent = false;
68+
69+ dummyItem->setAcceptedMouseButtons((Qt::MouseButtons)acceptedMouseButtons);
70+
71+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
72+ gotTouchEvent = true;
73+ event->setAccepted(itemAcceptsTouch);
74+ };
75+
76+ dummyItem->mousePressEventHandler = [&](QMouseEvent *event) {
77+ gotMousePressEvent = true;
78+ event->accept();
79+ };
80+
81+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view,
82+ 12345 /* timestamp */);
83+
84+ QVERIFY(gotTouchEvent);
85+ QCOMPARE(gotMousePressEvent, shouldGetMousePress);
86+}
87+
88+QTEST_MAIN(tst_TouchDispatcher)
89+
90+#include "tst_TouchDispatcher.moc"
tests/plugins/Unity/Indicators/CMakeLists.txt+1
@@ -40,3 +40,4 @@ indicator_test(indicatorsmodeltest ADDITIONAL_LIBS IndicatorsQml)
4040indicator_test(menucontentactivatortest ADDITIONAL_CPPS ${INDICATORS_DIR}/menucontentactivator.cpp)
4141indicator_test(unitymenumodelstacktest ADDITIONAL_CPPS ${TEST_DIR}/mocks/QMenuModel/unitymenumodel.cpp ${INDICATORS_DIR}/unitymenumodelstack.cpp)
4242indicator_test(rootactionstatetest ADDITIONAL_LIBS IndicatorsQml)
43+indicator_test(sharedunitymenumodeltest ADDITIONAL_LIBS IndicatorsQml)
tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cppnew file+102
@@ -0,0 +1,102 @@
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 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+ */
17+
18+#include "sharedunitymenumodel.h"
19+#include "unitymenumodelcache.h"
20+
21+#include <QtTest>
22+#include <unitymenumodel.h>
23+
24+class SharedUnityMenuModelTest : public QObject
25+{
26+ Q_OBJECT
27+
28+ SharedUnityMenuModel* createFullModel(const QByteArray& testId)
29+ {
30+ SharedUnityMenuModel* model = new SharedUnityMenuModel;
31+ model->setBusName("com.canonical." + testId);
32+ model->setMenuObjectPath("/com/canonical/" + testId);
33+ QVariantMap actions;
34+ actions["test"] = QString("/com/canonical/%1/actions").arg(QString(testId));
35+ model->setActions(actions);
36+
37+ return model;
38+ }
39+
40+private Q_SLOTS:
41+
42+ void testCreateModel()
43+ {
44+ QSharedPointer<SharedUnityMenuModel> model(createFullModel("test1"));
45+ QVERIFY(model->model() != nullptr);
46+ }
47+
48+ void testDifferentDataCreatesDifferentModels()
49+ {
50+ QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
51+ QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test2"));
52+
53+ QVERIFY(model1->model() != model2->model());
54+ }
55+
56+ void testSameDataCreatesSameModels()
57+ {
58+ QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
59+ QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
60+
61+ QCOMPARE(model1->model(), model2->model());
62+ }
63+
64+ void testSharedOwnership()
65+ {
66+ QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
67+ QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
68+
69+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
70+ model1.clear();
71+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
72+ model2.clear();
73+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), false);
74+ }
75+
76+ // Tests that changing cached model data does not change the model path of others
77+ void testLP1328646()
78+ {
79+ QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
80+ QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
81+
82+ model2->setMenuObjectPath("/com/canonical/LP1328646");
83+
84+ QVERIFY(model1->model() != model2->model());
85+ QCOMPARE(model1->model()->menuObjectPath(), QByteArray("/com/canonical/test1"));
86+ QCOMPARE(model2->model()->menuObjectPath(), QByteArray("/com/canonical/LP1328646"));
87+ }
88+
89+ // Tests that the cache is recreated if deleted.
90+ void testDeletedCache()
91+ {
92+ QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
93+
94+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
95+ delete UnityMenuModelCache::singleton();
96+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), false);
97+ }
98+
99+};
100+
101+QTEST_GUILESS_MAIN(SharedUnityMenuModelTest)
102+#include "sharedunitymenumodeltest.moc"
tests/plugins/Unity/Launcher/CMakeLists.txt+5−1
@@ -7,6 +7,7 @@ include_directories(
77 ${CMAKE_SOURCE_DIR}/plugins/AccountsService
88 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher
99 ${GSETTINGS_QT_INCLUDE_DIRS}
10+ ${libunity8-private_SOURCE_DIR}
1011 )
1112
1213add_definitions(-DSM_BUSNAME=sessionBus)
@@ -34,7 +35,10 @@ add_executable(launchermodeltestExec
3435 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
3536 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
3637 )
37−target_link_libraries(launchermodeltestExec ${GSETTINGS_QT_LDFLAGS})
38+target_link_libraries(launchermodeltestExec
39+ unity8-private
40+ ${GSETTINGS_QT_LDFLAGS}
41+ )
3842qt5_use_modules(launchermodeltestExec Test Core DBus Xml Gui)
3943
4044# copy .desktop files into build directory for shadow builds
tests/plugins/Unity/Launcher/gsettings.cpp+8−4
@@ -19,8 +19,6 @@
1919
2020#include "gsettings.h"
2121
22−#include <QDebug>
23−
2422// This is a mock implementation to not touch GSettings for real during tests
2523
2624GSettings::GSettings(QObject *parent):
@@ -31,10 +29,16 @@ GSettings::GSettings(QObject *parent):
3129
3230QStringList GSettings::storedApplications() const
3331{
34− return QStringList();
32+ return m_entries;
3533}
3634
3735void GSettings::setStoredApplications(const QStringList &storedApplications)
3836{
39− Q_UNUSED(storedApplications)
37+ m_entries = storedApplications;
38+}
39+
40+void GSettings::simulateDConfChanged(const QStringList &storedApplications)
41+{
42+ setStoredApplications(storedApplications);
43+ Q_EMIT changed();
4044}
tests/plugins/Unity/Launcher/gsettings.h+8
@@ -33,6 +33,14 @@ public:
3333
3434 QStringList storedApplications() const;
3535 void setStoredApplications(const QStringList &storedApplications);
36+
37+ void simulateDConfChanged(const QStringList &storedApplications);
38+
39+Q_SIGNALS:
40+ void changed();
41+
42+private:
43+ QStringList m_entries;
3644};
3745
3846#endif
tests/plugins/Unity/Launcher/launchermodeltest.cpp+88−11
@@ -24,6 +24,7 @@
2424#include "launcheritem.h"
2525#include "launchermodel.h"
2626#include "dbusinterface.h"
27+#include "gsettings.h"
2728
2829#include <QtTest>
2930#include <QDBusInterface>
@@ -81,7 +82,15 @@ public:
8182 return nullptr;
8283 }
8384 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) { return nullptr; }
84− bool stopApplication(const QString &) { return false; }
85+ bool stopApplication(const QString &appId) {
86+ Q_FOREACH(MockApp* app, m_list) {
87+ if (app->appId() == appId) {
88+ removeApplication(m_list.indexOf(app));
89+ return true;
90+ }
91+ }
92+ return false;
93+ }
8594 bool focusApplication(const QString &appId) {
8695 Q_FOREACH(MockApp* app, m_list) {
8796 app->setFocused(app->appId() == appId);
@@ -124,6 +133,7 @@ private Q_SLOTS:
124133
125134 void initTestCase() {
126135 launcherModel = new LauncherModel(this);
136+ QCoreApplication::processEvents(); // to let the model register on DBus
127137 QCOMPARE(launcherModel->rowCount(QModelIndex()), 0);
128138
129139 appManager = new MockAppManager(this);
@@ -137,6 +147,8 @@ private Q_SLOTS:
137147
138148 appManager->addApplication(new MockApp("no-icon"));
139149 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
150+
151+ launcherModel->m_settings->setStoredApplications(QStringList());
140152 }
141153
142154 // Removing apps from appmanager and launcher as pinned ones would stick
@@ -172,14 +184,15 @@ private Q_SLOTS:
172184 launcherModel->pin(launcherModel->get(0)->appId());
173185 QCOMPARE(launcherModel->get(0)->pinned(), true);
174186 QCOMPARE(launcherModel->get(1)->pinned(), false);
175− QCOMPARE(spy.count(), 1);
187+ QCOMPARE(spy.count() > 0, true);
176188 QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
177189
190+ spy.clear();
178191 launcherModel->requestRemove(launcherModel->get(0)->appId());
179192 QCOMPARE(launcherModel->get(0)->pinned(), false);
180193 QCOMPARE(launcherModel->get(1)->pinned(), false);
181− QCOMPARE(spy.count(), 2);
182− QCOMPARE(spy.at(1).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
194+ QCOMPARE(spy.count(), 1);
195+ QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
183196 }
184197
185198 void testRemove_data() {
@@ -188,7 +201,7 @@ private Q_SLOTS:
188201 QTest::addColumn<bool>("running");
189202
190203 QTest::newRow("non-pinned, running") << false << true;
191− QTest::newRow("pinned, running") << true << false;
204+ QTest::newRow("pinned, running") << true << true;
192205 QTest::newRow("pinned, non-running") << true << false;
193206 }
194207
@@ -201,23 +214,24 @@ private Q_SLOTS:
201214
202215 // pin one if required
203216 if (pinned) {
204− launcherModel->pin(launcherModel->get(1)->appId());
217+ launcherModel->pin("abs-icon");
205218 }
206219
207220 // stop it if required
208221 if (!running) {
209− appManager->removeApplication(1);
222+ appManager->stopApplication("abs-icon");
210223 }
211224
212225 // Now remove it
213− launcherModel->requestRemove(launcherModel->get(1)->appId());
226+ launcherModel->requestRemove("abs-icon");
214227
215228 if (running) {
216229 // both apps are running, both apps must still be here
217230 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
218231
219232 // Item must be unpinned now
220− QCOMPARE(launcherModel->get(1)->pinned(), false);
233+ int index = launcherModel->findApplication("abs-icon");
234+ QCOMPARE(launcherModel->get(index)->pinned(), false);
221235
222236 } else if (pinned) {
223237 // Item 1 must go away, item 0 is here to stay
@@ -226,7 +240,7 @@ private Q_SLOTS:
226240
227241 // done our checks. now stop the app if was still running
228242 if (running) {
229− appManager->removeApplication(1);
243+ appManager->stopApplication("abs-icon");
230244 }
231245
232246 // It needs to go away in any case now
@@ -371,12 +385,75 @@ private Q_SLOTS:
371385 QCOMPARE(launcherModel->get(index)->count(), 55);
372386 }
373387
374− void testRefresh() {
388+ void testRefreshAfterDeletedDesktopFiles_data() {
389+ QTest::addColumn<bool>("deleted");
390+ QTest::newRow("have .desktop files") << false;
391+ QTest::newRow("deleted .desktop files") << true;
392+ }
393+
394+ void testRefreshAfterDeletedDesktopFiles() {
395+ QFETCH(bool, deleted);
396+
397+ // pin both apps
398+ launcherModel->pin("abs-icon");
399+ launcherModel->pin("no-icon");
400+ // close both apps
401+ appManager->removeApplication(0);
402+ appManager->removeApplication(0);
403+
404+ // "delete" the .desktop files
405+ QString oldCurrent = QDir::currentPath();
406+ if (deleted) {
407+ // In testing mode, the launcher searches the current dir for the sample .desktop file
408+ // We can make that fail by changing the current dir
409+ QDir::setCurrent("..");
410+ }
411+
412+ // Call refresh
375413 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
376414 QDBusReply<void> reply = interface.call("Refresh");
377415
378416 // Make sure the call to Refresh returned without error.
379417 QCOMPARE(reply.isValid(), true);
418+
419+ QCOMPARE(launcherModel->rowCount(), deleted ? 0 : 2);
420+
421+ // Restoring current dir
422+ QDir::setCurrent(oldCurrent);
423+ }
424+
425+ void testSettings() {
426+ GSettings *settings = launcherModel->m_settings;
427+
428+ // Nothing pinned at startup
429+ QCOMPARE(settings->storedApplications().count(), 0);
430+
431+ // pin both apps
432+ launcherModel->pin("abs-icon");
433+ launcherModel->pin("no-icon");
434+
435+ // Now settings should have 2 apps
436+ QCOMPARE(settings->storedApplications().count(), 2);
437+
438+ // close both apps
439+ appManager->removeApplication(0);
440+ appManager->removeApplication(0);
441+
442+ // Now settings should have 2 apps
443+ QCOMPARE(settings->storedApplications().count(), 2);
444+
445+ // Now remove 1 app through the backend, make sure one is still there
446+ settings->simulateDConfChanged(QStringList() << "abs-icon");
447+ QCOMPARE(settings->storedApplications().count(), 1);
448+
449+ // Check if it disappeared from the frontend too
450+ QCOMPARE(launcherModel->rowCount(), 1);
451+
452+ // Add them back but in reverse order
453+ settings->simulateDConfChanged(QStringList() << "no-icon" << "abs-icon");
454+ QCOMPARE(launcherModel->rowCount(), 2);
455+ QCOMPARE(launcherModel->get(0)->appId(), QString("no-icon"));
456+ QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
380457 }
381458};
382459
tests/plugins/Unity/Session/CMakeLists.txt+4
@@ -1,6 +1,7 @@
11include_directories(
22 ${CMAKE_CURRENT_BINARY_DIR}
33 ${CMAKE_SOURCE_DIR}/plugins/Unity/Session
4+ ${libunity8-private_SOURCE_DIR}
45)
56
67add_definitions(-DSM_BUSNAME=sessionBus)
@@ -17,4 +18,7 @@ add_executable(sessionbackendtestExec
1718 sessionbackendtest.cpp
1819 ${CMAKE_SOURCE_DIR}/plugins/Unity/Session/dbusunitysessionservice.cpp
1920)
21+target_link_libraries(sessionbackendtestExec
22+ unity8-private
23+ )
2024qt5_use_modules(sessionbackendtestExec Test Core Qml DBus)
tests/plugins/Unity/Session/sessionbackendtest.cpp+1
@@ -45,6 +45,7 @@ private Q_SLOTS:
4545 QFETCH(QString, method);
4646
4747 DBusUnitySessionService dbusUnitySessionService;
48+ QCoreApplication::processEvents(); // to let the service register on DBus
4849
4950 QDBusConnection con = QDBusConnection::sessionBus();
5051 QDBusInterface interface ("com.canonical.Unity",
tests/qmltests/CMakeLists.txt+10−11
@@ -12,16 +12,15 @@ set(qmltest_DEFAULT_IMPORT_PATHS
1212set(qmltest_DEFAULT_TARGETS qmlunittests)
1313set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
1414set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
15−add_qml_test(Panel IndicatorItem)
1615add_qml_test(utils/Unity/Test UnityTest)
1716
1817set(qmltest_DEFAULT_TARGETS qmluitests)
1918set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
2019set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
2120
22−add_qml_test(. Shell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
23−add_qml_test(. ShellWithPin ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single-pin")
24−add_qml_test(. TabletShell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
21+add_qml_test(. Shell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single:${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
22+add_qml_test(. ShellWithPin ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single-pin:${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
23+add_qml_test(. TabletShell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full:${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
2524add_qml_test(Components Background)
2625add_qml_test(Components Carousel)
2726add_qml_test(Components DraggingArea)
@@ -72,14 +71,14 @@ add_qml_test(Launcher Launcher)
7271add_qml_test(Notifications Notifications)
7372add_qml_test(Notifications VisualSnapDecisionsQueue)
7473add_qml_test(Panel ActiveCallHint)
75−add_qml_test(Panel IndicatorRow)
76−add_qml_test(Panel Indicators)
77−add_qml_test(Panel MenuContent)
78−add_qml_test(Panel Panel)
74+add_qml_test(Panel IndicatorItem)
75+add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
76+add_qml_test(Panel IndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
77+add_qml_test(Panel IndicatorsBar ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
78+add_qml_test(Panel IndicatorsMenu ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
79+add_qml_test(Panel MenuContent ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
80+add_qml_test(Panel Panel ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
7981add_qml_test(Panel SearchIndicator)
80−add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
81−add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
82−add_qml_test(Panel/Indicators CachedUnityMenuModel ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
8382# These MenuItemFactory tests need the test/mocks/ to come before plugins/
8483add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
8584add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
tests/qmltests/Components/tst_ZoomableImage.qml+21−21
@@ -56,23 +56,23 @@ Rectangle {
5656 when: windowShown
5757
5858 function test_loadImage() {
59− var lazyImage = findChild(zoomableImage, "lazyImage");
59+ var imageRenderer = findChild(zoomableImage, "imageRenderer");
6060
6161 zoomableImage.source = widgetData0["source"];
6262 zoomableImage.zoomable = widgetData0["zoomable"];
6363 waitForRendering(zoomableImage);
64− tryCompare(zoomableImage, "imageState", "default");
64+ tryCompare(zoomableImage, "imageStatus", Image.Null);
6565
66− signalSpy.signalName = "onStateChanged";
67− signalSpy.target = lazyImage;
66+ signalSpy.signalName = "onStatusChanged";
67+ signalSpy.target = imageRenderer;
6868 signalSpy.clear();
6969
7070 zoomableImage.source = widgetData1["source"];
7171 zoomableImage.zoomable = widgetData1["zoomable"];
72− waitForRendering(lazyImage);
73− tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(widgetData1["source"]); }, true);
72+ waitForRendering(imageRenderer);
73+ tryCompareFunction(function() { return get_filename(imageRenderer.source.toString()) === get_filename(widgetData1["source"]); }, true);
7474 waitForRendering(zoomableImage);
75− tryCompare(zoomableImage, "imageState", "ready");
75+ tryCompare(zoomableImage, "imageStatus", Image.Ready);
7676 compare (signalSpy.count, 1);
7777 }
7878
@@ -84,15 +84,15 @@ Rectangle {
8484
8585 function test_mousewheel() {
8686 var image = findChild(zoomableImage, "image");
87− var lazyImage = findChild(zoomableImage, "lazyImage");
87+ var imageRenderer = findChild(zoomableImage, "imageRenderer");
8888 var flickable = findChild(zoomableImage, "flickable");
8989
9090 zoomableImage.source = widgetData2["source"];
9191 zoomableImage.zoomable = true;
9292 waitForRendering(zoomableImage);
9393
94− tryCompare(zoomableImage, "imageState", "ready");
95− tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(widgetData2["source"]); }, true);
94+ tryCompare(zoomableImage, "imageStatus", Image.Ready);
95+ tryCompareFunction(function() { return get_filename(imageRenderer.source.toString()) === get_filename(widgetData2["source"]); }, true);
9696 waitForRendering(image);
9797
9898 // move mouse to center
@@ -105,16 +105,16 @@ Rectangle {
105105 for (var i=0; i<10; i++) {
106106 mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, 10);
107107 tryCompare(image, "scale", oldScale + (i + 1) * 0.1);
108− compare(flickable.contentWidth, Math.max(lazyImage.width * image.scale, flickable.width));
109− compare(flickable.contentHeight, Math.max(lazyImage.height * image.scale, flickable.height));
108+ compare(flickable.contentWidth, Math.max(imageRenderer.width * image.scale, flickable.width));
109+ compare(flickable.contentHeight, Math.max(imageRenderer.height * image.scale, flickable.height));
110110 }
111111
112112 // zoom out
113113 for (var i=0; i<10; i++) {
114114 mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, -10);
115115 tryCompare(image, "scale", oldScale + 1.0 - (i + 1) * 0.1);
116− compare(flickable.contentWidth, Math.max(lazyImage.width * image.scale, flickable.width));
117− compare(flickable.contentHeight, Math.max(lazyImage.height * image.scale, flickable.height));
116+ compare(flickable.contentWidth, Math.max(imageRenderer.width * image.scale, flickable.width));
117+ compare(flickable.contentHeight, Math.max(imageRenderer.height * image.scale, flickable.height));
118118 }
119119 }
120120 }
@@ -135,7 +135,7 @@ Rectangle {
135135
136136 function test_pinch(data) {
137137 var image = findChild(zoomableImage, "image");
138− var lazyImage = findChild(zoomableImage, "lazyImage");
138+ var imageRenderer = findChild(zoomableImage, "imageRenderer");
139139 var flickable = findChild(zoomableImage, "flickable");
140140
141141 signalSpy.signalName = "onScaleChanged";
@@ -146,8 +146,8 @@ Rectangle {
146146 zoomableImage.zoomable = data.zoomable;
147147 waitForRendering(zoomableImage);
148148
149− tryCompare(zoomableImage, "imageState", "ready");
150− tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(data.source); }, true);
149+ tryCompare(zoomableImage, "imageStatus", Image.Ready);
150+ tryCompareFunction(function() { return get_filename(imageRenderer.source.toString()) === get_filename(data.source); }, true);
151151 waitForRendering(image);
152152
153153 var x1Start = zoomableImage.width * 2 / 6;
@@ -179,15 +179,15 @@ Rectangle {
179179 compare(newScale > oldScale, data.answer2, "scale factor didn't changed");
180180 compare(signalSpy.count == 0, data.answer3, "scale signal count error");
181181 compare(image.scale, newScale, "scale factor error");
182− compare(flickable.contentWidth, Math.max(lazyImage.width * image.scale, flickable.width));
183− compare(flickable.contentHeight, Math.max(lazyImage.height * image.scale, flickable.height));
182+ compare(flickable.contentWidth, Math.max(imageRenderer.width * image.scale, flickable.width));
183+ compare(flickable.contentHeight, Math.max(imageRenderer.height * image.scale, flickable.height));
184184
185185 wait(3000); // have to delay between two consequent pinch event.
186186 // pinch zoom-out
187187 touchPinch(zoomableImage, x1End, y1End, x1Start, y1Start, x2End, y2End, x2Start, y2Start);
188188 tryCompare(image, "scale", oldScale);
189− compare(flickable.contentWidth, Math.max(lazyImage.width * image.scale, flickable.width));
190− compare(flickable.contentHeight, Math.max(lazyImage.height * image.scale, flickable.height));
189+ compare(flickable.contentWidth, Math.max(imageRenderer.width * image.scale, flickable.width));
190+ compare(flickable.contentHeight, Math.max(imageRenderer.height * image.scale, flickable.height));
191191 }
192192 }
193193 }
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+25−24
@@ -48,12 +48,13 @@ Rectangle {
4848 name: "PreviewImageGalleryTest"
4949 when: windowShown
5050
51− property Item slideShow: findChild(imageGallery.rootItem, "slideShow")
52− property Item slideShowCloseButton: findChild(slideShow, "slideShowCloseButton")
53− property Item slideShowListView: findChild(slideShow, "slideShowListView")
51+ property Item overlay: findChild(imageGallery.rootItem, "overlay")
52+ property Item overlayCloseButton: findChild(overlay, "overlayCloseButton")
53+ property Item overlayListView: findChild(overlay, "overlayListView")
5454
5555 function cleanup() {
56− slideShow.visible = false;
56+ overlay.hide();
57+ tryCompare(overlay, "visible", false);
5758 imageGallery.widgetData = sourcesModel1;
5859 waitForRendering(imageGallery);
5960 }
@@ -64,29 +65,29 @@ Rectangle {
6465 compare(placeholderScreenshot.visible, true);
6566 }
6667
67− function test_slideShowOpenClose() {
68+ function test_overlayOpenClose() {
6869 var image0 = findChild(imageGallery, "previewImage0");
6970 mouseClick(image0, image0.width / 2, image0.height / 2);
70− tryCompare(slideShow, "visible", true);
71− tryCompare(slideShow, "scale", 1.0);
72− tryCompare(slideShowCloseButton, "visible", true);
73− mouseClick(slideShowCloseButton, slideShowCloseButton.width / 2, slideShowCloseButton.height / 2);
74− tryCompare(slideShow, "visible", false);
71+ tryCompare(overlay, "visible", true);
72+ tryCompare(overlay, "scale", 1.0);
73+ tryCompare(overlayCloseButton, "visible", true);
74+ mouseClick(overlayCloseButton, overlayCloseButton.width / 2, overlayCloseButton.height / 2);
75+ tryCompare(overlay, "visible", false);
7576 }
7677
77− function test_slideShowShowHideHeader() {
78+ function test_overlayShowHideHeader() {
7879 var image0 = findChild(imageGallery, "previewImage0");
7980 mouseClick(image0, image0.width / 2, image0.height / 2);
80− tryCompare(slideShow, "visible", true);
81− tryCompare(slideShow, "scale", 1.0);
82− tryCompare(slideShowCloseButton, "visible", true);
83− mouseClick(slideShow, slideShow.width / 2, slideShow.height / 2);
84− tryCompare(slideShowCloseButton, "visible", false);
85− mouseClick(slideShow, slideShow.width / 2, slideShow.height / 2);
86− tryCompare(slideShowCloseButton, "visible", true);
81+ tryCompare(overlay, "visible", true);
82+ tryCompare(overlay, "scale", 1.0);
83+ tryCompare(overlayCloseButton, "visible", true);
84+ mouseClick(overlay, overlay.width / 2, overlay.height / 2);
85+ tryCompare(overlayCloseButton, "visible", false);
86+ mouseClick(overlay, overlay.width / 2, overlay.height / 2);
87+ tryCompare(overlayCloseButton, "visible", true);
8788 }
8889
89− function test_slideShowOpenCorrectImage_data() {
90+ function test_overlayOpenCorrectImage_data() {
9091 return [
9192 { tag: "Image 0", index: 0 },
9293 { tag: "Image 1", index: 1 },
@@ -94,13 +95,13 @@ Rectangle {
9495 ];
9596 }
9697
97− function test_slideShowOpenCorrectImage(data) {
98+ function test_overlayOpenCorrectImage(data) {
9899 var image = findChild(imageGallery, "previewImage" + data.index);
99100 mouseClick(image, image.width / 2, image.height / 2);
100− tryCompare(slideShow, "visible", true);
101− tryCompare(slideShow, "scale", 1.0);
102− tryCompare(slideShowListView, "currentIndex", data.index);
103− verify(image.source === slideShowListView.currentItem.source);
101+ tryCompare(overlay, "visible", true);
102+ tryCompare(overlay, "scale", 1.0);
103+ tryCompare(overlayListView, "currentIndex", data.index);
104+ verify(image.source === overlayListView.currentItem.source);
104105 }
105106 }
106107}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+30−8
@@ -36,26 +36,48 @@ Rectangle {
3636
3737 PreviewZoomableImage {
3838 id: zoomableImage
39− anchors.fill: parent
40− widgetData: widgetData1
39+ anchors.centerIn: parent
40+ widgetData: widgetData0
4141 }
4242
4343 UT.UnityTestCase {
4444 name: "PreviewZoomableImageTest"
4545 when: windowShown
4646
47− function test_loadImage() {
48− var image = findChild(zoomableImage, "image");
47+ property Item lazyImage: findChild(zoomableImage, "lazyImage");
48+ property Item overlay: findChild(zoomableImage.rootItem, "overlay");
49+
50+ function init() {
51+ waitForRendering(zoomableImage);
52+ waitForRendering(overlay);
53+ }
54+
55+ function cleanup() {
56+ overlay.hide();
57+ tryCompare(overlay, "visible", false);
58+ zoomableImage.widgetData = widgetData0;
59+ }
4960
61+ function test_loadImage() {
5062 zoomableImage.widgetData = widgetData0;
5163 waitForRendering(zoomableImage);
52− waitForRendering(image);
53− tryCompare(image, "imageState", "ready");
64+ waitForRendering(lazyImage);
65+ tryCompare(lazyImage, "state", "ready");
5466
5567 zoomableImage.widgetData = widgetData1;
5668 waitForRendering(zoomableImage);
57− waitForRendering(image);
58− tryCompare(image, "imageState", "default");
69+ waitForRendering(lazyImage);
70+ tryCompare(lazyImage, "state", "default");
71+ }
72+
73+ function test_zoomableImageOpenClose() {
74+ var overlayCloseButton = findChild(overlay, "overlayCloseButton");
75+ mouseClick(lazyImage, lazyImage.width / 2, lazyImage.height / 2);
76+ tryCompare(overlay, "visible", true);
77+ tryCompare(overlay, "scale", 1.0);
78+ tryCompare(overlayCloseButton, "visible", true);
79+ mouseClick(overlayCloseButton, overlayCloseButton.width / 2, overlayCloseButton.height / 2);
80+ tryCompare(overlay, "visible", false);
5981 }
6082 }
6183}
tests/qmltests/Dash/tst_Card.qml+13
@@ -186,6 +186,19 @@ Rectangle {
186186 height: units.gu(4)
187187 color: "orange"
188188 }
189+
190+ Rectangle {
191+ color: "red"
192+ anchors { left: parent.left; right: parent.right }
193+ height: units.gu(4)
194+ Label {
195+ text: "Toggle Visible"
196+ }
197+ MouseArea {
198+ anchors.fill: parent
199+ onClicked: loader.visible = !loader.visible
200+ }
201+ }
189202 }
190203 }
191204
tests/qmltests/Dash/tst_Dash.qml+6−26
@@ -36,7 +36,6 @@ Item {
3636 Dash {
3737 id: dash
3838 anchors.fill: parent
39− showScopeOnLoaded: "MockScope2"
4039 }
4140
4241 UT.UnityTestCase {
@@ -57,14 +56,14 @@ Item {
5756 tryCompare(dashContentList, "count", 6);
5857 tryCompare(scopes, "loaded", true);
5958 tryCompareFunction(function() {
60− var mockScope1Loader = findChild(dash, "MockScope1 loader");
59+ var mockScope1Loader = findChild(dash, "scopeLoader0");
6160 return mockScope1Loader && mockScope1Loader.item != null; },
6261 true, 15000);
6362 tryCompareFunction(function() {
64− var mockScope1Loader = findChild(dash, "MockScope1 loader");
63+ var mockScope1Loader = findChild(dash, "scopeLoader0");
6564 return mockScope1Loader && mockScope1Loader.status === Loader.Ready; },
6665 true, 15000);
67− waitForRendering(findChild(dash, "MockScope1 loader").item);
66+ waitForRendering(findChild(dash, "scopeLoader0").item);
6867 }
6968
7069 function get_scope_data() {
@@ -80,27 +79,6 @@ Item {
8079 return get_scope_data()
8180 }
8281
83− function test_show_scope_on_load(data) {
84− var dashContentList = findChild(dash, "dashContentList");
85−
86− dash.showScopeOnLoaded = data.tag
87− scopes.clear();
88− tryCompare(dashContentList, "count", 0);
89− scopes.load();
90− tryCompare(scopes, "loaded", true);
91− tryCompare(dashContentList, "count", 6);
92−
93− verify(dashContentList != undefined);
94− if (data.visualIndex == -1) {
95− tryCompare(dashContentList, "currentIndex", 0);
96− expectFail(data.tag, "non favorite scopes should not be visble in the scopes model");
97− compare(dashContentList.currentItem.scopeId, data.tag); // this should fail
98− } else {
99− tryCompare(dashContentList, "currentIndex", data.visualIndex);
100− compare(dashContentList.currentItem.scopeId, data.tag);
101− }
102− }
103−
10482 function test_dash_overview_show_select_same_favorite() {
10583 // Show the overview
10684 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, dash.height - units.gu(18));
@@ -314,7 +292,9 @@ Item {
314292 mouseFlick(dash, startX, startY, stopX, stopY);
315293 compare(dashContentList.currentIndex, 2, "Could not flick to scope id 2");
316294 var dashCommunicatorService = findInvisibleChild(dash, "dashCommunicatorService");
317− dashCommunicatorService.mockSetCurrentScope("clickscope", true, false);
295+ dashCommunicatorService.mockSetCurrentScope(0, true, false);
296+ tryCompare(dashContentList, "currentIndex", 0)
297+ dashCommunicatorService.mockSetCurrentScope(1, true, false);
318298 tryCompare(dashContentList, "currentIndex", 1)
319299 }
320300
tests/qmltests/Dash/tst_DashContent.qml+8−8
@@ -73,14 +73,14 @@ Item {
7373 tryCompare(scopeLoadedSpy, "count", 6, 15000);
7474 tryCompare(scopesModel, "loaded", true);
7575 tryCompareFunction(function() {
76− var mockScope1Loader = findChild(shell, "MockScope1 loader");
76+ var mockScope1Loader = findChild(shell, "scopeLoader0");
7777 return mockScope1Loader && mockScope1Loader.item != null; },
7878 true, 15000);
7979 tryCompareFunction(function() {
80− var mockScope1Loader = findChild(shell, "MockScope1 loader");
80+ var mockScope1Loader = findChild(shell, "scopeLoader0");
8181 return mockScope1Loader && mockScope1Loader.status === Loader.Ready; },
8282 true, 15000);
83− waitForRendering(findChild(shell, "MockScope1 loader").item);
83+ waitForRendering(findChild(shell, "scopeLoader0").item);
8484 }
8585
8686 function init() {
@@ -131,7 +131,7 @@ Item {
131131 function test_show_header_on_list_movement() {
132132 var dashContentList = findChild(dashContent, "dashContentList");
133133 verify(dashContentList !== null);
134− var scope = findChild(dashContent, "MockScope1 loader");
134+ var scope = findChild(dashContent, "scopeLoader0");
135135 waitForRendering(scope);
136136
137137 var categoryListView = findChild(scope, "categoryListView");
@@ -149,7 +149,7 @@ Item {
149149 function test_set_current_scope_reset() {
150150 var dashContentList = findChild(dashContent, "dashContentList");
151151 verify(dashContentList, "Couldn't find dashContentList");
152− var scope = findChild(dashContent, "MockScope1 loader");
152+ var scope = findChild(dashContent, "scopeLoader0");
153153
154154 tryCompare(scope, "status", Loader.Ready);
155155
@@ -261,7 +261,7 @@ Item {
261261
262262 function test_carouselAspectRatio() {
263263 tryCompareFunction(function() {
264− var scope = findChild(dashContent, "MockScope1 loader");
264+ var scope = findChild(dashContent, "scopeLoader0");
265265 if (scope != null) {
266266 var dashCategory1 = findChild(scope, "dashCategory1");
267267 if (dashCategory1 != null) {
@@ -273,7 +273,7 @@ Item {
273273 },
274274 true);
275275
276− var scope = findChild(dashContent, "MockScope1 loader");
276+ var scope = findChild(dashContent, "scopeLoader0");
277277 var dashCategory1 = findChild(scope, "dashCategory1");
278278 var cardTool = findChild(dashCategory1, "cardTool");
279279 var carouselLV = findChild(dashCategory1, "listView");
@@ -501,7 +501,7 @@ Item {
501501 function test_searchHint() {
502502 var dashContentList = findChild(dashContent, "dashContentList");
503503 verify(dashContentList !== null);
504− var scope = findChild(dashContent, "MockScope1 loader");
504+ var scope = findChild(dashContent, "scopeLoader0");
505505 waitForRendering(scope);
506506
507507 var categoryListView = findChild(scope, "categoryListView");
tests/qmltests/Dash/tst_GenericScopeView.qml+33
@@ -603,6 +603,39 @@ Item {
603603 spy.wait()
604604 compare(spy.count, 1)
605605 }
606+
607+ function test_item_noninteractive() {
608+ waitForRendering(genericScopeView);
609+
610+ var categoryListView = findChild(genericScopeView, "categoryListView");
611+ waitForRendering(categoryListView);
612+
613+ var category0 = findChild(categoryListView, "dashCategory0");
614+ waitForRendering(category0);
615+
616+ var cardTool = findChild(category0, "cardTool");
617+ var cardGrid = category0.item;
618+
619+ cardTool.template["non-interactive"] = true;
620+ compare(cardGrid.cardTool.template["non-interactive"], true);
621+
622+ var item0 = findChild(cardGrid, "delegate0");
623+ waitForRendering(item0);
624+ item0.template = cardTool.template;
625+ compare(item0.template["non-interactive"], true);
626+ compare(item0.enabled, false);
627+ var touchdown = findChild(item0, "touchdown");
628+
629+ compare(touchdown.visible, false);
630+ mouseClick(item0, item0.width / 2, item0.height / 2);
631+ compare(touchdown.visible, false);
632+
633+ cardTool.template["non-interactive"] = false;
634+ compare(cardGrid.cardTool.template["non-interactive"], false);
635+ item0.template = cardTool.template;
636+ compare(item0.template["non-interactive"], false);
637+ compare(item0.enabled, true);
638+ }
606639 }
607640 }
608641}
tests/qmltests/Greeter/tst_Clock.qml+49−34
@@ -19,7 +19,7 @@ import QtTest 1.0
1919import ".."
2020import "../../../qml/Greeter"
2121import Ubuntu.Components 0.1
22−import QMenuModel 0.1
22+import Unity.Indicators 0.1 as Indicators
2323import Unity.Test 0.1 as UT
2424
2525Rectangle {
@@ -36,56 +36,71 @@ Rectangle {
3636 }
3737 }
3838
39− UnityMenuModel {
40− id: menuModel
41− modelData: [{
42− "rowData": {
43− "actionState": { "label": Qt.formatTime(new Date("October 13, 1975 11:13:00")) }
44− }
45− }]
39+ function updateDatetimeModelTime(label) {
40+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicator/datetime/phone",
41+ [{
42+ "rowData": {
43+ "actionState": { "label": label }
44+ }
45+ }]);
4646 }
4747
4848 UT.UnityTestCase {
4949 name: "Clock"
50+ when: windowShown
5051
5152 function init() {
52− var cachedModel = findChild(clock, "timeModel");
53− verify(cachedModel !== undefined);
54− cachedModel.model = menuModel;
53+ updateDatetimeModelTime(Qt.formatTime(new Date("October 13, 1975 12:14:00")));
54+ clock.visible = true;
5555 }
5656
57− function test_customDate() {
58− var dateObj = new Date("October 13, 1975 11:13:00")
59− var dateString = Qt.formatDate(dateObj, Qt.DefaultLocaleLongDate)
60− var timeString = Qt.formatTime(dateObj)
57+ // Test that the date portion of the clock updates with custom value.
58+ // Time portion is controlled by indicators
59+ function test_updateDate() {
60+ var dateLabel = findChild(clock, "dateLabel");
61+ var timeLabel = findChild(clock, "timeLabel");
6162
62− clock.currentDate = dateObj
63− var dateLabel = findChild(clock, "dateLabel")
64− compare(dateLabel.text, dateString, "Not the expected date")
65− var timeLabel = findChild(clock, "timeLabel")
66− compare(timeLabel.text, timeString, "Not the expected time")
63+ var timeString = Qt.formatTime(new Date("October 13, 1975 12:14:00"));
64+
65+ // initial date.
66+ var dateObj = new Date("October 13, 1975 11:13:00");
67+ var dateString = Qt.formatDate(dateObj, Qt.DefaultLocaleLongDate);
68+ clock.currentDate = dateObj;
69+
70+ compare(dateLabel.text, dateString, "Not the expected date");
71+ compare(timeLabel.text, timeString, "Time should come from indicators");
72+
73+ // update date.
74+ var dateObj2 = new Date("October 14, 1976 13:15:00");
75+ var dateString2 = Qt.formatDate(dateObj2, Qt.DefaultLocaleLongDate);
76+ clock.currentDate = dateObj2;
77+
78+ compare(dateLabel.text, dateString2, "Not the expected date");
79+ compare(timeLabel.text,timeString, "Time should come from indicators");
6780 }
6881
69− function test_dateUpdate() {
70− var dateObj = new Date("October 13, 1975 11:13:00")
71− var dateString = Qt.formatDate(dateObj, Qt.DefaultLocaleLongDate)
72− var timeString = Qt.formatTime(dateObj)
82+ // Test that the date portion of the clock updates with custom value.
83+ // Time portion is controlled by indicators
84+ function test_updateTime() {
85+ var timeLabel = findChild(clock, "timeLabel");
7386
74− clock.visible = false
75− var timeModel = findInvisibleChild(clock, "timeModel")
87+ var timeString1 = Qt.formatTime(new Date("October 13, 1975 11:15:00"));
88+ var timeString2 = Qt.formatTime(new Date("October 14, 1976 12:16:00"));
7689
77− compare(timeModel.menuObjectPath, "", "Clock shouldn't be connected to Indicators when not visible.")
90+ updateDatetimeModelTime(timeString1);
91+ compare(timeLabel.text, timeString1, "Time should come from indicators");
7892
79− clock.currentDate = dateObj
93+ updateDatetimeModelTime(timeString2);
94+ compare(timeLabel.text, timeString2, "Time should come from indicators");
95+ }
8096
81− var dateLabel = findChild(clock, "dateLabel")
82− compare(dateLabel.text, dateString, "Not the expected date")
83− var timeLabel = findChild(clock, "timeLabel")
84− compare(timeLabel.text, timeString, "Not the expected time")
97+ function test_indicatorDisconnect() {
98+ clock.visible = false
99+ var timeModel = findInvisibleChild(clock, "timeModel")
100+ compare(timeModel.menuObjectPath, "", "Clock shouldn't be connected to Indicators when not visible.")
85101
86102 clock.visible = true
87−
88− verify(timeModel.menuObjectPath != "", "Should be connected to Indicators.")
103+ verify(timeModel.menuObjectPath !== "", "Should be connected to Indicators.")
89104 }
90105 }
91106}
tests/qmltests/Notifications/tst_Notifications.qml+172−41
@@ -53,6 +53,7 @@ Row {
5353 summary: "",
5454 body: "",
5555 icon: "",
56+ value: 0,
5657 secondaryIcon: "",
5758 actions: []
5859 }
@@ -125,10 +126,11 @@ Row {
125126 function addEphemeralIconSummaryNotification() {
126127 var n = {
127128 type: Notification.Ephemeral,
129+ hints: {"x-canonical-non-shaped-icon": "false"},
128130 summary: "Photo upload completed",
129131 body: "",
130− icon: "",
131− secondaryIcon: "../graphics/applicationIcons/facebook.png",
132+ icon: "../graphics/applicationIcons/facebook.png",
133+ secondaryIcon: "",
132134 actions: []
133135 }
134136
@@ -148,20 +150,53 @@ Row {
148150 mockModel.append(n)
149151 }
150152
153+ function addConfirmationNotification() {
154+ var n = {
155+ type: Notification.Confirmation,
156+ hints: {"x-canonical-non-shaped-icon": "true"},
157+ summary: "Confirmation notification",
158+ body: "",
159+ icon: "image://theme/audio-volume-medium",
160+ secondaryIcon: "",
161+ value: 50,
162+ actions: [],
163+ }
164+
165+ mockModel.append(n)
166+ }
167+
168+ function add2ndConfirmationNotification() {
169+ var n = {
170+ type: Notification.Confirmation,
171+ hints: {"x-canonical-non-shaped-icon": "true",
172+ "x-canonical-value-bar-tint": "true"},
173+ summary: "Confirmation notification",
174+ body: "High Volume",
175+ icon: "image://theme/audio-volume-high",
176+ secondaryIcon: "",
177+ value: 85,
178+ actions: [],
179+ }
180+
181+ mockModel.append(n)
182+ }
183+
151184 function clearNotifications() {
152− mockModel.clear()
185+ while(mockModel.count > 1) {
186+ remove1stNotification()
187+ }
153188 }
154189
155190 function remove1stNotification() {
156− if (mockModel.count > 0)
157− mockModel.remove(0)
191+ if (mockModel.count > 1)
192+ mockModel.remove(1)
158193 }
159194
160195 Rectangle {
161196 id: notificationsRect
162197
163198 width: units.gu(40)
164− height: units.gu(71)
199+ height: units.gu(115)
165200
166201 MouseArea{
167202 id: clickThroughCatcher
@@ -183,7 +218,7 @@ Row {
183218 id: interactiveControls
184219
185220 width: units.gu(30)
186− height: units.gu(81)
221+ height: units.gu(115)
187222 color: "grey"
188223
189224 Column {
@@ -227,6 +262,18 @@ Row {
227262 onClicked: addInteractiveNotification()
228263 }
229264
265+ Button {
266+ width: parent.width
267+ text: "add a confirmation"
268+ onClicked: addConfirmationNotification()
269+ }
270+
271+ Button {
272+ width: parent.width
273+ text: "add a 2nd confirmation"
274+ onClicked: add2ndConfirmationNotification()
275+ }
276+
230277 Button {
231278 width: parent.width
232279 text: "remove 1st notification"
@@ -264,12 +311,16 @@ Row {
264311 summaryVisible: true,
265312 bodyVisible: true,
266313 iconVisible: true,
267− shapedIcon: true,
268− nonShapedIcon: false,
314+ centeredIconVisible: false,
315+ shaped: true,
316+ nonShaped: false,
269317 secondaryIconVisible: true,
270318 buttonRowVisible: true,
271319 buttonTinted: true,
272− hasSound: false
320+ hasSound: false,
321+ valueVisible: false,
322+ valueLabelVisible: false,
323+ valueTinted: false
273324 },
274325 {
275326 tag: "2-over-1 Snap Decision with button-tint",
@@ -285,31 +336,37 @@ Row {
285336 summaryVisible: true,
286337 bodyVisible: true,
287338 iconVisible: false,
288− shapedIcon: false,
289− nonShapedIcon: false,
339+ centeredIconVisible: false,
340+ shaped: false,
290341 secondaryIconVisible: false,
291342 buttonRowVisible: false,
292343 buttonTinted: true,
293− hasSound: false
344+ hasSound: false,
345+ valueVisible: false,
346+ valueLabelVisible: false,
347+ valueTinted: false
294348 },
295349 {
296350 tag: "Ephemeral notification - icon-summary layout",
297351 type: Notification.Ephemeral,
298− hints: {"x-canonical-private-affirmative-tint": "false"},
352+ hints: {},
299353 summary: "Photo upload completed",
300354 body: "",
301− icon: "",
302− secondaryIcon: "../graphics/applicationIcons/facebook.png",
355+ icon: "../graphics/applicationIcons/facebook.png",
356+ secondaryIcon: "",
303357 actions: [],
304358 summaryVisible: true,
305359 bodyVisible: false,
306− iconVisible: false,
307− shapedIcon: false,
308− nonShapedIcon: false,
309− secondaryIconVisible: true,
360+ iconVisible: true,
361+ centeredIconVisible: false,
362+ shaped: true,
363+ secondaryIconVisible: false,
310364 buttonRowVisible: false,
311365 buttonTinted: false,
312− hasSound: false
366+ hasSound: false,
367+ valueVisible: false,
368+ valueLabelVisible: false,
369+ valueTinted: false
313370 },
314371 {
315372 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
@@ -326,12 +383,15 @@ Row {
326383 bodyVisible: false,
327384 interactiveAreaEnabled: false,
328385 iconVisible: false,
329− shapedIcon: false,
330− nonShapedIcon: false,
386+ centeredIconVisible: false,
387+ shaped: false,
331388 secondaryIconVisible: true,
332389 buttonRowVisible: false,
333390 buttonTinted: false,
334− hasSound: false
391+ hasSound: false,
392+ valueVisible: false,
393+ valueLabelVisible: false,
394+ valueTinted: false
335395 },
336396 {
337397 tag: "Interactive notification",
@@ -346,12 +406,15 @@ Row {
346406 summaryVisible: true,
347407 bodyVisible: true,
348408 iconVisible: true,
349− shapedIcon: true,
350− nonShapedIcon: false,
409+ centeredIconVisible: false,
410+ shaped: true,
351411 secondaryIconVisible: false,
352412 buttonRowVisible: false,
353413 buttonTinted: false,
354− hasSound: true
414+ hasSound: true,
415+ valueVisible: false,
416+ valueLabelVisible: false,
417+ valueTinted: false
355418 },
356419 {
357420 tag: "Snap Decision without secondary icon and no button-tint",
@@ -367,12 +430,15 @@ Row {
367430 summaryVisible: true,
368431 bodyVisible: true,
369432 iconVisible: true,
370− shapedIcon: true,
371− nonShapedIcon: false,
433+ centeredIconVisible: false,
434+ shaped: true,
372435 secondaryIconVisible: false,
373436 buttonRowVisible: true,
374437 buttonTinted: false,
375− hasSound: true
438+ hasSound: true,
439+ valueVisible: false,
440+ valueLabelVisible: false,
441+ valueTinted: false
376442 },
377443 {
378444 tag: "Ephemeral notification",
@@ -387,12 +453,15 @@ Row {
387453 summaryVisible: true,
388454 bodyVisible: true,
389455 iconVisible: true,
390− shapedIcon: true,
391− nonShapedIcon: false,
456+ centeredIconVisible: false,
457+ shaped: true,
392458 secondaryIconVisible: true,
393459 buttonRowVisible: false,
394460 buttonTinted: false,
395− hasSound: true
461+ hasSound: true,
462+ valueVisible: false,
463+ valueLabelVisible: false,
464+ valueTinted: false
396465 },
397466 {
398467 tag: "Ephemeral notification with non-shaped icon",
@@ -401,18 +470,68 @@ Row {
401470 "x-canonical-non-shaped-icon": "true"},
402471 summary: "Contacts",
403472 body: "Synchronised contacts-database with cloud-storage.",
404− icon: "../graphics/applicationIcons/contacts-app.png",
473+ icon: "image://theme/contacts-app",
405474 secondaryIcon: "",
406475 actions: [],
407476 summaryVisible: true,
408477 bodyVisible: true,
409478 iconVisible: true,
410− shapedIcon: false,
411− nonShapedIcon: true,
479+ centeredIconVisible: false,
480+ shaped: false,
481+ secondaryIconVisible: false,
482+ buttonRowVisible: false,
483+ buttonTinted: false,
484+ hasSound: false,
485+ valueVisible: false,
486+ valueLabelVisible: false,
487+ valueTinted: false
488+ },
489+ {
490+ tag: "Confirmation notification with value",
491+ type: Notification.Confirmation,
492+ hints: {"x-canonical-non-shaped-icon": "true"},
493+ summary: "",
494+ body: "",
495+ icon: "image://theme/audio-volume-medium",
496+ secondaryIcon: "",
497+ value: 50,
498+ actions: [],
499+ summaryVisible: false,
500+ bodyVisible: false,
501+ iconVisible: false,
502+ centeredIconVisible: true,
503+ shaped: false,
412504 secondaryIconVisible: false,
413505 buttonRowVisible: false,
414506 buttonTinted: false,
415− hasSound: false
507+ hasSound: false,
508+ valueVisible: true,
509+ valueLabelVisible: false,
510+ valueTinted: false
511+ },
512+ {
513+ tag: "Confirmation notification with value, label and tint",
514+ type: Notification.Confirmation,
515+ hints: {"x-canonical-non-shaped-icon": "true",
516+ "x-canonical-value-bar-tint" : "true"},
517+ summary: "",
518+ body: "High Volume",
519+ icon: "image://theme/audio-volume-high",
520+ secondaryIcon: "",
521+ value: 85,
522+ actions: [],
523+ summaryVisible: false,
524+ bodyVisible: false,
525+ iconVisible: false,
526+ centeredIconVisible: true,
527+ shaped: false,
528+ secondaryIconVisible: false,
529+ buttonRowVisible: false,
530+ buttonTinted: false,
531+ hasSound: false,
532+ valueVisible: true,
533+ valueLabelVisible: true,
534+ valueTinted: true
416535 }
417536 ]
418537 }
@@ -450,17 +569,29 @@ Row {
450569 waitForRendering(notification);
451570
452571 var icon = findChild(notification, "icon")
453− var shapedIcon = findChild(notification, "shapedIcon")
454− var nonShapedIcon = findChild(notification, "nonShapedIcon")
572+ var centeredIcon = findChild(notification, "centeredIcon")
455573 var interactiveArea = findChild(notification, "interactiveArea")
456574 var secondaryIcon = findChild(notification, "secondaryIcon")
457575 var summaryLabel = findChild(notification, "summaryLabel")
458576 var bodyLabel = findChild(notification, "bodyLabel")
459577 var buttonRow = findChild(notification, "buttonRow")
578+ var valueIndicator = findChild(notification, "valueIndicator")
579+ var valueLabel = findChild(notification, "valueLabel")
580+ var innerBar = findChild(notification, "innerBar")
460581
461582 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
462− compare(shapedIcon.visible, data.shapedIcon, "shaped-icon visibility is incorrect")
463− compare(nonShapedIcon.visible, data.nonShapedIcon, "non-shaped-icon visibility is incorrect")
583+ if (icon.visible) {
584+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
585+ }
586+ compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
587+ if (centeredIcon.visible) {
588+ compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
589+ }
590+ compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
591+ if (valueIndicator.visible) {
592+ verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
593+ }
594+ compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
464595
465596 // test input does not fall through
466597 mouseClick(notification, notification.width / 2, notification.height / 2)
tests/qmltests/Panel/IndicatorTest.qmlnew file+80
@@ -0,0 +1,80 @@
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.1
18+import QtQuick.Layouts 1.1
19+import QtTest 1.0
20+import "../../../qml/Panel"
21+import Ubuntu.Components 0.1
22+import Unity.Test 0.1 as UT
23+import Unity.Indicators 0.1 as Indicators
24+
25+Rectangle {
26+ id: root
27+ color: "white"
28+
29+ property alias indicatorsModel: __visibleIndicatorsModel
30+ property alias originalModelData: __indicatorsModel.originalModelData
31+
32+ Indicators.VisibleIndicatorsModel {
33+ id: __visibleIndicatorsModel
34+
35+ model: __indicatorsModel
36+ }
37+
38+ Indicators.IndicatorsModel {
39+ id: __indicatorsModel
40+ Component.onCompleted: load();
41+ }
42+
43+ function insertIndicator(index) {
44+ var i;
45+ var insertIndex = 0;
46+ var done = false;
47+ for (i = index; !done && i >= 1; i--) {
48+
49+ var lookFor = __indicatorsModel.originalModelData[i-1]["identifier"]
50+
51+ var j;
52+ for (j = __indicatorsModel.modelData.length-1; !done && j >= 0; j--) {
53+ if (__indicatorsModel.modelData[j]["identifier"] === lookFor) {
54+ insertIndex = j+1;
55+ done = true;
56+ }
57+ }
58+ }
59+ __indicatorsModel.insert(insertIndex, __indicatorsModel.originalModelData[index]);
60+ }
61+
62+ function removeIndicator(index) {
63+ var i;
64+ for (i = 0; i < __indicatorsModel.modelData.length; i++) {
65+ if (__indicatorsModel.modelData[i]["identifier"] === __indicatorsModel.originalModelData[index]["identifier"]) {
66+ __indicatorsModel.remove(i);
67+ break;
68+ }
69+ }
70+ }
71+
72+ function setIndicatorVisible(index, visible) {
73+ var identifier = __indicatorsModel.originalModelData[index]["identifier"];
74+ __indicatorsModel.setIndicatorVisible(identifier, visible);
75+ }
76+
77+ function resetData() {
78+ __indicatorsModel.load();
79+ }
80+}
tests/qmltests/Panel/Indicators/tst_CachedUnityMenuModel.qmldeleted−137
@@ -1,137 +0,0 @@
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−}
tests/qmltests/Panel/Indicators/tst_DefaultIndicatorWidget.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 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 "../../../../qml/Panel/Indicators"
22−
23−Item {
24− id: testView
25− width: units.gu(40)
26− height: units.gu(70)
27−
28− DefaultIndicatorWidget {
29− id: widget
30−
31− anchors {
32− left: parent.left
33− top: parent.top
34− }
35−
36− menuModel: UnityMenuModel {}
37− busName: "test"
38− actionsObjectPath: "test"
39− deviceMenuObjectPath: "test"
40−
41− rootMenuType: ""
42−
43− iconSize: units.gu(3.2)
44− height: units.gu(3)
45− }
46−
47− UT.UnityTestCase {
48− name: "DefaultIndicatorWidget"
49− when: windowShown
50−
51− function init() {
52− widget.rootActionState.icons = [];
53− widget.rootActionState.leftLabel = "";
54− widget.rootActionState.rightLabel = "";
55− waitForRendering(widget)
56− }
57−
58− // FIXME: add tests
59− }
60−}
tests/qmltests/Panel/tst_ActiveCallHint.qml−1
@@ -19,7 +19,6 @@ import QtTest 1.0
1919import Unity.Test 0.1 as UT
2020import Ubuntu.Telephony 0.1 as Telephony
2121import Unity.Application 0.1
22−import ".."
2322import "../../../qml/Panel"
2423
2524Item {
tests/qmltests/Panel/tst_IndicatorItem.qml+173−18
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,37 +14,192 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
17+import QtQuick 2.1
18+import QtQuick.Layouts 1.1
1819import QtTest 1.0
19−import ".."
20−import "../../../qml/Panel"
2120import Ubuntu.Components 0.1
2221import Unity.Test 0.1 as UT
22+import "../../../qml/Panel"
2323
2424Rectangle {
25− width: units.gu(10)
26− height: units.gu(5)
27− color: "black"
25+ width: units.gu(80)
26+ height: units.gu(30)
27+ color: "white"
2828
29− IndicatorItem {
30− id: indicatorItem
29+ RowLayout {
3130 anchors.fill: parent
31+ anchors.margins: units.gu(1)
32+
33+ Rectangle {
34+ id: itemArea
35+ color: "blue"
36+ Layout.fillWidth: true
37+ Layout.fillHeight: true
38+
39+ Rectangle {
40+ color: "black"
41+ anchors.fill: indicatorItem
42+ }
43+
44+ IndicatorItem {
45+ id: indicatorItem
46+ height: expanded ? units.gu(7) : units.gu(3)
47+ anchors.centerIn: parent
48+ identifier: "indicator-test"
49+
50+ rootActionState {
51+ title: titleLabel.text
52+ leftLabel : leftLabel.text
53+ rightLabel : rightLabel.text
54+ icons : {
55+ var icons = [];
56+ var i = 0;
57+ if (iconEnabled.checked) {
58+ for (i = 0; i < String(iconCount.text); i++) {
59+ icons.push("image://theme/audio-volume-high");
60+ }
61+ }
62+ return icons;
63+ }
64+ }
65+
66+ Behavior on height {
67+ NumberAnimation {
68+ id: heightAnimation
69+ duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing
70+ }
71+ }
72+ }
73+ }
74+
75+ ColumnLayout {
76+ Layout.alignment: Qt.AlignTop
77+ Layout.fillWidth: false
78+
79+ Button {
80+ id: expandButton
81+ Layout.fillWidth: true
82+ text: indicatorItem.expanded ? "Collapse" : "Expand"
83+ onClicked: indicatorItem.expanded = !indicatorItem.expanded
84+ }
85+
86+ Button {
87+ id: selectButton
88+ Layout.fillWidth: true
89+ text: indicatorItem.selected ? "Unselect" : "Select"
90+ onClicked: indicatorItem.selected = !indicatorItem.selected
91+ }
92+
93+ Rectangle {
94+ Layout.preferredHeight: units.dp(1);
95+ Layout.fillWidth: true;
96+ color: "black"
97+ }
98+
99+ RowLayout {
100+ CheckBox { id: iconEnabled; checked: true }
101+ Label { text: "icons Count:" }
102+ TextField { id: iconCount; text: "1"; enabled: iconEnabled.checked }
103+ }
104+
105+ RowLayout {
106+ Label { text: "Left Label:" }
107+ TextField { id: leftLabel; text: "Left"}
108+ }
109+
110+ RowLayout {
111+ Label { text: "Right Label:" }
112+ TextField { id: rightLabel; text: "Right"}
113+ }
114+
115+ RowLayout {
116+ Label { text: "Title:" }
117+ TextField { id: titleLabel; text: "Title"}
118+ }
119+ }
32120 }
33121
34122 UT.UnityTestCase {
35123 name: "IndicatorItem"
124+ when: windowShown
125+
126+ function init() {
127+ indicatorItem.selected = false;
128+ indicatorItem.expanded = false;
129+ indicatorItem.rootActionState.title = "Test Title";
130+ indicatorItem.rootActionState.leftLabel = "TestLeftLabel";
131+ indicatorItem.rootActionState.rightLabel = "TestRightLabel";
132+ indicatorItem.rootActionState.icons = [ "image://theme/audio-volume-high" ];
36133
37− function test_dimmed() {
38− indicatorItem.dimmed = false;
39− tryCompareFunction(function(){return indicatorItem.opacity}, 1.0);
40− indicatorItem.dimmed = true;
41− tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
134+ tryCompare(heightAnimation, "running", false);
42135 }
43136
44− function test_empty() {
45− compare(indicatorItem.indicatorVisible, false, "IndicatorItem should not be visible.");
46− indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
47− tryCompare(indicatorItem, "indicatorVisible", true);
137+ function test_expand() {
138+ indicatorItem.expanded = true;
139+ tryCompare(indicatorItem, "height", units.gu(7));
140+ indicatorItem.expanded = false;
141+ tryCompare(indicatorItem, "height", units.gu(3));
142+ }
143+
144+ function test_minimizedVisibility() {
145+ compare(findChild(indicatorItem, "leftLabel").opacity, 1.0);
146+ compare(findChild(indicatorItem, "rightLabel").opacity, 1.0);
147+ compare(findChild(indicatorItem, "icons").opacity, 1.0);
148+ compare(findChild(indicatorItem, "indicatorName").opacity, 0.0);
149+ }
150+
151+ function test_expandIcon() {
152+ indicatorItem.expanded = true;
153+
154+ tryCompare(findChild(indicatorItem, "leftLabel"), "opacity", 0.0);
155+ tryCompare(findChild(indicatorItem, "rightLabel"), "opacity", 0.0);
156+ tryCompare(findChild(indicatorItem, "icons"), "opacity", 1.0);
157+ tryCompare(findChild(indicatorItem, "indicatorName"), "opacity", 1.0);
158+ }
159+
160+ function test_expandRightLabel() {
161+ indicatorItem.expanded = true;
162+
163+ indicatorItem.rootActionState.icons = [];
164+
165+ tryCompare(findChild(indicatorItem, "leftLabel"), "opacity", 0.0);
166+ tryCompare(findChild(indicatorItem, "rightLabel"), "opacity", 1.0);
167+ tryCompare(findChild(indicatorItem, "icons"), "opacity", 0.0);
168+ tryCompare(findChild(indicatorItem, "indicatorName"), "opacity", 1.0);
169+ }
170+
171+ function test_expandLeftLabel() {
172+ indicatorItem.expanded = true;
173+
174+ indicatorItem.rootActionState.rightLabel = "";
175+ indicatorItem.rootActionState.icons = [];
176+
177+ tryCompare(findChild(indicatorItem, "rightLabel"), "opacity", 0.0);
178+ tryCompare(findChild(indicatorItem, "leftLabel"), "opacity", 1.0);
179+ tryCompare(findChild(indicatorItem, "icons"), "opacity", 0.0);
180+ tryCompare(findChild(indicatorItem, "indicatorName"), "opacity", 1.0);
181+ }
182+
183+ function test_select() {
184+ tryCompare(findChild(indicatorItem, "icon0"), "color", "#ededed");
185+ tryCompare(findChild(indicatorItem, "icon0"), "opacity", 1.0);
186+ tryCompare(findChild(indicatorItem, "leftLabel"), "color", "#ededed");
187+ tryCompare(findChild(indicatorItem, "rightLabel"), "color", "#ededed");
188+ tryCompare(findChild(indicatorItem, "indicatorName"), "color", "#ededed");
189+
190+ indicatorItem.expanded = true;
191+ tryCompare(findChild(indicatorItem, "icon0"), "color", "#4c4c4c");
192+ tryCompare(findChild(indicatorItem, "icon0"), "opacity", 0.6);
193+ tryCompare(findChild(indicatorItem, "leftLabel"), "color", "#4c4c4c");
194+ tryCompare(findChild(indicatorItem, "rightLabel"), "color", "#4c4c4c");
195+ tryCompare(findChild(indicatorItem, "indicatorName"), "color", "#4c4c4c");
196+
197+ indicatorItem.selected = true;
198+ tryCompare(findChild(indicatorItem, "icon0"), "color", "#ededed");
199+ tryCompare(findChild(indicatorItem, "icon0"), "opacity", 1.0);
200+ tryCompare(findChild(indicatorItem, "leftLabel"), "color", "#ededed");
201+ tryCompare(findChild(indicatorItem, "rightLabel"), "color", "#ededed");
202+ tryCompare(findChild(indicatorItem, "indicatorName"), "color", "#ededed");
48203 }
49204 }
50205}
tests/qmltests/Panel/tst_IndicatorItemRow.qmlnew file+290
@@ -0,0 +1,290 @@
1+/*
2+ * Copyright 2013-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.1
18+import QtQuick.Layouts 1.1
19+import QtTest 1.0
20+import "../../../qml/Panel"
21+import Ubuntu.Components 0.1
22+import Unity.Test 0.1 as UT
23+import Unity.Indicators 0.1 as Indicators
24+
25+IndicatorTest {
26+ id: root
27+ width: units.gu(120)
28+ height: units.gu(40)
29+ color: "white"
30+
31+ RowLayout {
32+ anchors.fill: parent
33+ anchors.margins: units.gu(1)
34+
35+ Rectangle {
36+ Layout.fillWidth: true
37+ Layout.fillHeight: true
38+
39+ id: itemArea
40+ color: "blue"
41+
42+ Rectangle {
43+ color: "black"
44+ anchors.fill: indicatorsRow
45+ }
46+
47+ IndicatorItemRow {
48+ id: indicatorsRow
49+ height: expanded ? units.gu(7) : units.gu(3)
50+ anchors.centerIn: parent
51+ indicatorsModel: root.indicatorsModel
52+ enableLateralChanges: ma.pressed
53+
54+ Behavior on height {
55+ NumberAnimation {
56+ id: heightAnimation
57+ duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing
58+ }
59+ }
60+
61+ MouseArea {
62+ id: ma
63+ anchors.fill: parent
64+ onPositionChanged: {
65+ indicatorsRow.lateralPosition = mouse.x;
66+ }
67+ onPressed: {
68+ if (pressed) {
69+ indicatorsRow.lateralPosition = mouse.x;
70+ indicatorsRow.selectItemAt(mouse.x);
71+ }
72+ }
73+ }
74+ }
75+ }
76+
77+ ColumnLayout {
78+ Layout.alignment: Qt.AlignTop
79+ Layout.fillWidth: false
80+
81+ Button {
82+ Layout.fillWidth: true
83+ text: indicatorsRow.expanded ? "Collapse" : "Expand"
84+ onClicked: indicatorsRow.expanded = !indicatorsRow.expanded
85+ }
86+
87+ Rectangle {
88+ Layout.preferredHeight: units.dp(1);
89+ Layout.fillWidth: true;
90+ color: "black"
91+ }
92+
93+ Repeater {
94+ model: root.originalModelData
95+ RowLayout {
96+ CheckBox {
97+ checked: true
98+ onCheckedChanged: checked ? insertIndicator(index) : removeIndicator(index);
99+ }
100+ Label {
101+ Layout.fillWidth: true
102+ text: modelData["identifier"]
103+ }
104+
105+ CheckBox {
106+ checked: true
107+ onCheckedChanged: setIndicatorVisible(index, checked);
108+ }
109+ Label {
110+ text: "visible"
111+ }
112+ }
113+ }
114+ }
115+ }
116+
117+ UT.UnityTestCase {
118+ name: "IndicatorItemRow"
119+ when: windowShown
120+
121+ function init() {
122+ root.resetData();
123+
124+ indicatorsRow.resetCurrentItem();
125+ indicatorsRow.lateralPosition = -1;
126+
127+ indicatorsRow.expanded = false;
128+ tryCompare(heightAnimation, "running", false);
129+ tryCompare(findChild(indicatorsRow, "highlight"), "highlightCenterOffset", 0);
130+ wait(1); // row seems to take a bit of time for item x values to update.
131+ }
132+
133+ function wait_for_expansion_to_settle() {
134+ tryCompare(heightAnimation, "running", false);
135+ wait(200); // put a little extra wait in for things to settle
136+ }
137+
138+ function test_indicatorRowChanges_data() {
139+ return [
140+ { remove: [0, 2] },
141+ { remove: [0, 1, 2, 3, 4] },
142+ ];
143+ }
144+
145+ // test the changes in the available indicators updates the
146+ // indicators that are visible.
147+ function test_indicatorRowChanges(data) {
148+ var i;
149+ var item;
150+ var itemsToRemove = [0, 2];
151+
152+ verify(root.originalModelData.length > 0);
153+ for (i = 0; i < root.originalModelData.length; i++) {
154+ item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem");
155+ verify(item);
156+
157+ compare(item.ownIndex, i, "Item at incorrect index");
158+ }
159+
160+ for (i = data.remove.length-1; i >= 0; i--) {
161+ removeIndicator(data.remove[i]);
162+ }
163+
164+ // test removals
165+ for (i = 0; i < root.originalModelData.length; i++) {
166+ item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem");
167+
168+ verify(data.remove.indexOf(i) !== -1 ? (item === null) : (item !== null));
169+ }
170+
171+ // test insertion
172+ for (i = 0; i < data.remove.length; i++) {
173+ insertIndicator(data.remove[i]);
174+ }
175+
176+ for (i = 0; i < root.originalModelData.length; i++) {
177+ item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem");
178+ verify(item);
179+
180+ compare(item.ownIndex, i, "Item at incorrect index");
181+ }
182+ }
183+
184+ function test_validCurrentItem_data() {
185+ return [
186+ { index: 0 },
187+ { index: 2 },
188+ { index: 4 }
189+ ];
190+ }
191+
192+ // test selecting the item at it's position sets the current item of the row.
193+ function test_validCurrentItem(data) {
194+ var dataItem = findChild(indicatorsRow, root.originalModelData[data.index]["identifier"] + "-panelItem");
195+ verify(dataItem !== null);
196+
197+ indicatorsRow.selectItemAt(dataItem.x + dataItem.width/2);
198+ compare(indicatorsRow.currentItem, dataItem);
199+ }
200+
201+ // tests item default selection (no item at position X)
202+ function test_invalidCurrentItem() {
203+ indicatorsRow.selectItemAt(-100);
204+ var item = findChild(indicatorsRow, root.originalModelData[0]["identifier"] + "-panelItem");
205+ compare(indicatorsRow.currentItem, item);
206+ }
207+
208+ // testing that changing the lateral position offset of the row changes the current item.
209+ function test_lateralPositionChangesCurrentItem_data() {
210+ return [
211+ { tag: "0 -> 4", from: 0, to: 4 },
212+ { tag: "3 -> 1", from: 3, to: 1 }
213+ ];
214+ }
215+
216+ function test_lateralPositionChangesCurrentItem(data) {
217+ indicatorsRow.expanded = true;
218+ wait_for_expansion_to_settle();
219+
220+ var fromItem = findChild(indicatorsRow, root.originalModelData[data.from]["identifier"] + "-panelItem");
221+ verify(fromItem !== null);
222+
223+ var toItem = findChild(indicatorsRow, root.originalModelData[data.to]["identifier"] + "-panelItem");
224+ verify(toItem !== null);
225+
226+ var fromPosition = indicatorsRow.mapFromItem(fromItem, fromItem.width/2, fromItem.height/2);
227+ var toPosition = indicatorsRow.mapFromItem(toItem, toItem.width/2, toItem.height/2);
228+
229+ mousePress(indicatorsRow, fromPosition.x, fromPosition.y);
230+ compare(indicatorsRow.currentItem, fromItem, "Initial item not selected");
231+
232+ // this uses the MouseArea above to change the indicatorRow lateralPosition
233+ mouseFlick(indicatorsRow, fromPosition.x, fromPosition.y, toPosition.x, toPosition.y, false, false, units.gu(5), 30);
234+
235+ mouseRelease(indicatorsRow, fromPosition.x, fromPosition.y);
236+ compare(indicatorsRow.currentItem, toItem, "Current item did not change to expected item");
237+ }
238+
239+ // testing that positive changes to the lateral position offset shifts the highlight offset to the right
240+ function test_positiveLateralPositionChangesHighlightOffset() {
241+ indicatorsRow.expanded = true;
242+ wait_for_expansion_to_settle();
243+
244+ var highlight = findChild(indicatorsRow, "highlight");
245+ var item = findChild(indicatorsRow, root.originalModelData[2]["identifier"] + "-panelItem");
246+ verify(item !== null);
247+ var mappedPosition = indicatorsRow.mapFromItem(item, item.width/2, item.height/2);
248+
249+ mousePress(indicatorsRow, mappedPosition.x, mappedPosition.y);
250+ var originalHightlightX = highlight.x;
251+ var offset = 1;
252+ while((highlight.x - originalHightlightX) <= units.gu(0.5) && offset < units.gu(10)) {
253+ mouseMove(indicatorsRow, mappedPosition.x + offset, mappedPosition.y, 10);
254+ offset = offset + 2;
255+ }
256+ // verify that we hit the offset
257+ verify((highlight.x - originalHightlightX) >= units.gu(0.5));
258+ mouseRelease(indicatorsRow);
259+
260+ // should go back to 0
261+ tryCompare(highlight, "highlightCenterOffset", 0);
262+ }
263+
264+ // testing that negative changes to the lateral position offset shifts the highlight offset to the left
265+ function test_negativeLateralPositionChangesHighlightOffset() {
266+ indicatorsRow.expanded = true;
267+ wait_for_expansion_to_settle();
268+
269+ var highlight = findChild(indicatorsRow, "highlight");
270+ var item = findChild(indicatorsRow, root.originalModelData[2]["identifier"] + "-panelItem");
271+ verify(item !== null);
272+ var mappedPosition = indicatorsRow.mapFromItem(item, item.width/2, item.height/2);
273+
274+ mousePress(indicatorsRow, mappedPosition.x, mappedPosition.y);
275+ var originalHightlightX = highlight.x;
276+ var offset = 1;
277+ while((highlight.x - originalHightlightX) >= -units.gu(0.5) && offset < units.gu(10)) {
278+ mouseMove(indicatorsRow, mappedPosition.x - offset, mappedPosition.y, 10);
279+ offset = offset + 2;
280+ }
281+
282+ // verify that we hit the offset
283+ verify((highlight.x - originalHightlightX) <= -units.gu(0.5));
284+ mouseRelease(indicatorsRow);
285+
286+ // should go back to 0
287+ tryCompare(findChild(indicatorsRow, "highlight"), "highlightCenterOffset", 0);
288+ }
289+ }
290+}
tests/qmltests/Panel/Indicators/tst_DefaultIndicatorPage.qml → tests/qmltests/Panel/tst_IndicatorPage.qmlrenamed+18−25
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -17,27 +17,23 @@
1717import QtQuick 2.0
1818import QtTest 1.0
1919import Unity.Test 0.1 as UT
20−import QMenuModel 0.1
20+import Unity.Indicators 0.1 as Indicators
2121import Ubuntu.Settings.Menus 0.1 as Menus
22−import "../../../../qml/Panel/Indicators"
22+import "../../../qml/Panel"
2323
2424Item {
2525 id: testView
2626 width: units.gu(40)
2727 height: units.gu(70)
2828
29− DefaultIndicatorPage {
29+ IndicatorPage {
3030 id: page
31−
3231 anchors.fill: parent
3332
34− menuModel: UnityMenuModel {}
35− busName: "test"
36− actionsObjectPath: "test"
37− menuObjectPath: "test"
38−
3933 identifier: "test-indicator"
40− rootMenuType: "com.canonical.indicator.root"
34+ busName: "com.caninical.indicator.test"
35+ actionsObjectPath: "/com/canonical/indicator/test"
36+ menuObjectPath: "/com/canonical/indicator/test"
4137
4238 factory {
4339 _map: {
@@ -130,29 +126,28 @@ Item {
130126 "submenu": []
131127 }]; // end row 1
132128
129+ function initializeMenuData(data) {
130+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicator/test",
131+ data);
132+ }
133+
133134 UT.UnityTestCase {
134− name: "DefaultIndicatorPage"
135+ name: "IndicatorPage"
135136
136137 function init() {
137− var mainMenu = findChild(page, "mainMenu");
138− page.menuModel.modelData = [];
139− verify(mainMenu.model !== null);
138+ initializeMenuData([]);
140139 }
141140
142141 function test_reloadData() {
143142 var mainMenu = findChild(page, "mainMenu");
144143
145− page.menuModel.modelData = [];
146144 tryCompare(mainMenu, "count", 0);
147145
148− page.menuModel.modelData = fullMenuData;
146+ initializeMenuData(fullMenuData);
149147 tryCompare(mainMenu, "count", 3);
150148
151− page.menuModel.modelData = [];
149+ initializeMenuData([]);
152150 tryCompare(mainMenu, "count", 0);
153−
154− page.menuModel.modelData = fullMenuData;
155− tryCompare(mainMenu, "count", 3);
156151 }
157152
158153 function test_traverse_rootMenuType_data() {
@@ -164,7 +159,7 @@ Item {
164159
165160 function test_traverse_rootMenuType(data) {
166161 page.rootMenuType = data.rootMenuType;
167− page.menuModel.modelData = fullMenuData;
162+ initializeMenuData(fullMenuData);
168163
169164 var mainMenu = findChild(page, "mainMenu");
170165 tryCompare(mainMenu, "count", data.expectedCount);
@@ -179,9 +174,7 @@ Item {
179174
180175 function test_remove_selected_item(data) {
181176 var mainMenu = findChild(page, "mainMenu");
182− page.menuModel.modelData = fullMenuData;
183−
184− tryCompare(mainMenu, "count", 3);
177+ initializeMenuData(fullMenuData);
185178
186179 var menuId = "menu"+data.remove
187180
tests/qmltests/Panel/tst_IndicatorRow.qmldeleted−160
@@ -1,160 +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 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 ".."
21−import "../../../qml/Panel"
22−import Unity.Indicators 0.1 as Indicators
23−
24−/*
25− This tests the IndicatorRow component by using a fake model to stage data in the indicators
26− A view will show with indicators at the top, as does in the shell.
27−*/
28−Item {
29− id: rootItem
30− width: units.gu(40)
31− height: units.gu(60)
32−
33− function init_test()
34− {
35− indicatorModel.load("test1");
36−
37− indicatorRow.state = "initial";
38− indicatorRow.setCurrentItemIndex(-1);
39− indicatorRow.unitProgress = 0.0;
40− }
41−
42− PanelBackground {
43− anchors.fill: indicatorRow
44− }
45−
46− IndicatorRow {
47− id: indicatorRow
48− anchors {
49− left: parent.left
50− right: parent.right
51− }
52−
53− indicatorsModel: indicatorModel
54−
55− Component.onCompleted: indicatorModel.load("test1")
56− }
57−
58− Indicators.IndicatorsModel {
59− id: indicatorModel
60− }
61−
62− UT.UnityTestCase {
63− name: "IndicatorRow"
64− when: windowShown
65−
66− function get_indicator_item(index) {
67− return findChild(indicatorRow.row, "item" + index);
68− }
69−
70− function test_set_current_item() {
71− init_test();
72− indicatorRow.setCurrentItemIndex(0);
73− compare(indicatorRow.indicatorsModel.data(indicatorRow.currentItemIndex, Indicators.IndicatorsModelRole.Identifier), "indicator-fake1", "Incorrect item at position 0");
74−
75− indicatorRow.setCurrentItemIndex(1);
76− compare(indicatorRow.indicatorsModel.data(indicatorRow.currentItemIndex, Indicators.IndicatorsModelRole.Identifier), "indicator-fake2", "Incorrect item at position 1");
77−
78− indicatorRow.setCurrentItemIndex(2);
79− compare(indicatorRow.indicatorsModel.data(indicatorRow.currentItemIndex, Indicators.IndicatorsModelRole.Identifier), "indicator-fake3", "Incorrect item at position 2");
80− }
81−
82− function test_highlight_data() {
83− return [
84− { index: 0, progress: 0.0, current: false, other: false },
85− { index: 0, progress: 0.1, current: true, other: false },
86− { index: 0, progress: 0.5, current: true, other: false },
87− { index: 0, progress: 1.0, current: true, other: false },
88− { index: 2, progress: 0.0, current: false, other: false },
89− { index: 2, progress: 0.1, current: true, other: false },
90− { index: 2, progress: 0.5, current: true, other: false },
91− { index: 2, progress: 1.0, current: true, other: false }
92− ];
93− }
94−
95− function test_highlight(data) {
96− init_test();
97−
98− indicatorRow.unitProgress = data.progress;
99− indicatorRow.setCurrentItemIndex(data.index);
100−
101− compare(indicatorRow.currentItem.highlighted, data.current, "Indicator hightlight did not match for current item");
102−
103− for (var i = 0; i < indicatorRow.row.count; i++) {
104− compare(get_indicator_item(i).highlighted, i === data.index ? data.current: data.other, "Indicator hightlight did not match for item iter");
105− }
106− }
107−
108− function test_opacity_data() {
109− return [
110− { index: 0, progress: 0.0, current: 1.0, other: 1.0 },
111− { index: 0, progress: 0.1, current: 1.0, other: 0.9 },
112− { index: 0, progress: 0.5, current: 1.0, other: 0.5 },
113− { index: 0, progress: 1.0, current: 1.0, other: 0.0 },
114− { index: 2, progress: 0.0, current: 1.0, other: 1.0 },
115− { index: 2, progress: 0.1, current: 1.0, other: 0.9 },
116− { index: 2, progress: 0.5, current: 1.0, other: 0.5 },
117− { index: 2, progress: 1.0, current: 1.0, other: 0.0 }
118− ];
119− }
120−
121− function test_opacity(data) {
122− init_test();
123−
124− indicatorRow.unitProgress = data.progress;
125− indicatorRow.setCurrentItemIndex(data.index);
126−
127− tryCompare(indicatorRow.currentItem, "opacity", data.current);
128−
129− for (var i = 0; i < indicatorRow.row.count; i++) {
130− tryCompare(get_indicator_item(i), "opacity", i === data.index ? data.current: data.other);
131− }
132− }
133−
134− function test_dimmed_data() {
135− return [
136− { index: 0, progress: 0.0, current: false, other: false },
137− { index: 0, progress: 0.1, current: false, other: true },
138− { index: 0, progress: 0.5, current: false, other: true },
139− { index: 0, progress: 1.0, current: false, other: true },
140− { index: 2, progress: 0.0, current: false, other: false },
141− { index: 2, progress: 0.1, current: false, other: true },
142− { index: 2, progress: 0.5, current: false, other: true },
143− { index: 2, progress: 1.0, current: false, other: true }
144− ];
145− }
146−
147− function test_dimmed(data) {
148− init_test();
149−
150− indicatorRow.unitProgress = data.progress;
151− indicatorRow.setCurrentItemIndex(data.index);
152−
153− compare(indicatorRow.currentItem.dimmed, data.current, "Indicator dim did not match for current item");
154−
155− for (var i = 0; i < indicatorRow.row.count; i++) {
156− compare(get_indicator_item(i).dimmed, i === data.index ? data.current: data.other, "Indicator dim did not match for item iter");
157− }
158− }
159− }
160−}
tests/qmltests/Panel/tst_Indicators.qmldeleted−230
@@ -1,230 +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 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 Ubuntu.Components 0.1 as UC
21−import ".."
22−import "../../../qml/Panel"
23−import "../../../qml/Components"
24−
25−/*
26− This tests the Indicators component by using a fake model to stage data in the indicators
27− A view will show with indicators at the top, as does in the shell. There is a clickable area
28− marked "Click Me" which can be used to expose the indicators.
29−*/
30−Item {
31− id: shell
32− width: units.gu(40)
33− height: units.gu(80)
34−
35− PanelBackground {
36− anchors.fill: indicators
37− }
38−
39− Indicators {
40− id: indicators
41− anchors {
42− right: parent.right
43− }
44− width: (shell.width > units.gu(60)) ? units.gu(40) : shell.width
45− y: 0
46− shown: false
47− profile: "test1"
48−
49− openedHeight: parent.height - button.height
50− }
51−
52− UC.Button {
53− id: button
54− text: indicators.shown ? "Hide" : "Show"
55− anchors {
56− bottom: shell.bottom
57− left: parent.left
58− right: parent.right
59− }
60− height: 50
61−
62− onClicked: {
63− if (!indicators.shown) {
64− indicators.show();
65− } else {
66− indicators.hide();
67− }
68− }
69− }
70−
71− UT.UnityTestCase {
72− name: "Indicators"
73− when: windowShown
74−
75− function init() {
76− indicators.initialise();
77−
78− indicators.hide();
79− tryCompare(indicators.hideAnimation, "running", false);
80− tryCompare(indicators, "state", "initial");
81− }
82−
83− // Showing the indicators should fully open the indicator panel.
84− function test_show() {
85− indicators.show()
86− tryCompare(indicators, "fullyOpened", true);
87− }
88−
89− // Test the change in the revealer lateral position changes the current panel menu to fit the position
90− // of the indicator in the row.
91− function test_change_revealer_lateral_position()
92− {
93− // tests changing the lateral position of the revealer activates the correct indicator items.
94−
95− var indicatorRow = findChild(indicators, "indicatorRow")
96− verify(indicatorRow !== null);
97− var indicatorRowItems = findChild(indicatorRow, "indicatorRowItems");
98− verify(indicatorRowItems !== null);
99−
100− for (var i = 0; i < indicatorRowItems.count; i++) {
101− var indicatorItem = findChild(indicatorRowItems, "item" + i);
102−
103− if (!indicatorItem.visible)
104− continue;
105−
106− var indicatorPosition = indicators.mapFromItem(indicatorItem,
107− indicatorItem.width/2, indicatorItem.height/2);
108−
109− touchFlick(indicators,
110− indicatorPosition.x, indicatorPosition.y,
111− indicatorPosition.x, indicators.openedHeight * 0.4,
112− true /* beginTouch */, false /* endTouch */);
113−
114− compare(indicatorRow.currentItem, indicatorItem,
115− "Incorrect item activated at position " + i);
116−
117− touchFlick(indicators,
118− indicatorPosition.x, indicators.openedHeight * 0.4,
119− indicatorPosition.x, indicatorPosition.y,
120− false /* beginTouch */, true /* endTouch */);
121−
122− // wait until fully closed
123− tryCompare(indicators, "height", indicators.panelHeight);
124− }
125− }
126−
127− // values for specific state changes are subject to internal decisions, so we can't
128− // determine the true height value which would cause the state to change without making
129− // too many assuptyions
130− // However, we can assume that a partially opened panel will not be initial, and fully
131− // opened panel will be locked.
132−
133− function test_progress_changes_state_to_not_initial() {
134− indicators.height = indicators.openedHeight / 2
135− compare(indicators.state!="initial", true,
136− "Indicators should not be in initial state when partially opened.");
137− }
138−
139− function test_progress_changes_state_to_locked() {
140− indicators.height = indicators.openedHeight - indicators.panelHeight
141− compare(indicators.state, "locked", "Indicators should be locked when fully opened.");
142− }
143−
144− function test_partially_open() {
145− indicators.height = indicators.openedHeight / 2
146− compare(indicators.partiallyOpened, true,
147− "Indicator should show as partially opened when height is half of openedHeight");
148− compare(indicators.fullyOpened, false,
149− "Indicator should not show as fully opened when height is half of openedHeight");
150− }
151−
152− function test_fully_open() {
153− indicators.height = indicators.openedHeight
154− compare(indicators.partiallyOpened, false);
155− compare(indicators.fullyOpened, true);
156− }
157−
158− function init_invisible_indicator(identifier) {
159− tryCompareFunction(function() { return findChild(indicators, identifier+"-delegate") !== undefined }, true);
160− var item = findChild(indicators, identifier+"-delegate");
161−
162− item.enabled = false;
163− }
164−
165− function test_row_visible_menuContent_visible_data() { return [
166− {tag: "first", visible: [false, true, true, true, true] },
167− {tag: "adjacent", visible: [true, false, false, true, true] },
168− {tag: "bounds", visible: [false, true, true, true, false] },
169− {tag: "disjoint", visible: [true, false, true, false, true] },
170− {tag: "last", visible: [true, true, true, true, false] }];
171− }
172−
173− function test_row_visible_menuContent_visible(data) {
174− indicators.show();
175−
176− var contentListView = findChild(indicators, "indicatorsContentListView");
177− var indicatorRowItems = findChild(indicators, "indicatorRowItems");
178−
179− var count = data.visible.length
180− for (var i = 0; i< data.visible.length; i++) {
181− if (data.visible[i] === false) {
182− init_invisible_indicator("indicator-fake" + (i + 1));
183− count--;
184− }
185− }
186−
187− tryCompare(indicatorRowItems, "count", count);
188−
189− for (i = 0; i < data.visible.length; i++) {
190− var widgetName = "indicator-fake" + (i + 1 + "-widget");
191− var pageName = "indicator-fake" + (i + 1 + "-page");
192−
193− // check for item
194− tryCompareFunction(function() { return findChild(indicatorRowItems, widgetName) !== null }, data.visible[i]);
195−
196− // check for tab
197− tryCompareFunction(function() { return findChild(contentListView, pageName) !== null }, data.visible[i]);
198− }
199− }
200−
201− function test_indicator_visible_correct_menu_data() { return [
202− {tag: "current-first", currentIndex: 0, visible: [false, true, true, true, true], expectedIndex: 0, expextedMenu: "indicator-fake2" },
203− {tag: "current-last", currentIndex: 4, visible: [true, true, true, true, false], expectedIndex: 3, expextedMenu: "indicator-fake4" },
204− {tag: "after", currentIndex: 0, visible: [true, false, true, true, true], expectedIndex: 0, expextedMenu: "indicator-fake1" },
205− {tag: "before", currentIndex: 1, visible: [false, true, true, true, true], expectedIndex: 1, expextedMenu: "indicator-fake2" }];
206− }
207−
208− function test_indicator_visible_correct_menu(data) {
209− var contentListView = findChild(indicators, "indicatorsContentListView");
210− var indicatorRow = findChild(indicators, "indicatorRow");
211−
212− indicators.show();
213− indicatorRow.setCurrentItemIndex(data.currentIndex);
214− tryCompare(indicators, "fullyOpened", true);
215−
216− for (var i = 0; i< data.visible.length; i++) {
217− if (data.visible[i] === false) {
218− init_invisible_indicator("indicator-fake" + (i + 1));
219− }
220− }
221−
222− // check for current selected item
223− tryCompare(indicatorRow, "currentItemIndex", data.expectedIndex);
224−
225− // check for current selected tab
226− tryCompareFunction(function() { return findChild(contentListView, data.expextedMenu) === contentListView.currentItem }, true);
227−
228− }
229− }
230−}
tests/qmltests/Panel/tst_IndicatorsBar.qmlnew file+224
@@ -0,0 +1,224 @@
1+/*
2+ * Copyright 2013-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.1
18+import QtQuick.Layouts 1.1
19+import QtTest 1.0
20+import "../../../qml/Panel"
21+import Ubuntu.Components 0.1
22+import Unity.Test 0.1 as UT
23+import Unity.Indicators 0.1 as Indicators
24+
25+IndicatorTest {
26+ id: root
27+ width: units.gu(100)
28+ height: units.gu(40)
29+
30+
31+ RowLayout {
32+ anchors.fill: parent
33+ anchors.margins: units.gu(1)
34+
35+ Rectangle {
36+ id: itemArea
37+ color: "blue"
38+ Layout.fillWidth: true
39+ Layout.fillHeight: true
40+
41+ Rectangle {
42+ color: "black"
43+ anchors.fill: indicatorsBar
44+ }
45+
46+ IndicatorsBar {
47+ id: indicatorsBar
48+ height: expanded ? units.gu(7) : units.gu(3)
49+ width: units.gu(30)
50+ anchors.centerIn: parent
51+ indicatorsModel: root.indicatorsModel
52+ interactive: expanded && height === units.gu(7)
53+
54+ Behavior on height {
55+ NumberAnimation {
56+ id: heightAnimation
57+ duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
58+ }
59+ }
60+
61+ MouseArea {
62+ anchors.fill: parent
63+ enabled: !indicatorsBar.expanded
64+ onPressed: {
65+ indicatorsBar.selectItemAt(mouse.x);
66+ indicatorsBar.expanded = true
67+ }
68+ }
69+ }
70+ }
71+
72+ ColumnLayout {
73+ Layout.alignment: Qt.AlignTop
74+ Layout.fillWidth: false
75+
76+ Button {
77+ Layout.fillWidth: true
78+ text: indicatorsBar.expanded ? "Collapse" : "Expand"
79+ onClicked: indicatorsBar.expanded = !indicatorsBar.expanded
80+ }
81+
82+ Rectangle {
83+ Layout.preferredHeight: units.dp(1);
84+ Layout.fillWidth: true;
85+ color: "black"
86+ }
87+
88+ Repeater {
89+ model: root.originalModelData
90+ RowLayout {
91+ CheckBox {
92+ checked: true
93+ onCheckedChanged: checked ? insertIndicator(index) : removeIndicator(index);
94+ }
95+ Label {
96+ Layout.fillWidth: true
97+ text: modelData["identifier"]
98+ }
99+
100+ CheckBox {
101+ checked: true
102+ onCheckedChanged: setIndicatorVisible(index, checked);
103+ }
104+ Label {
105+ text: "visible"
106+ }
107+ }
108+ }
109+ }
110+ }
111+
112+ UT.UnityTestCase {
113+ name: "IndicatorsBar"
114+ when: windowShown
115+
116+ function init() {
117+ indicatorsBar.expanded = false;
118+ wait_for_expansion_to_settle();
119+ }
120+
121+ function test_expandSelectedItem_data() {
122+ return [
123+ { index: 0 },
124+ { index: 2 },
125+ { index: 4 }
126+ ];
127+ }
128+
129+ function wait_for_expansion_to_settle() {
130+ tryCompare(heightAnimation, "running", false);
131+ wait(UbuntuAnimation.SnapDuration); // put a little extra wait in for things to settle
132+ }
133+
134+ // Rough check that expanding a selected item keeps it within the area of the original item.
135+ function test_expandSelectedItem(data) {
136+ var dataItem = findChild(indicatorsBar, root.originalModelData[data.index]["identifier"] + "-panelItem");
137+ verify(dataItem !== null);
138+
139+ var mappedPosition = indicatorsBar.mapFromItem(dataItem, dataItem.width/2, dataItem.height/2);
140+
141+ indicatorsBar.selectItemAt(mappedPosition.x);
142+ indicatorsBar.expanded = true;
143+ wait_for_expansion_to_settle();
144+
145+
146+ // mappedPosition contained within mappedRect
147+ tryCompareFunction(function() {
148+ var mappedRect = indicatorsBar.mapFromItem(dataItem, 0, 0, dataItem.width, dataItem.height);
149+ return mappedRect.x <= mappedPosition.x; },
150+ true);
151+ tryCompareFunction(function() {
152+ var mappedRect = indicatorsBar.mapFromItem(dataItem, 0, 0, dataItem.width, dataItem.height);
153+ return mappedRect.x + mappedRect.width >= mappedPosition.x;
154+ }, true);
155+ }
156+
157+ function test_scrollOffset() {
158+ indicatorsBar.expanded = true;
159+ wait_for_expansion_to_settle();
160+
161+ var lastItemIndex = root.originalModelData.length-1;
162+ var dataItem = findChild(indicatorsBar, root.originalModelData[lastItemIndex]["identifier"] + "-panelItem");
163+ verify(dataItem !== null);
164+
165+ var row = findChild(indicatorsBar, "indicatorItemRow");
166+ // test will not work without these conditions
167+ verify(row.width >= indicatorsBar.width + dataItem.width);
168+
169+ var mappedPosition = indicatorsBar.mapFromItem(dataItem, dataItem.width/2, dataItem.height/2);
170+ indicatorsBar.addScrollOffset(-dataItem.width);
171+ var newMappedPosition = indicatorsBar.mapFromItem(dataItem, dataItem.width/2, dataItem.height/2);
172+
173+ compare(mappedPosition.x, newMappedPosition.x - dataItem.width);
174+ }
175+
176+ function test_selectItemWhenExpanded_data() {
177+ return [
178+ { index: 3 },
179+ { index: 4 }
180+ ];
181+ }
182+
183+ function test_selectItemWhenExpanded(data) {
184+ indicatorsBar.expanded = true;
185+ wait_for_expansion_to_settle();
186+
187+ var dataItem = findChild(indicatorsBar, root.originalModelData[data.index]["identifier"] + "-panelItem");
188+ if (indicatorsBar.mapFromItem(dataItem, dataItem.width/2, dataItem.height/2).x < 0) {
189+ skip("Out of bounds");
190+ }
191+ mouseClick(dataItem, dataItem.width/2, dataItem.height/2);
192+ verify(dataItem.selected === true);
193+ }
194+
195+ function test_visibleIndicators_data() {
196+ return [
197+ { visible: [true, false, true, false, true, true] },
198+ { visible: [false, false, false, false, false, false] }
199+ ];
200+ }
201+
202+ function test_visibleIndicators(data) {
203+ for (var i = 0; i < data.visible.length; i++) {
204+ var visible = data.visible[i];
205+ root.setIndicatorVisible(i, visible);
206+
207+ var dataItem = findChild(indicatorsBar, root.originalModelData[i]["identifier"] + "-panelItem");
208+ tryCompare(dataItem, "opacity", visible ? 1.0 : 0.0);
209+ tryCompareFunction(function() { return dataItem.width > 0.0; }, visible);
210+ }
211+
212+ indicatorsBar.expanded = true;
213+ wait_for_expansion_to_settle();
214+
215+ for (var i = 0; i < data.visible.length; i++) {
216+ root.setIndicatorVisible(i, data.visible[i]);
217+
218+ var dataItem = findChild(indicatorsBar, root.originalModelData[i]["identifier"] + "-panelItem");
219+ tryCompareFunction(function() { return dataItem.opacity > 0.0; }, true);
220+ tryCompareFunction(function() { return dataItem.width > 0.0; }, true);
221+ }
222+ }
223+ }
224+}
tests/qmltests/Panel/tst_IndicatorsMenu.qmlnew file+258
@@ -0,0 +1,258 @@
1+/*
2+ * Copyright 2013-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.1
18+import QtQuick.Layouts 1.1
19+import QtTest 1.0
20+import "../../../qml/Panel"
21+import Ubuntu.Components 0.1
22+import Unity.Test 0.1 as UT
23+import Unity.Indicators 0.1 as Indicators
24+
25+IndicatorTest {
26+ id: root
27+ width: units.gu(80)
28+ height: units.gu(71)
29+ color: "white"
30+
31+ property string indicatorProfile: "phone"
32+
33+ RowLayout {
34+ anchors.fill: parent
35+ anchors.margins: units.gu(1)
36+
37+ Rectangle {
38+ Layout.fillWidth: true
39+ Layout.fillHeight: true
40+
41+ id: itemArea
42+ color: "blue"
43+
44+ IndicatorsMenu {
45+ id: indicatorsMenu
46+ width: units.gu(40)
47+ anchors {
48+ top: parent.top
49+ right: parent.right
50+ }
51+ minimizedPanelHeight: units.gu(3)
52+ expandedPanelHeight: units.gu(7)
53+ openedHeight: parent.height
54+ indicatorsModel: root.indicatorsModel
55+ shown: false
56+ }
57+ }
58+
59+ ColumnLayout {
60+ Layout.alignment: Qt.AlignTop
61+ Layout.fillWidth: false
62+
63+ Button {
64+ Layout.fillWidth: true
65+ text: indicatorsMenu.shown ? "Hide" : "Show"
66+ onClicked: {
67+ if (indicatorsMenu.shown) {
68+ indicatorsMenu.hide();
69+ } else {
70+ indicatorsMenu.show();
71+ }
72+ }
73+ }
74+
75+ Rectangle {
76+ Layout.preferredHeight: units.dp(1);
77+ Layout.fillWidth: true;
78+ color: "black"
79+ }
80+
81+ Repeater {
82+ model: root.originalModelData
83+ RowLayout {
84+ CheckBox {
85+ checked: true
86+ onCheckedChanged: checked ? insertIndicator(index) : removeIndicator(index);
87+ }
88+ Label {
89+ Layout.fillWidth: true
90+ text: modelData["identifier"]
91+ }
92+
93+ CheckBox {
94+ checked: true
95+ onCheckedChanged: setIndicatorVisible(index, checked);
96+ }
97+ Label {
98+ text: "visible"
99+ }
100+ }
101+ }
102+ }
103+ }
104+
105+ UT.UnityTestCase {
106+ id: testCase
107+ name: "IndicatorsMenu"
108+ when: windowShown
109+
110+ function init() {
111+ indicatorsMenu.hide();
112+ tryCompare(indicatorsMenu.hideAnimation, "running", false);
113+ compare(indicatorsMenu.state, "initial");
114+
115+ indicatorsMenu.verticalVelocityThreshold = 0.5
116+ }
117+
118+ function get_indicator_item(index) {
119+ var indicatorItem = findChild(indicatorsMenu, root.originalModelData[index]["identifier"] + "-panelItem");
120+ verify(indicatorItem !== null);
121+
122+ return indicatorItem;
123+ }
124+
125+ // Showing the indicators should fully open the indicator panel.
126+ function test_showAndHide() {
127+ indicatorsMenu.show();
128+ tryCompare(indicatorsMenu, "fullyOpened", true);
129+
130+ indicatorsMenu.hide();
131+ tryCompare(indicatorsMenu, "fullyClosed", true);
132+ }
133+
134+ // Test that closing the indicators ends up in the correct position.
135+ function test_hideEndsInCorrectPosition() {
136+ var indicatorsBar = findChild(indicatorsMenu, "indicatorsBar");
137+ var flickable = findChild(indicatorsBar, "flickable");
138+
139+ var originalContentX = flickable.contentX;
140+
141+ indicatorsMenu.show();
142+ indicatorsBar.setCurrentItemIndex(0);
143+ tryCompare(indicatorsMenu, "fullyOpened", true);
144+
145+ indicatorsMenu.hide();
146+ tryCompare(flickable, "contentX", originalContentX);
147+ }
148+
149+ function test_progress_changes_state_to_reveal() {
150+ indicatorsMenu.height = indicatorsMenu.openedHeight / 2;
151+ compare(indicatorsMenu.state, "reveal", "Indicators should be revealing when partially opened.");
152+
153+ indicatorsMenu.height = indicatorsMenu.openedHeight;
154+ compare(indicatorsMenu.state, "reveal", "Indicators should still be revealing when fully opened.");
155+ }
156+
157+ function test_open_state() {
158+ compare(indicatorsMenu.fullyClosed, true, "Indicator should show as fully closed.");
159+ compare(indicatorsMenu.partiallyOpened, false, "Indicator should not show as partially opened");
160+ compare(indicatorsMenu.fullyOpened, false, "Indicator should not show as fully opened");
161+
162+ indicatorsMenu.height = indicatorsMenu.openedHeight / 2
163+ compare(indicatorsMenu.fullyClosed, false, "Indicator should not show as fully closed.");
164+ compare(indicatorsMenu.partiallyOpened, true, "Indicator should show as partially opened");
165+ compare(indicatorsMenu.fullyOpened, false, "Indicator should not show as fully opened");
166+
167+ indicatorsMenu.height = indicatorsMenu.openedHeight;
168+ compare(indicatorsMenu.fullyClosed, false, "Indicator should not show as fully closed.");
169+ compare(indicatorsMenu.partiallyOpened, false, "Indicator should show as partially opened");
170+ compare(indicatorsMenu.fullyOpened, true, "Indicator should not show as fully opened");
171+ }
172+
173+ // Pressing on the indicator panel should activate the indicator hints
174+ // and expose the header
175+ function test_hint() {
176+ var indicatorItem = get_indicator_item(0);
177+ var mappedPosition = root.mapFromItem(indicatorItem, indicatorItem.width/2, indicatorItem.height/2);
178+
179+ touchPress(indicatorsMenu, mappedPosition.x, indicatorsMenu.minimizedPanelHeight / 2);
180+
181+ // hint animation should be run, meaning that indicators will move downwards
182+ // by hintValue pixels without any drag taking place
183+ tryCompareFunction(function() { return indicatorsMenu.height }, indicatorsMenu.expandedPanelHeight + units.gu(2));
184+ tryCompare(indicatorsMenu, "partiallyOpened", true);
185+
186+ touchRelease(indicatorsMenu, mappedPosition.x, indicatorsMenu.minimizedPanelHeight / 2);
187+ }
188+
189+ // tests swiping on an indicator item activates the correct item.
190+ function test_swipeForCurrentItem()
191+ {
192+ var indicatorItemRow = findChild(indicatorsMenu, "indicatorItemRow");
193+ verify(indicatorItemRow !== null);
194+
195+ for (var i = 0; i < root.originalModelData.length; i++) {
196+ var indicatorItem = get_indicator_item(i);
197+
198+ var mappedPosition = root.mapFromItem(indicatorItem, indicatorItem.width/2, indicatorItem.height/2);
199+
200+ touchFlick(indicatorsMenu,
201+ mappedPosition.x, mappedPosition.y,
202+ mappedPosition.x, indicatorsMenu.openedHeight / 2,
203+ true /* beginTouch */, false /* endTouch */);
204+
205+ compare(indicatorItemRow.currentItem, indicatorItem,
206+ "Incorrect item activated at position " + i);
207+
208+ touchFlick(indicatorItemRow,
209+ mappedPosition.x, indicatorsMenu.openedHeight / 2,
210+ mappedPosition.x, mappedPosition.y,
211+ false /* beginTouch */, true /* endTouch */);
212+
213+ // wait until fully closed
214+ tryCompare(indicatorsMenu, "fullyClosed", true);
215+ }
216+ }
217+
218+ // Test the vertical velocity check when flicking the indicators open at an angle.
219+ // If the vertical velocity is above a specific point, we shouldnt change active indicators
220+ // if the x position changes
221+ function test_verticalVelocityDetector() {
222+ indicatorsMenu.verticalVelocityThreshold = 0;
223+ verify(root.originalModelData.length >= 2);
224+
225+ var indicatorItemRow = findChild(indicatorsMenu, "indicatorItemRow");
226+ verify(indicatorItemRow !== null);
227+
228+ // Get the first indicator
229+ var firstItem = get_indicator_item(0);
230+ var firstItemMappedPosition = root.mapFromItem(firstItem, firstItem.width/2, firstItem.height/2);
231+
232+ // 1) Drag the mouse down to hint a bit
233+ touchFlick(indicatorsMenu,
234+ firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight / 2,
235+ firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
236+ true /* beginTouch */, false /* endTouch */);
237+
238+ tryCompare(indicatorItemRow, "currentItem", firstItem)
239+
240+ // next time position will have moved.
241+ var nextItem = get_indicator_item(1);
242+ var nextItemMappedPosition = root.mapFromItem(nextItem, nextItem.width/2, nextItem.height/2);
243+
244+ // 1) Flick mouse down to bottom
245+ touchFlick(indicatorsMenu,
246+ firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
247+ nextItemMappedPosition.x, indicatorsMenu.openedHeight / 3,
248+ false /* beginTouch */, false /* endTouch */,
249+ units.gu(50) /* speed */, 5 /* iterations */); // more samples needed for accurate velocity
250+
251+ compare(indicatorItemRow.currentItem, firstItem, "First indicator should still be the current item");
252+ // after waiting in the same spot with touch down, it should update to the next item.
253+ tryCompare(indicatorItemRow, "currentItem", nextItem);
254+
255+ touchRelease(indicatorsMenu, nextItemMappedPosition.x, indicatorsMenu.openedHeight / 3);
256+ }
257+ }
258+}
tests/qmltests/Panel/tst_MenuContent.qml+9−25
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -17,36 +17,21 @@
1717import QtQuick 2.0
1818import QtTest 1.0
1919import Unity.Test 0.1 as UT
20−import ".."
2120import "../../../qml/Panel"
2221import Unity.Indicators 0.1 as Indicators
2322
24−Item {
25− id: shell
23+IndicatorTest {
24+ id: root
2625 width: units.gu(40)
2726 height: units.gu(70)
2827
29− property var indicator_status: {
30− 'indicator-fake1-page': { 'started': false, 'reset': 0 },
31− 'indicator-fake2-page': { 'started': false, 'reset': 0 },
32− 'indicator-fake3-page': { 'started': false, 'reset': 0 },
33− 'indicator-fake4-page': { 'started': false, 'reset': 0 },
34− 'indicator-fake5-page': { 'started': false, 'reset': 0 }
35− }
36−
3728 // Dummy objects
3829 Item { id: greeter }
3930 Item { id: handle }
4031
41−
42− Indicators.IndicatorsModel {
43− id: indicatorsModel
44− Component.onCompleted: load("test1")
45− }
46−
4732 MenuContent {
4833 id: menuContent
49− indicatorsModel: indicatorsModel
34+ indicatorsModel: root.indicatorsModel
5035 height: parent.height - 50
5136 }
5237
@@ -78,12 +63,12 @@ Item {
7863 if (menuContent.currentMenuIndex == -1)
7964 activate_content(0);
8065 else
81− activate_content((menuContent.currentMenuIndex + 1) % indicatorsModel.count)
66+ activate_content((menuContent.currentMenuIndex + 1) % root.originalModelData.length)
8267 }
8368
8469 function activate_content(index)
8570 {
86− menuContent.setCurrentMenuIndex(index)
71+ menuContent.currentMenuIndex = index;
8772 }
8873
8974 function get_test_menu_objecName(index) {
@@ -115,7 +100,7 @@ Item {
115100
116101 // Check that the correct menus are displayed for the requested item.
117102 function test_show_menu() {
118− var menuCount = indicatorsModel.count;
103+ var menuCount = root.originalModelData.length;
119104 verify(menuCount > 0, "Menu count should be greater than zero");
120105
121106 var listView = menu_content_test.findChild(menuContent, "indicatorsContentListView")
@@ -126,7 +111,7 @@ Item {
126111 var menuIndex = i%menuCount;
127112
128113 activate_content(menuIndex);
129− testItemObjectName = indicatorsModel.data(menuIndex, Indicators.IndicatorsModelRole.Identifier);
114+ testItemObjectName = indicatorsModel.model.data(menuIndex, Indicators.IndicatorsModelRole.Identifier);
130115 compare(listView.currentIndex, menuIndex, "Current tab index does not match selected tab index");
131116 tryCompareFunction(current_item_equals_test_item, true);
132117 }
@@ -134,14 +119,13 @@ Item {
134119
135120 // Tests QTBUG-30632 - asynchronous loader crashes when changing index quickly.
136121 function test_multi_activate() {
137− var menuCount = indicatorsModel.count;
122+ var menuCount = root.originalModelData.length;
138123 verify(menuCount > 0, "Menu count should be greater than zero");
139124
140125 for (var i = 0; i < 100; i++) {
141126 activate_content(i % menuCount);
142127 compare(menuContent.currentMenuIndex, i%menuCount);
143128 }
144− wait(100);
145129 }
146130 }
147131}
tests/qmltests/Panel/tst_Panel.qml+185−214
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,95 +14,106 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
17+import QtQuick 2.1
18+import QtQuick.Layouts 1.1
1819import QtTest 1.0
1920import Unity.Test 0.1 as UT
20−import Ubuntu.Components 0.1 as UC
21+import Ubuntu.Components 0.1
22+import Unity.Indicators 0.1 as Indicators
2123import Ubuntu.Telephony 0.1 as Telephony
22−import ".."
2324import "../../../qml/Panel"
2425
25−/*
26− This tests the Panel component using a fake model to stage data in the indicators
27− A view will show with indicators at the top, as does in the shell. This can be controlled
28− as in the shell.
29−*/
30−Item {
31− id: shell
32− width: units.gu(40)
33− height: units.gu(80)
34−
35− Panel {
36− id: panel
37− anchors {
38− left: parent.left
39− right: parent.right
40− }
41− height: parent.height - row.height
42− fullscreenMode: false
26+IndicatorTest {
27+ id: root
28+ width: units.gu(100)
29+ height: units.gu(71)
30+ color: "white"
4331
44− indicators {
45− profile: "test1"
46− panelHeight: units.gu(5)
47− }
48− callHint {
49− height: units.gu(4)
50− }
32+ property string indicatorProfile: "phone"
5133
52− property real panelAndSeparatorHeight: panel.indicators.panelHeight + units.dp(2)
53− }
34+ RowLayout {
35+ anchors.fill: parent
36+ anchors.margins: units.gu(1)
37+ clip: true
5438
39+ Rectangle {
40+ Layout.fillWidth: true
41+ Layout.fillHeight: true
5542
56− Row {
57− id: row
58− anchors {
59− bottom: shell.bottom
60− left: parent.left
61− right: parent.right
62− }
63− height: 50
43+ id: itemArea
44+ color: "blue"
6445
65− UC.Button {
66− text: panel.indicators.shown ? "Hide" : "Show"
67− anchors {
68− top: parent.top
69− bottom: parent.bottom
46+ Panel {
47+ id: panel
48+ anchors.fill: parent
49+ indicators {
50+ width: parent.width > units.gu(60) ? units.gu(40) : parent.width
51+ indicatorsModel: root.indicatorsModel
52+ }
53+
54+ property real panelAndSeparatorHeight: panel.indicators.minimizedPanelHeight + units.dp(2)
7055 }
71− width: parent.width/3
56+ }
7257
73− onClicked: {
74− if (panel.indicators.shown) {
75− panel.indicators.hide();
76− } else {
77− panel.indicators.show();
58+ ColumnLayout {
59+ Layout.alignment: Qt.AlignTop
60+ Layout.fillWidth: false
61+
62+ Button {
63+ Layout.fillWidth: true
64+ text: panel.indicators.shown ? "Hide" : "Show"
65+ onClicked: {
66+ if (panel.indicators.shown) {
67+ panel.indicators.hide();
68+ } else {
69+ panel.indicators.show();
70+ }
7871 }
7972 }
80− }
8173
82− UC.Button {
83− text: panel.fullscreenMode ? "Maximize" : "FullScreen"
84− anchors {
85− top: parent.top
86− bottom: parent.bottom
74+ Button {
75+ text: panel.fullscreenMode ? "Maximize" : "FullScreen"
76+ Layout.fillWidth: true
77+ onClicked: panel.fullscreenMode = !panel.fullscreenMode
8778 }
88− width: parent.width/3
8979
90− onClicked: panel.fullscreenMode = !panel.fullscreenMode
91− }
80+ Button {
81+ Layout.fillWidth: true
82+ text: callManager.hasCalls ? "Called" : "No Calls"
83+ onClicked: {
84+ if (callManager.foregroundCall) {
85+ callManager.foregroundCall = null;
86+ } else {
87+ callManager.foregroundCall = phoneCall;
88+ }
89+ }
90+ }
9291
93− UC.Button {
94− text: callManager.hasCalls ? "Called" : "No Calls"
95− anchors {
96− top: parent.top
97− bottom: parent.bottom
92+ Rectangle {
93+ Layout.preferredHeight: units.dp(1);
94+ Layout.fillWidth: true;
95+ color: "black"
9896 }
99− width: parent.width/3
10097
101− onClicked: {
102− if (callManager.foregroundCall) {
103− callManager.foregroundCall = null;
104− } else {
105− callManager.foregroundCall = phoneCall;
98+ Repeater {
99+ model: root.originalModelData
100+ RowLayout {
101+ CheckBox {
102+ checked: true
103+ onCheckedChanged: checked ? insertIndicator(index) : removeIndicator(index);
104+ }
105+ Label {
106+ Layout.fillWidth: true
107+ text: modelData["identifier"]
108+ }
109+
110+ CheckBox {
111+ checked: true
112+ onCheckedChanged: setIndicatorVisible(index, checked);
113+ }
114+ Label {
115+ text: "visible"
116+ }
106117 }
107118 }
108119 }
@@ -118,13 +129,12 @@ Item {
118129 when: windowShown
119130
120131 function init() {
121− panel.indicators.initialise();
122132 panel.fullscreenMode = false;
133+ callManager.foregroundCall = null;
123134
124135 panel.indicators.hide();
125136 // Wait for animation to complete
126137 tryCompare(panel.indicators.hideAnimation, "running", false);
127− callManager.foregroundCall = null;
128138
129139 // Wait for the indicators to get into position.
130140 // (switches between normal and fullscreen modes are animated)
@@ -133,100 +143,21 @@ Item {
133143 }
134144
135145 function get_indicator_item(index) {
136− var indicatorRow = findChild(panel.indicators, "indicatorRow");
137− verify(indicatorRow !== null);
138−
139− return findChild(indicatorRow.row, "item" + index);
140− }
141−
142− function get_indicator_item_position(index) {
143− var indicatorRow = findChild(panel.indicators, "indicatorRow");
144− verify(indicatorRow !== null);
145−
146− var indicatorItem = get_indicator_item(index);
146+ var indicatorItem = findChild(panel, root.originalModelData[index]["identifier"]+"-panelItem");
147147 verify(indicatorItem !== null);
148148
149− return panel.mapFromItem(indicatorItem, indicatorItem.width/2, indicatorItem.height/2);
150− }
151−
152− // Pressing on the indicator panel should activate the indicator hints
153− // and expose a portion of the conent.
154− function test_hint() {
155− var indicatorItemCoord = get_indicator_item_position(0);
156−
157− touchPress(panel, indicatorItemCoord.x, panel.indicators.panelHeight / 2);
158−
159− // hint animation should be run, meaning that indicators will move downwards
160− // by hintValue pixels without any drag taking place
161− tryCompareFunction(function() { return panel.indicators.height },
162− panel.indicators.panelHeight + panel.indicators.hintValue);
163− tryCompare(panel.indicators, "partiallyOpened", true);
164− tryCompare(panel.indicators, "fullyOpened", false);
165−
166− touchRelease(panel, indicatorItemCoord.x, panel.indicators.panelHeight/2);
167− }
168−
169− // Pressing on the top edge of the screen should have no effect if the panel
170− // is hidden (fullscreen), which is the case when a fullscreen app is being shown
171− function test_noHintOnFullscreenMode() {
172− panel.fullscreenMode = true;
173− // Wait for the indicators to get into position.
174− // (switches between normal and fullscreen modes are animated)
175− var indicatorArea = findChild(panel, "indicatorArea");
176− tryCompare(indicatorArea, "y", -panel.panelHeight);
177−
178− var indicatorItemCoord = get_indicator_item_position(0);
179−
180− touchPress(panel, indicatorItemCoord.x, panel.indicators.panelHeight / 2);
181−
182− // Give some time for a hint animation to change things, if any
183− wait(500);
184−
185− // no hint animation when fullscreen
186− compare(panel.indicators.partiallyOpened, false,
187− "Indicator should not be partially opened when panel is pressed in" +
188− " fullscreenmode");
189− compare(panel.indicators.fullyOpened, false, "Indicator should not be partially" +
190− " opened when panel is pressed in fullscreenmode");
191−
192− touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
193− }
194−
195− // Pressing on the top edge of the indicator should have no effect if the panel
196− // has an active call
197− function test_noHintOnActiveCall() {
198− callManager.foregroundCall = phoneCall;
199−
200− var indicatorItemCoord = get_indicator_item_position(0);
201−
202− touchPress(panel, indicatorItemCoord.x, panel.callHint.height + panel.indicators.panelHeight / 2);
203−
204− // Give some time for a hint animation to change things, if any
205− wait(500);
206−
207− // no hint animation when fullscreen
208− compare(panel.indicators.partiallyOpened, false,
209− "Indicator should not be partially opened when panel is pressed in" +
210− " fullscreenmode");
211− compare(panel.indicators.fullyOpened, false, "Indicator should not be partially" +
212− " opened when panel is pressed in fullscreenmode");
213−
214− touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
149+ return indicatorItem;
215150 }
216151
217152 function test_drag_show_data() {
218153 return [
219− { tag: "pinned", fullscreenFlag: false, alreadyOpen: false, call: null,
154+ { tag: "pinned", fullscreen: false, call: null,
220155 indicatorY: 0 },
221− { tag: "fullscreen", fullscreenFlag: true, alreadyOpen: false, call: null,
156+ { tag: "fullscreen", fullscreen: true, call: null,
222157 indicatorY: -panel.panelAndSeparatorHeight },
223− { tag: "pinned-alreadyOpen", fullscreenFlag: false, alreadyOpen: true, call: null,
224− indicatorY: 0 },
225− { tag: "fullscreen-alreadyOpen", fullscreenFlag: true, alreadyOpen: true, call: null,
226− indicatorY: 0 },
227− { tag: "pinned-callActive", fullscreenFlag: false, alreadyOpen: false, call: phoneCall,
158+ { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
228159 indicatorY: 0},
229− { tag: "fullscreen-callActive", fullscreenFlag: true, alreadyOpen: false, call: phoneCall,
160+ { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
230161 indicatorY: -panel.panelAndSeparatorHeight }
231162 ];
232163 }
@@ -234,16 +165,12 @@ Item {
234165 // Dragging from a indicator item in the panel will gradually expose the
235166 // indicators, first by running the hint animation, then after dragging down will
236167 // expose more of the panel, binding it to the selected indicator and opening it's menu.
168+ // Tested from first Y pixel to check for swipe from offscreen.
237169 function test_drag_show(data) {
238− panel.fullscreenMode = data.fullscreenFlag;
170+ panel.fullscreenMode = data.fullscreen;
239171 callManager.foregroundCall = data.call;
240172
241− if (data.alreadyOpen) {
242− panel.indicators.show();
243− tryCompare(panel.indicators, "fullyOpened", true);
244− }
245−
246− var indicatorRow = findChild(panel.indicators, "indicatorRow");
173+ var indicatorRow = findChild(panel.indicators, "indicatorItemRow");
247174 verify(indicatorRow !== null);
248175
249176 var menuContent = findChild(panel.indicators, "menuContent");
@@ -256,25 +183,16 @@ Item {
256183 // (switches between normal and fullscreen modes are animated)
257184 tryCompareFunction(function() { return indicatorArea.y }, data.indicatorY);
258185
259− // do this for each indicator item
260− for (var i = 0; i < indicatorRow.row.count; i++) {
261−
186+ for (var i = 0; i < root.originalModelData.length; i++) {
262187 var indicatorItem = get_indicator_item(i);
263− verify(indicatorItem !== null);
264188
265− if (!indicatorItem.visible)
266− continue;
189+ var startXPosition = root.mapFromItem(indicatorItem, indicatorItem.width / 2, 0).x;
190+ var startYPosition = root.mapFromItem(panel, 0, 0).y;
267191
268− var indicatorItemCoord = get_indicator_item_position(i);
269−
270− touchPress(panel,
271− indicatorItemCoord.x, panel.indicators.panelHeight / 2);
272−
273− // 1) Drag the mouse down
274192 touchFlick(panel,
275− indicatorItemCoord.x, panel.indicators.panelHeight / 2,
276− indicatorItemCoord.x, panel.height,
277− false /* beginTouch */, false /* endTouch */);
193+ startXPosition, startYPosition,
194+ startXPosition, panel.height,
195+ true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
278196
279197 // Indicators height should follow the drag, and therefore increase accordingly.
280198 // They should be at least half-way through the screen
@@ -282,56 +200,109 @@ Item {
282200 function() {return panel.indicators.height >= panel.height * 0.5},
283201 true);
284202
285− touchRelease(panel, indicatorItemCoord.x, panel.height);
203+ touchRelease(panel, startXPosition, panel.height);
286204
287− compare(indicatorRow.currentItem, indicatorItem,
288− "Incorrect item activated at position " + i);
289− compare(menuContent.currentMenuIndex, i, "Menu conetent should be enabled for item at position " + i);
205+ compare(indicatorRow.currentItemIndex, i, "Indicator item should be activated at position " + i);
206+ compare(menuContent.currentMenuIndex, i, "Menu conetent should be activated for item at position " + i);
290207
291208 // init for next indicatorItem
292− if (!data.alreadyOpen) {
293− panel.indicators.hide();
294− tryCompare(panel.indicators.hideAnimation, "running", false);
295− tryCompare(panel.indicators, "state", "initial");
296− }
209+ panel.indicators.hide();
210+ tryCompare(panel.indicators.hideAnimation, "running", false);
211+ tryCompare(panel.indicators, "state", "initial");
297212 }
298213 }
299214
300− // Test the vertical velocity check when flicking the indicators open at an angle.
301− // If the vertical velocity is above a specific point, we shouldnt change active indicators
302− // if the x position changes
303− function test_vertical_velocity_detector() {
304− panel.fullscreenMode = false;
215+ function test_drag_hide_data() {
216+ return [
217+ { tag: "pinned", fullscreen: false, call: null,
218+ indicatorY: 0 },
219+ { tag: "fullscreen", fullscreen: true, call: null,
220+ indicatorY: -panel.panelAndSeparatorHeight },
221+ { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
222+ indicatorY: 0},
223+ { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
224+ indicatorY: -panel.panelAndSeparatorHeight }
225+ ];
226+ }
227+
228+ // Dragging the shown indicators up from bottom of panel will hide the indicators
229+ // Tested from last Y pixel to check for swipe from offscreen.
230+ function test_drag_hide(data) {
231+ panel.fullscreenMode = data.fullscreen;
232+ callManager.foregroundCall = data.call;
305233
306− var indicatorRow = findChild(panel.indicators, "indicatorRow");
234+ var indicatorRow = findChild(panel.indicators, "indicatorItemRow");
307235 verify(indicatorRow !== null);
308236
309− // Get the first indicator
310− var indicatorItemFirst = get_indicator_item(0);
311− verify(indicatorItemFirst !== null);
237+ var menuContent = findChild(panel.indicators, "menuContent");
238+ verify(menuContent !== null);
312239
313− var indicatorItemCoordFirst = get_indicator_item_position(0);
314− var indicatorItemCoordNext = get_indicator_item_position(indicatorRow.row.count - 1);
240+ var indicatorArea = findChild(panel, "indicatorArea");
241+ verify(indicatorArea !== null);
315242
316− touchPress(panel,
317− indicatorItemCoordFirst.x, panel.indicators.panelHeight / 2);
243+ // Wait for the indicators to get into position.
244+ // (switches between normal and fullscreen modes are animated)
245+ tryCompareFunction(function() { return indicatorArea.y }, data.indicatorY);
246+
247+ var startXPosition = root.mapFromItem(panel.indicators, panel.indicators.width / 2, 0).x;
248+ var startYPosition = root.mapFromItem(panel, 0, panel.height).y;
249+
250+ panel.indicators.show();
251+ tryCompare(panel.indicators.showAnimation, "running", false);
252+ tryCompare(panel.indicators, "unitProgress", 1);
253+
254+
255+ touchFlick(panel.indicators,
256+ startXPosition, startYPosition,
257+ startXPosition, 0,
258+ true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
318259
319− // 1) Drag the mouse down to hint a bit
320− touchFlick(panel,
321− indicatorItemCoordFirst.x, panel.indicators.panelHeight / 2,
322− indicatorItemCoordFirst.x, panel.indicators.panelHeight * 2,
323− false /* beginTouch */, false /* endTouch */);
260+ // Indicators height should follow the drag, and therefore increase accordingly.
261+ // They should be at least half-way through the screen
262+ tryCompareFunction(
263+ function() {return panel.indicators.height <= panel.height * 0.5},
264+ true);
324265
325− tryCompare(indicatorRow, "currentItem", indicatorItemFirst)
266+ touchRelease(panel.indicators, startXPosition, 0);
326267
327− // 1) Flick mouse down to bottom
328− touchFlick(panel,
329− indicatorItemCoordFirst.x, panel.indicators.panelHeight * 2,
330− indicatorItemCoordNext.x, panel.height,
331− false /* beginTouch */, true /* endTouch */,
332− units.gu(10) /* speed */, 30 /* iterations */); // more samples needed for accurate velocity
268+ tryCompare(panel.indicators.hideAnimation, "running", true);
269+ tryCompare(panel.indicators.hideAnimation, "running", false);
270+ tryCompare(panel.indicators, "state", "initial");
271+ }
272+
273+ function test_hint_data() {
274+ return [
275+ { tag: "normal", fullscreen: false, call: null, hintExpected: true},
276+ { tag: "fullscreen", fullscreen: true, call: null, hintExpected: false},
277+ { tag: "call hint", fullscreen: false, call: phoneCall, hintExpected: false},
278+ ];
279+ }
280+
281+ function test_hint(data) {
282+ panel.fullscreenMode = data.fullscreen;
283+ callManager.foregroundCall = data.call;
284+
285+ if (data.fullscreen) {
286+ // Wait for the indicators to get into position.
287+ // (switches between normal and fullscreen modes are animated)
288+ var indicatorArea = findChild(panel, "indicatorArea");
289+ tryCompare(indicatorArea, "y", -panel.panelHeight);
290+ }
291+
292+ var indicatorItem = get_indicator_item(0);
293+ var mappedPosition = root.mapFromItem(indicatorItem, indicatorItem.width / 2, indicatorItem.height / 2);
294+
295+ touchPress(panel, mappedPosition.x, panel.indicators.minimizedPanelHeight / 2);
296+
297+ // Give some time for a hint animation to change things, if any
298+ wait(500);
299+
300+ // no hint animation when fullscreen
301+ compare(panel.indicators.fullyClosed, !data.hintExpected, "Indicator should be fully closed");
302+ compare(panel.indicators.partiallyOpened, data.hintExpected, "Indicator should be partialy opened");
303+ compare(panel.indicators.fullyOpened, false, "Indicator should not be fully opened");
333304
334− compare(indicatorRow.currentItem, indicatorItemFirst, "First indicator should still be the current item");
305+ touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
335306 }
336307 }
337308}
tests/qmltests/Panel/tst_SearchIndicator.qml−1
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.0
1818import QtTest 1.0
19−import ".."
2019import "../../../qml/Panel"
2120import Ubuntu.Components 0.1
2221import Unity.Test 0.1 as UT
tests/qmltests/Stages/tst_PhoneStage.qml+17
@@ -314,6 +314,23 @@ Item {
314314 tryCompare(app, "orientation", Qt.LandscapeOrientation);
315315 }
316316
317+ function test_backgroundClickCancelsSpread() {
318+ addApps(3);
319+
320+ var focusedAppId = ApplicationManager.focusedApplicationId;
321+
322+ goToSpread();
323+
324+ mouseClick(phoneStage, units.gu(1), units.gu(1));
325+
326+ // Make sure the spread is in the idle position
327+ var spreadView = findChild(phoneStage, "spreadView");
328+ tryCompare(spreadView, "contentX", -spreadView.shift);
329+
330+ // Make sure the same app is still focused
331+ compare(focusedAppId, ApplicationManager.focusedApplicationId);
332+ }
333+
317334 function cleanup() {
318335 while (ApplicationManager.count > 1) {
319336 var oldCount = ApplicationManager.count;
tests/qmltests/tst_Shell.qml+133−44
@@ -25,14 +25,16 @@ import Ubuntu.Components 1.1
2525import Ubuntu.Telephony 0.1 as Telephony
2626import Unity.Application 0.1
2727import Unity.Connectivity 0.1
28+import Unity.Notifications 1.0
2829import Unity.Test 0.1 as UT
2930import Powerd 0.1
3031
3132import "../../qml"
3233
33−Row {
34+Item {
3435 id: root
35− spacing: 0
36+ width: units.gu(60)
37+ height: units.gu(71)
3638
3739 QtObject {
3840 id: applicationArguments
@@ -50,47 +52,43 @@ Row {
5052 }
5153 }
5254
53− Loader {
54− id: shellLoader
55−
56− // Copied from Shell.qml
57− property bool tablet: false
58− width: tablet ? units.gu(160)
59− : applicationArguments.hasGeometry() ? applicationArguments.width()
60− : units.gu(40)
61− height: tablet ? units.gu(100)
62− : applicationArguments.hasGeometry() ? applicationArguments.height()
63− : units.gu(71)
64−
65− property bool itemDestroyed: false
66− sourceComponent: Component {
67− Shell {
68− Component.onDestruction: {
69− shellLoader.itemDestroyed = true;
55+ Row {
56+ anchors.fill: parent
57+ Loader {
58+ id: shellLoader
59+
60+ property bool itemDestroyed: false
61+ sourceComponent: Component {
62+ Shell {
63+ property string indicatorProfile: "phone"
64+
65+ Component.onDestruction: {
66+ shellLoader.itemDestroyed = true;
67+ }
7068 }
7169 }
7270 }
73− }
7471
75− Rectangle {
76− color: "white"
77− width: units.gu(30)
78− height: shellLoader.height
79−
80− Column {
81− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
82− spacing: units.gu(1)
83− Row {
84− anchors { left: parent.left; right: parent.right }
85− Button {
86− text: "Show Greeter"
87− onClicked: {
88− if (shellLoader.status !== Loader.Ready)
89− return;
90−
91− var greeter = testCase.findChild(shellLoader.item, "greeter");
92− if (!greeter.shown) {
93− greeter.show();
72+ Rectangle {
73+ color: "white"
74+ width: units.gu(30)
75+ height: shellLoader.height
76+
77+ Column {
78+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
79+ spacing: units.gu(1)
80+ Row {
81+ anchors { left: parent.left; right: parent.right }
82+ Button {
83+ text: "Show Greeter"
84+ onClicked: {
85+ if (shellLoader.status !== Loader.Ready)
86+ return;
87+
88+ var greeter = testCase.findChild(shellLoader.item, "greeter");
89+ if (!greeter.shown) {
90+ greeter.show();
91+ }
9492 }
9593 }
9694 }
@@ -98,6 +96,31 @@ Row {
9896 }
9997 }
10098
99+ Component {
100+ id: mockNotification
101+
102+ QtObject {
103+ function invokeAction(actionId) {
104+ mockNotificationsModel.actionInvoked(actionId)
105+ }
106+ }
107+ }
108+ ListModel {
109+ id: mockNotificationsModel
110+
111+ signal actionInvoked(string actionId)
112+
113+ function getRaw(id) {
114+ return mockNotification.createObject(mockNotificationsModel)
115+ }
116+
117+ onActionInvoked: {
118+ if(actionId == "ok_id") {
119+ mockNotificationsModel.clear()
120+ }
121+ }
122+ }
123+
101124 SignalSpy {
102125 id: launcherShowDashHomeSpy
103126 signalName: "showDashHome"
@@ -119,6 +142,12 @@ Row {
119142 signalName: "unlockingAllModems"
120143 }
121144
145+ SignalSpy {
146+ id: notificationActionSpy
147+ target: mockNotificationsModel
148+ signalName: "actionInvoked"
149+ }
150+
122151 Telephony.CallEntry {
123152 id: phoneCall
124153 phoneNumber: "+447812221111"
@@ -132,6 +161,8 @@ Row {
132161 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
133162
134163 function init() {
164+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
165+
135166 swipeAwayGreeter();
136167
137168 sessionSpy.target = findChild(shell, "greeter")
@@ -142,6 +173,7 @@ Row {
142173 }
143174
144175 function cleanup() {
176+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
145177 launcherShowDashHomeSpy.target = null;
146178
147179 shellLoader.itemDestroyed = false;
@@ -178,6 +210,65 @@ Row {
178210 compare(ApplicationManager.count, 1)
179211 }
180212
213+ function test_snapDecisionDismissalReturnsFocus() {
214+ var notifications = findChild(shell, "notificationList");
215+ var app = ApplicationManager.startApplication("camera-app");
216+ var stage = findChild(shell, "stage")
217+ // Open an application and focus
218+ waitUntilApplicationWindowIsFullyVisible(app);
219+ ApplicationManager.focusApplication(app);
220+ tryCompare(app.session.surface, "activeFocus", true);
221+
222+ notifications.model = mockNotificationsModel;
223+
224+ // FIXME: Hack: SortFilterProxyModelQML doesn't work with QML ListModels which we use
225+ // for mocking here (RoleType can't be found in the QML model). As we only need to show
226+ // one SnapDecision lets just disable the filtering and make appear any notification as a
227+ // SnapDecision.
228+ var snapDecisionProxyModel = findInvisibleChild(shell, "snapDecisionProxyModel");
229+ snapDecisionProxyModel.filterRegExp = RegExp("");
230+
231+ // Pop-up a notification
232+ addSnapDecisionNotification();
233+ waitForRendering(shell);
234+
235+ // Make sure the notification really opened
236+ var notification = findChild(notifications, "notification" + (mockNotificationsModel.count - 1));
237+ verify(notification !== undefined && notification != null, "notification wasn't found");
238+ tryCompare(notification, "height", notification.implicitHeight)
239+ waitForRendering(notification);
240+
241+ // Make sure activeFocus went away from the app window
242+ tryCompare(app.session.surface, "activeFocus", false);
243+ tryCompare(stage, "interactive", false);
244+
245+ // Clicking the button should dismiss the notification and return focus
246+ var buttonAccept = findChild(notification, "notify_button0");
247+ mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2);
248+
249+ // Make sure we're back to normal
250+ tryCompare(app.session.surface, "activeFocus", true);
251+ compare(stage.interactive, true, "Stages not interactive again after modal notification has closed");
252+ }
253+
254+ function addSnapDecisionNotification() {
255+ var n = {
256+ type: Notification.SnapDecision,
257+ hints: {"x-canonical-private-affirmative-tint": "true"},
258+ summary: "Tom Ato",
259+ body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
260+ icon: "../graphics/avatars/funky.png",
261+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
262+ actions: [{ id: "ok_id", label: "Ok"},
263+ { id: "cancel_id", label: "Cancel"},
264+ { id: "notreally_id", label: "Not really"},
265+ { id: "noway_id", label: "messages:No way"},
266+ { id: "nada_id", label: "messages:Nada"}]
267+ }
268+
269+ mockNotificationsModel.append(n)
270+ }
271+
181272 function test_leftEdgeDrag_data() {
182273 return [
183274 {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
@@ -294,7 +385,7 @@ Row {
294385 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
295386
296387 compare(dashCommunicatorSpy.count, 1);
297− compare(dashCommunicatorSpy.signalArguments[0][0], "clickscope");
388+ compare(dashCommunicatorSpy.signalArguments[0][0], 0);
298389 }
299390
300391 function test_showInputMethod() {
@@ -345,7 +436,7 @@ Row {
345436 var touchX = shell.width / 2;
346437 var indicators = findChild(shell, "indicators");
347438 touchFlick(indicators,
348− touchX /* fromX */, indicators.panelHeight * 0.5 /* fromY */,
439+ touchX /* fromX */, indicators.minimizedPanelHeight * 0.5 /* fromY */,
349440 touchX /* toX */, shell.height * 0.5 /* toY */,
350441 true /* beginTouch */, false /* endTouch */);
351442 verify(indicators.partiallyOpened);
@@ -382,7 +473,7 @@ Row {
382473 var indicators = findChild(shell, "indicators");
383474
384475 var touchStartX = shell.width / 2;
385− var touchStartY = shell.height - (indicators.panelHeight * 0.5);
476+ var touchStartY = shell.height - (indicators.minimizedPanelHeight * 0.5);
386477 touchFlick(shell,
387478 touchStartX, touchStartY,
388479 touchStartX, shell.height * 0.1);
@@ -558,8 +649,6 @@ Row {
558649 }
559650
560651 function test_unlockAllModemsOnBoot() {
561− // TODO reenable when service ready (LP: #1361074)
562− expectFail("", "Unlock on boot temporarily disabled");
563652 tryCompare(unlockAllModemsSpy, "count", 1)
564653 }
565654
tests/qmltests/tst_ShellWithPin.qml+5
@@ -61,6 +61,8 @@ Item {
6161 property bool itemDestroyed: false
6262 sourceComponent: Component {
6363 Shell {
64+ property string indicatorProfile: "phone"
65+
6466 Component.onDestruction: {
6567 shellLoader.itemDestroyed = true
6668 }
@@ -127,6 +129,7 @@ Item {
127129 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
128130
129131 function init() {
132+ tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
130133 sessionSpy.target = findChild(shell, "greeter")
131134 swipeAwayGreeter()
132135 waitForLockscreen()
@@ -135,6 +138,8 @@ Item {
135138 }
136139
137140 function cleanup() {
141+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
142+
138143 shellLoader.itemDestroyed = false
139144
140145 shellLoader.active = false
tests/qmltests/tst_TabletShell.qml+5
@@ -57,6 +57,8 @@ Row {
5757 property bool itemDestroyed: false
5858 sourceComponent: Component {
5959 Shell {
60+ property string indicatorProfile: "phone"
61+
6062 Component.onDestruction: {
6163 shellLoader.itemDestroyed = true
6264 }
@@ -119,12 +121,15 @@ Row {
119121 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
120122
121123 function init() {
124+ tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
122125 sessionSpy.clear()
123126 sessionSpy.target = findChild(shell, "greeter")
124127 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
125128 }
126129
127130 function cleanup() {
131+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
132+
128133 shellLoader.itemDestroyed = false
129134
130135 shellLoader.active = false
tests/uqmlscene/main.cpp+5−5
@@ -428,6 +428,10 @@ int main(int argc, char ** argv)
428428
429429 MouseTouchAdaptor mouseTouchAdaptor;
430430 app.installNativeEventFilter(&mouseTouchAdaptor);
431+ // Create it before loading the module, so that TestUtil
432+ // doesn't create one with a fake timer factory.
433+ // When interacting manually with a test we want the real deal.
434+ new TouchRegistry;
431435
432436#ifndef QT_NO_TRANSLATION
433437 QTranslator translator;
@@ -499,11 +503,7 @@ int main(int argc, char ** argv)
499503 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
500504 if (contentItem) {
501505 qxView = new QQuickView(&engine, nullptr);
502− if (TouchRegistry::instance() == nullptr) {
503− new TouchRegistry(qxView);
504− } else {
505− TouchRegistry::instance()->setParent(qxView);
506− }
506+ TouchRegistry::instance()->setParent(qxView);
507507 qxView->installEventFilter(TouchRegistry::instance());
508508 window = qxView;
509509 // Set window default properties; the qml can still override them
tests/utils/modules/Unity/Test/testutil.cpp+10−22
@@ -31,7 +31,6 @@ TestUtil::TestUtil(QObject *parent)
3131 : QObject(parent)
3232 , m_targetWindow(0)
3333 , m_touchDevice(0)
34− , m_installedTouchRegistry(false)
3534{
3635}
3736
@@ -83,30 +82,19 @@ void TestUtil::ensureTouchDevice()
8382
8483void TestUtil::ensureTouchRegistryInstalled()
8584{
86− if (m_installedTouchRegistry)
85+ if (TouchRegistry::instance())
8786 return;
8887
89− TouchRegistry *touchRegistry;
90− if (TouchRegistry::instance() == nullptr) {
91− // Tests can be *very* slow to run and we don't want things timing out because
92− // of that. So give it fake timers to use (they will never time out)
93− touchRegistry = new TouchRegistry(this, new FakeTimerFactory);
94− } else {
95− touchRegistry = TouchRegistry::instance();
96− if (touchRegistry->parent() != this) {
97− // someone else created it. leave it alone
98− m_installedTouchRegistry = true;
99− }
100− }
88+ if (!m_targetWindow)
89+ return;
10190
102− ensureTargetWindow();
91+ // Tests can be *very* slow to run and we don't want things timing out because
92+ // of that. So give it fake timers to use (they will never time out)
93+ TouchRegistry *touchRegistry = new TouchRegistry(this, new FakeTimerFactory);
10394
104− if (m_targetWindow) {
105− QQuickView *view = qobject_cast<QQuickView*>(m_targetWindow);
106− if (view) {
107− view->installEventFilter(touchRegistry);
108− touchRegistry->setParent(view);
109− m_installedTouchRegistry = true;
110− }
95+ QQuickView *view = qobject_cast<QQuickView*>(m_targetWindow);
96+ if (view) {
97+ view->installEventFilter(touchRegistry);
98+ touchRegistry->setParent(view);
11199 }
112100}
tests/utils/modules/Unity/Test/testutil.h−1
@@ -42,7 +42,6 @@ private:
4242
4343 QWindow *m_targetWindow;
4444 QTouchDevice *m_touchDevice;
45− bool m_installedTouchRegistry;
4645};
4746
4847QML_DECLARE_TYPE(TestUtil)