on xenial help sign in
merged with trunk
commit3800f689adbe6397d5c0ee70a108c90fa5d95f96 authorNick Dedekind <nick.dedekind@canonical.com> dateWed 14 Dec 2016, 15:38 (10 years ago) parent9b6323c497f799
85 files changed+2624−796
CMakeLists.txt+13 data/com.canonical.Unity8.gschema.xml+2−2 debian/changelog+33 debian/control+3−1 plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1 plugins/Greeter/Unity/Launcher/launcheritem.cpp+13 plugins/Greeter/Unity/Launcher/launcheritem.h+3 plugins/LightDM/FullLightDM/CMakeLists.txt+5−1 plugins/Unity/Indicators/CMakeLists.txt+3 plugins/Unity/Launcher/CMakeLists.txt+4−1 plugins/Unity/Launcher/appdrawermodel.cpp+62 plugins/Unity/Launcher/appdrawermodel.h+33 plugins/Unity/Launcher/launcheritem.cpp+13 plugins/Unity/Launcher/launcheritem.h+4 plugins/Unity/Launcher/launchermodel.cpp+6−37 plugins/Unity/Launcher/launchermodel.h−14 plugins/Unity/Launcher/plugin.cpp+2−1 plugins/Unity/Launcher/ualwrapper.cpp+73 plugins/Unity/Launcher/ualwrapper.h+35 plugins/Utils/CMakeLists.txt+4 plugins/Utils/ElapsedTimer.h+1−1 plugins/Utils/Timer.cpp+5−2 plugins/Utils/Timer.h+5−6 plugins/Utils/appdrawerproxymodel.cpp+189 plugins/Utils/appdrawerproxymodel.h+87 plugins/Utils/globalfunctions.cpp+5 plugins/Utils/globalfunctions.h+2 plugins/Utils/plugin.cpp+2 po/unity8.pot+80−43 qml/Components/KeyboardShortcutsOverlay.qml+13 qml/Components/VirtualTouchPad.qml+269−1 qml/Dash/Previews/PreviewAudioPlayback.qml+1 qml/DisabledScreenNotice.qml+3−55 qml/Greeter/Greeter.qml+1−5 qml/Greeter/LoginList.qml+1−1 qml/Greeter/WideView.qml+7−2 qml/Launcher/BackgroundBlur.qml+81 qml/Launcher/Drawer.qml+306 qml/Launcher/DrawerGridView.qml+47 qml/Launcher/DrawerListView.qml+32 qml/Launcher/Launcher.qml+182−34 qml/Launcher/MoreAppsHeader.qml+46 qml/Panel/Indicators/IndicatorMenuItemFactory.qml+22−1 qml/Shell.qml+11−35 qml/Stage/MoveHandler.qml+2−2 qml/Stage/Spread/WindowedRightEdgeMaths.qml+19−4 qml/Stage/Stage.qml+66−38 qml/Stage/StageMaths.qml+2−7 qml/Stage/WindowResizeArea.qml+2−70 qml/Stage/WindowStateSaver.qml+63 qml/Tutorial/TutorialLeftLong.qml+1−1 src/ShellApplication.cpp+1 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+1−1 tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+23−23 tests/mocks/QtMultimedia/videooutput.h+1−1 tests/mocks/Unity/Launcher/CMakeLists.txt+5−1 tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp+75 tests/mocks/Unity/Launcher/MockAppDrawerModel.h+32 tests/mocks/Unity/Launcher/MockLauncherItem.cpp+13 tests/mocks/Unity/Launcher/MockLauncherItem.h+6 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3 tests/mocks/Unity/Launcher/plugin.cpp+4 tests/mocks/Utils/CMakeLists.txt+4 tests/mocks/Utils/plugin.cpp+2 tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1 tests/plugins/Unity/Launcher/CMakeLists.txt+2−1 tests/plugins/Utils/WindowInputMonitorTest.cpp+76−9 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_VirtualTouchPad.qml+55−1 tests/qmltests/Dash/Previews/tst_Preview.qml+63 tests/qmltests/Dash/tst_Dash.qml+4−3 tests/qmltests/Dash/tst_DashShell.qml−29 tests/qmltests/Launcher/tst_Drawer.qml+264 tests/qmltests/Launcher/tst_Launcher.qml+4−4 tests/qmltests/Panel/Indicators/tst_IndicatorMenuItemFactory.qml+6−1 tests/qmltests/Stage/tst_ApplicationWindow.qml+2−20 tests/qmltests/Stage/tst_DesktopStage.qml+71 tests/qmltests/Stage/tst_PhoneStage.qml−26 tests/qmltests/Stage/tst_WindowResizeArea.qml+1−66 tests/qmltests/tst_DisabledScreenNotice.qml+23−9 tests/qmltests/tst_OrientedShell.qml+1−13 tests/qmltests/tst_Shell.qml+6−150 tests/qmltests/tst_ShellWithPin.qml−66 tests/uqmlscene/CMakeLists.txt+5−1 tests/utils/modules/Unity/Test/UnityTestCase.qml+4−4
CMakeLists.txt+13
@@ -40,6 +40,19 @@ if(cmake_build_type_lower MATCHES coverage)
4040 ENABLE_COVERAGE_REPORT(TARGETS ${SHELL_APP} FILTER /usr/include ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/*)
4141endif()
4242
43+find_package (ECM 1.7.0 QUIET NO_MODULE)
44+if (ECM_FOUND)
45+ # Provides us with -DECM_ENABLE_SANITIZERS='X'
46+ # Where X can be address, thread, memory, leak, undefined
47+ include("${ECM_MODULE_DIR}/ECMEnableSanitizers.cmake")
48+endif()
49+
50+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
51+ if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0.0")
52+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" )
53+ endif()
54+endif()
55+
4356# Make sure we have all the needed symbols
4457set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,defs")
4558set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,defs")
data/com.canonical.Unity8.gschema.xml+2−2
@@ -12,7 +12,7 @@
1212 <description>The usage mode chosen will affect the Window Management behaviour.</description>
1313 </key>
1414 <key type="y" name="edge-barrier-sensitivity">
15− <default>35</default>
15+ <default>50</default>
1616 <range min="1" max="100"/>
1717 <summary>Sensitivity of screen edge barriers for the mouse pointer.</summary>
1818 <description>Some UI actions like revealing the launcher or the applications spread are triggered by pushing the mouse pointer against a screen edge. This key defines how much you have to push in order to trigger the associated action.</description>
@@ -23,7 +23,7 @@
2323 <description>How much you have to push (in grid units)the mouse against an edge barrier when sensibility is 100.</description>
2424 </key>
2525 <key type="u" name="edge-barrier-max-push">
26− <default>60</default>
26+ <default>120</default>
2727 <summary>Maximum push needed to overcome edge barrier</summary>
2828 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
2929 </key>
debian/changelog+33
@@ -1,3 +1,36 @@
1+unity8 (8.15+17.04.20161207.1-0ubuntu1) zesty; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Add the Wsuggest-override flag to gcc
5+ * Add support for compiler sanitizers via ECM
6+ * Use timeStep as delay time (LP: #1642919)
7+ * Bring back fix for 1517830 (LP: #1517830)
8+ * Fix compile warnings in mocks
9+ * Do not hide panel when launching an application if the mouse is on
10+ the panel (LP: #1591311)
11+
12+ [ Daniel d'Andrada ]
13+ * Fix "make tryApplicationWindow"
14+ * Take save/restore functions out of WindowResizeArea
15+
16+ [ Josh Arenson ]
17+ * Enable the greeter to remember which session the user last logged
18+ into (LP: #1631365)
19+
20+ [ Lukáš Tinkl ]
21+ * Fix the Super key not invoking the dash scope home (LP: #1607427)
22+
23+ [ Michael Zanetti ]
24+ * Add the ApplicationDrawer
25+ * tune right edge push (LP: #1646094)
26+ * improve close button visiblity when hovering with the mouse
27+ * Update virtual touchpad visuals and add a tutorial. (LP: #1585220)
28+
29+ [ Pete Woods ]
30+ * MenuItemFactory: Add subtitle support to SwitchItem widget
31+
32+ -- Michał Sawicz <michal.sawicz@canonical.com> Wed, 07 Dec 2016 13:49:24 +0000
33+
134unity8 (8.15+17.04.20161129-0ubuntu1) zesty; urgency=medium
235
336 [ Albert Astals Cid ]
debian/control+3−1
@@ -39,7 +39,7 @@ Build-Depends: cmake,
3939 libubuntugestures5-private-dev (>= 1.3.2030),
4040 libudev-dev,
4141 libudm-common-dev,
42− libunity-api-dev (>= 7.120),
42+ libunity-api-dev (>= 7.121),
4343 libusermetricsoutput1-dev,
4444# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4545 libx11-dev[!arm64 !armhf],
@@ -50,6 +50,7 @@ Build-Depends: cmake,
5050 python3-all:any,
5151 python3-setuptools,
5252 qml-module-qt-labs-folderlistmodel,
53+ qml-module-qt-labs-settings,
5354 qml-module-qtqml-statemachine,
5455 qml-module-qtmultimedia (>= 5.4.1-1ubuntu19~overlay2),
5556 qml-module-qtquick-layouts,
@@ -124,6 +125,7 @@ Depends: dmz-cursor-theme,
124125 qmenumodel-qml (>= 0.2.11),
125126 qml-module-biometryd,
126127 qml-module-qt-labs-folderlistmodel,
128+ qml-module-qt-labs-settings,
127129 qml-module-qtqml-statemachine,
128130 qml-module-qtquick-xmllistmodel,
129131 qml-module-qtsysteminfo,
plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
22pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
33
44add_definitions(-DSM_BUSNAME=systemBus)
plugins/Greeter/Unity/Launcher/launcheritem.cpp+13
@@ -76,6 +76,19 @@ void LauncherItem::setIcon(const QString &icon)
7676 }
7777}
7878
79+QStringList LauncherItem::keywords() const
80+{
81+ return m_keywords;
82+}
83+
84+void LauncherItem::setKeywords(const QStringList &keywords)
85+{
86+ if (m_keywords != keywords) {
87+ m_keywords = keywords;
88+ Q_EMIT keywordsChanged(keywords);
89+ }
90+}
91+
7992bool LauncherItem::pinned() const
8093{
8194 return m_pinned;
plugins/Greeter/Unity/Launcher/launcheritem.h+3
@@ -34,6 +34,7 @@ public:
3434 QString appId() const override;
3535 QString name() const override;
3636 QString icon() const override;
37+ QStringList keywords() const override;
3738 bool pinned() const override;
3839 bool running() const override;
3940 bool recent() const override;
@@ -49,6 +50,7 @@ public:
4950private:
5051 void setName(const QString &name);
5152 void setIcon(const QString &icon);
53+ void setKeywords(const QStringList &keywords);
5254 void setPinned(bool pinned);
5355 void setRunning(bool running);
5456 void setRecent(bool recent);
@@ -63,6 +65,7 @@ private:
6365 QString m_appId;
6466 QString m_name;
6567 QString m_icon;
68+ QStringList m_keywords;
6669 bool m_pinned;
6770 bool m_running;
6871 bool m_recent;
plugins/LightDM/FullLightDM/CMakeLists.txt+5−1
@@ -6,10 +6,14 @@ add_definitions(-DFULL_LIGHTDM)
66
77include_directories(
88 ../
9− ${LIBLIGHTDM_INCLUDE_DIRS}
109 ${CMAKE_CURRENT_BINARY_DIR}
1110 )
1211
12+include_directories(
13+ SYSTEM
14+ ${LIBLIGHTDM_INCLUDE_DIRS}
15+ )
16+
1317foreach(source_file ${QMLPLUGIN_SRC})
1418 list(APPEND RELATIVE_QMLPLUGIN_SRC ../${source_file})
1519endforeach()
plugins/Unity/Indicators/CMakeLists.txt+3
@@ -11,6 +11,9 @@ include_directories(
1111include_directories(
1212 SYSTEM
1313 ${GLIB_INCLUDE_DIRS}
14+)
15+include_directories(
16+ SYSTEM
1417 ${GIO_INCLUDE_DIRS}
1518 ${QMENUMODEL_INCLUDE_DIRS}
1619)
plugins/Unity/Launcher/CMakeLists.txt+4−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
22pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
33
44add_definitions(-DSM_BUSNAME=systemBus)
@@ -25,6 +25,8 @@ set(QMLLAUNCHERPLUGIN_SRC
2525 dbusinterface.cpp
2626 gsettings.cpp
2727 asadapter.cpp
28+ appdrawermodel.cpp
29+ ualwrapper.cpp
2830 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
2931 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
3032 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
@@ -32,6 +34,7 @@ set(QMLLAUNCHERPLUGIN_SRC
3234 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
3335 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
3436 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
37+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
3538 )
3639
3740add_library(UnityLauncher-qml MODULE
plugins/Unity/Launcher/appdrawermodel.cppnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2016 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 "appdrawermodel.h"
18+#include "ualwrapper.h"
19+
20+#include <QDebug>
21+#include <QDateTime>
22+
23+AppDrawerModel::AppDrawerModel(QObject *parent):
24+ AppDrawerModelInterface(parent)
25+{
26+ Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
27+ UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
28+ if (!info.valid) {
29+ qWarning() << "Failed to get app info for app" << appId;
30+ continue;
31+ }
32+ m_list.append(new LauncherItem(appId, info.name, info.icon, this));
33+ m_list.last()->setKeywords(info.keywords);
34+ }
35+ qsrand(QDateTime::currentMSecsSinceEpoch() / 100);
36+}
37+
38+int AppDrawerModel::rowCount(const QModelIndex &parent) const
39+{
40+ Q_UNUSED(parent)
41+ return m_list.count();
42+}
43+
44+QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
45+{
46+ switch (role) {
47+ case RoleAppId:
48+ return m_list.at(index.row())->appId();
49+ case RoleName:
50+ return m_list.at(index.row())->name();
51+ case RoleIcon:
52+ return m_list.at(index.row())->icon();
53+ case RoleKeywords:
54+ return m_list.at(index.row())->keywords();
55+ case RoleUsage:
56+ // FIXME: u-a-l needs to provide API for usage stats.
57+ // don't forget to drop the qsrand() call in the ctor when dropping this.
58+ return qrand();
59+ }
60+
61+ return QVariant();
62+}
plugins/Unity/Launcher/appdrawermodel.hnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2016 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+
18+#include <unity/shell/launcher/AppDrawerModelInterface.h>
19+
20+#include "launcheritem.h"
21+
22+class AppDrawerModel: public AppDrawerModelInterface
23+{
24+ Q_OBJECT
25+public:
26+ AppDrawerModel(QObject* parent = nullptr);
27+
28+ int rowCount(const QModelIndex &parent) const override;
29+ QVariant data(const QModelIndex &index, int role) const override;
30+
31+private:
32+ QList<LauncherItem*> m_list;
33+};
plugins/Unity/Launcher/launcheritem.cpp+13
@@ -90,6 +90,19 @@ void LauncherItem::setIcon(const QString &icon)
9090 }
9191}
9292
93+QStringList LauncherItem::keywords() const
94+{
95+ return m_keywords;
96+}
97+
98+void LauncherItem::setKeywords(const QStringList &keywords)
99+{
100+ if (m_keywords != keywords) {
101+ m_keywords = keywords;
102+ Q_EMIT keywordsChanged(keywords);
103+ }
104+}
105+
93106bool LauncherItem::pinned() const
94107{
95108 return m_pinned;
plugins/Unity/Launcher/launcheritem.h+4
@@ -37,6 +37,7 @@ public:
3737 QString appId() const override;
3838 QString name() const override;
3939 QString icon() const override;
40+ QStringList keywords() const override;
4041 bool pinned() const override;
4142 bool running() const override;
4243 bool recent() const override;
@@ -52,6 +53,7 @@ public:
5253private:
5354 void setName(const QString &name);
5455 void setIcon(const QString &icon);
56+ void setKeywords(const QStringList &keywords);
5557 void setPinned(bool pinned);
5658 void setRunning(bool running);
5759 void setRecent(bool recent);
@@ -66,6 +68,7 @@ private:
6668 QString m_appId;
6769 QString m_name;
6870 QString m_icon;
71+ QStringList m_keywords;
6972 bool m_pinned;
7073 bool m_running;
7174 bool m_recent;
@@ -79,6 +82,7 @@ private:
7982 QuickListEntry m_quitAction;
8083
8184 friend class LauncherModel;
85+ friend class AppDrawerModel;
8286};
8387
8488#endif // LAUNCHERITEM_H
plugins/Unity/Launcher/launchermodel.cpp+6−37
@@ -19,18 +19,14 @@
1919#include "gsettings.h"
2020#include "dbusinterface.h"
2121#include "asadapter.h"
22+#include "ualwrapper.h"
2223
23−#include <ubuntu-app-launch/appid.h>
24−#include <ubuntu-app-launch/application.h>
25−#include <ubuntu-app-launch/registry.h>
2624#include <unity/shell/application/ApplicationInfoInterface.h>
2725#include <unity/shell/application/MirSurfaceListInterface.h>
2826
2927#include <QDesktopServices>
3028#include <QDebug>
3129
32−namespace ual = ubuntu::app_launch;
33−
3430using namespace unity::shell::application;
3531
3632LauncherModel::LauncherModel(QObject *parent):
@@ -38,8 +34,7 @@ LauncherModel::LauncherModel(QObject *parent):
3834 m_settings(new GSettings(this)),
3935 m_dbusIface(new DBusInterface(this)),
4036 m_asAdapter(new ASAdapter()),
41− m_appManager(nullptr),
42− m_ualRegistry(std::make_shared<ual::Registry>())
37+ m_appManager(nullptr)
4338{
4439 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
4540 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
@@ -159,7 +154,7 @@ void LauncherModel::pin(const QString &appId, int index)
159154 index = m_list.count();
160155 }
161156
162− auto appInfo = getApplicationInfo(appId);
157+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(appId);
163158 if (!appInfo.valid) {
164159 qWarning() << "Can't pin application, appId not found:" << appId;
165160 return;
@@ -335,32 +330,6 @@ int LauncherModel::findApplication(const QString &appId)
335330 return -1;
336331}
337332
338−LauncherModel::AppInfo LauncherModel::getApplicationInfo(const QString &appId)
339−{
340− AppInfo info;
341−
342− ual::AppID ualAppId = ual::AppID::find(m_ualRegistry, appId.toStdString());
343− if (ualAppId.empty()) {
344− return info;
345− }
346−
347− std::shared_ptr<ual::Application> ualApp;
348− try
349− {
350− ualApp = ual::Application::create(ualAppId, m_ualRegistry);
351− }
352− catch (std::runtime_error &e)
353− {
354− qWarning() << "Couldn't find application info for" << appId << "-" << e.what();
355− return info;
356− }
357−
358− info.valid = true;
359− info.name = QString::fromStdString(ualApp->info()->name());
360− info.icon = QString::fromStdString(ualApp->info()->iconPath());
361− return info;
362−}
363−
364333void LauncherModel::progressChanged(const QString &appId, int progress)
365334{
366335 const int idx = findApplication(appId);
@@ -408,7 +377,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
408377 }
409378 } else {
410379 // Need to create a new LauncherItem and show the highlight
411− auto appInfo = getApplicationInfo(appId);
380+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(appId);
412381 if (countVisible && appInfo.valid) {
413382 LauncherItem *item = new LauncherItem(appId,
414383 appInfo.name,
@@ -428,7 +397,7 @@ void LauncherModel::refresh()
428397 // First walk through all the existing items and see if we need to remove something
429398 QList<LauncherItem*> toBeRemoved;
430399 Q_FOREACH (LauncherItem* item, m_list) {
431− auto appInfo = getApplicationInfo(item->appId());
400+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(item->appId());
432401 if (!appInfo.valid) {
433402 // Application no longer available => drop it!
434403 toBeRemoved << item;
@@ -480,7 +449,7 @@ void LauncherModel::refresh()
480449 if (itemIndex == -1) {
481450 // Need to add it. Just add it into the addedIndex to keep same ordering as the list
482451 // in the settings.
483− auto appInfo = getApplicationInfo(entry);
452+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(entry);
484453 if (!appInfo.valid) {
485454 continue;
486455 }
plugins/Unity/Launcher/launchermodel.h−14
@@ -27,12 +27,6 @@ class GSettings;
2727class DBusInterface;
2828class ASAdapter;
2929
30−namespace ubuntu {
31− namespace app_launch {
32− class Registry;
33− }
34−}
35−
3630using namespace unity::shell::launcher;
3731using namespace unity::shell::application;
3832
@@ -73,13 +67,6 @@ private:
7367
7468 void unpin(const QString &appId);
7569
76− struct AppInfo {
77− bool valid = false;
78− QString name;
79− QString icon;
80− };
81− AppInfo getApplicationInfo(const QString &appId);
82−
8370private Q_SLOTS:
8471 void countChanged(const QString &appId, int count);
8572 void countVisibleChanged(const QString &appId, bool count);
@@ -98,7 +85,6 @@ private:
9885 ASAdapter *m_asAdapter;
9986
10087 ApplicationManagerInterface *m_appManager;
101− std::shared_ptr<ubuntu::app_launch::Registry> m_ualRegistry;
10288
10389 friend class LauncherModelTest;
10490};
plugins/Unity/Launcher/plugin.cpp+2−1
@@ -26,7 +26,7 @@
2626// local
2727#include "launchermodel.h"
2828#include "launcheritem.h"
29−
29+#include "appdrawermodel.h"
3030
3131using namespace unity::shell::launcher;
3232
@@ -46,4 +46,5 @@ void UnityLauncherPlugin::registerTypes(const char *uri)
4646 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
4747 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", QStringLiteral("Can't create new Launcher Items in QML. Get them from the LauncherModel."));
4848 qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", QStringLiteral("Can't create a QuickListModel in QML. Get them from the LauncherItems."));
49+ qmlRegisterType<AppDrawerModel>(uri, 0, 1, "AppDrawerModel");
4950}
plugins/Unity/Launcher/ualwrapper.cppnew file+73
@@ -0,0 +1,73 @@
1+/*
2+ * Copyright (C) 2016 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 <QDebug>
18+
19+#include "ualwrapper.h"
20+
21+#include <ubuntu-app-launch/registry.h>
22+using namespace ubuntu::app_launch;
23+
24+UalWrapper::UalWrapper(QObject *parent):
25+ QObject(parent)
26+{
27+
28+}
29+
30+QStringList UalWrapper::installedApps()
31+{
32+ QStringList appIds;
33+ try {
34+ for (const std::shared_ptr<Application> &app : Registry::installedApps()) {
35+ if (!app->appId().package.value().empty()) {
36+ appIds << QString::fromStdString(app->appId().package.value() + "_" + app->appId().appname.value());
37+ } else {
38+ appIds << QString::fromStdString(app->appId().appname);
39+ }
40+ }
41+ } catch (const std::runtime_error &e) {
42+ qWarning() << "ubuntu-all-launch threw an exception listing apps:" << e.what();
43+ }
44+
45+ return appIds;
46+}
47+
48+UalWrapper::AppInfo UalWrapper::getApplicationInfo(const QString &appId)
49+{
50+ AppInfo info;
51+
52+ try {
53+ AppID ualAppId = AppID::find(appId.toStdString());
54+ if (ualAppId.empty()) {
55+ qWarning() << "Empty ualAppId result for" << appId;
56+ return info;
57+ }
58+
59+ std::shared_ptr<Application> ualApp;
60+ ualApp = Application::create(ualAppId, Registry::getDefault());
61+
62+ info.name = QString::fromStdString(ualApp->info()->name());
63+ info.icon = QString::fromStdString(ualApp->info()->iconPath());
64+ for (const std::string &keyword : ualApp->info()->keywords().value()) {
65+ info.keywords << QString::fromStdString(keyword);
66+ }
67+ info.valid = true;
68+ } catch (const std::runtime_error &e) {
69+ qWarning() << "ubuntu-app-launch threw an exception getting app info for appId:" << appId << ":" << e.what();
70+ }
71+
72+ return info;
73+}
plugins/Unity/Launcher/ualwrapper.hnew file+35
@@ -0,0 +1,35 @@
1+/*
2+ * Copyright (C) 2016 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 <QObject>
18+
19+class UalWrapper: public QObject
20+{
21+ Q_OBJECT
22+public:
23+ struct AppInfo {
24+ bool valid = false;
25+ QString name;
26+ QString icon;
27+ QStringList keywords;
28+ };
29+
30+ UalWrapper(QObject* parent = nullptr);
31+
32+ static QStringList installedApps();
33+ static AppInfo getApplicationInfo(const QString &appId);
34+
35+};
plugins/Utils/CMakeLists.txt+4
@@ -15,6 +15,10 @@ set(QMLPLUGIN_SRC
1515 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
1616 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
1717 applicationsfiltermodel.cpp
18+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
19+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
20+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
21+ appdrawerproxymodel.cpp
1822 constants.cpp
1923 WindowInputMonitor.cpp
2024 inputwatcher.cpp
plugins/Utils/ElapsedTimer.h+1−1
@@ -41,7 +41,7 @@ class ElapsedTimer : public AbstractElapsedTimer {
4141public:
4242 void start() override { m_timer.start(); }
4343 qint64 msecsSinceReference() const override { return m_timer.msecsSinceReference(); }
44− qint64 elapsed() const override { return m_timer.elapsed(); }
44+ qint64 elapsed() const override { return m_timer.isValid() ? m_timer.elapsed() : 0; }
4545private:
4646 QElapsedTimer m_timer;
4747};
plugins/Utils/Timer.cpp+5−2
@@ -37,13 +37,16 @@ void Timer::setInterval(int msecs)
3737void Timer::start()
3838{
3939 m_timer.start();
40− AbstractTimer::start();
4140}
4241
4342void Timer::stop()
4443{
4544 m_timer.stop();
46− AbstractTimer::stop();
45+}
46+
47+bool Timer::isRunning() const
48+{
49+ return m_timer.isActive();
4750}
4851
4952bool Timer::isSingleShot() const
plugins/Utils/Timer.h+5−6
@@ -30,18 +30,16 @@ class AbstractTimer : public QObject
3030{
3131 Q_OBJECT
3232public:
33− AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
33+ AbstractTimer(QObject *parent) : QObject(parent) {}
3434 virtual int interval() const = 0;
3535 virtual void setInterval(int msecs) = 0;
36− virtual void start() { m_isRunning = true; }
37− virtual void stop() { m_isRunning = false; }
38− bool isRunning() const { return m_isRunning; }
36+ virtual void start() = 0;
37+ virtual void stop() = 0;
38+ virtual bool isRunning() const = 0;
3939 virtual bool isSingleShot() const = 0;
4040 virtual void setSingleShot(bool value) = 0;
4141Q_SIGNALS:
4242 void timeout();
43−private:
44− bool m_isRunning;
4543};
4644
4745/** A QTimer wrapper */
@@ -55,6 +53,7 @@ public:
5553 void setInterval(int msecs) override;
5654 void start() override;
5755 void stop() override;
56+ bool isRunning() const override;
5857 bool isSingleShot() const override;
5958 void setSingleShot(bool value) override;
6059private:
plugins/Utils/appdrawerproxymodel.cppnew file+189
@@ -0,0 +1,189 @@
1+/*
2+ * Copyright (C) 2016 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 "appdrawerproxymodel.h"
18+
19+#include <unity/shell/launcher/LauncherItemInterface.h>
20+
21+#include <QDebug>
22+
23+AppDrawerProxyModel::AppDrawerProxyModel(QObject *parent):
24+ QSortFilterProxyModel(parent)
25+{
26+ setSortRole(AppDrawerModelInterface::RoleName);
27+ setSortLocaleAware(true);
28+ sort(0);
29+
30+ connect(this, &QAbstractListModel::rowsInserted, this, &AppDrawerProxyModel::countChanged);
31+ connect(this, &QAbstractListModel::rowsRemoved, this, &AppDrawerProxyModel::countChanged);
32+ connect(this, &QAbstractListModel::layoutChanged, this, &AppDrawerProxyModel::countChanged);
33+}
34+
35+QAbstractItemModel *AppDrawerProxyModel::source() const
36+{
37+ return m_source;
38+}
39+
40+void AppDrawerProxyModel::setSource(QAbstractItemModel *source)
41+{
42+ if (m_source != source) {
43+ m_source = source;
44+ setSourceModel(m_source);
45+ setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
46+ connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidateFilter);
47+ connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidateFilter);
48+ Q_EMIT sourceChanged();
49+ }
50+}
51+
52+AppDrawerProxyModel::GroupBy AppDrawerProxyModel::group() const
53+{
54+ return m_group;
55+}
56+
57+void AppDrawerProxyModel::setGroup(AppDrawerProxyModel::GroupBy group)
58+{
59+ if (m_group != group) {
60+ m_group = group;
61+ Q_EMIT groupChanged();
62+ invalidateFilter();
63+ }
64+}
65+
66+QString AppDrawerProxyModel::filterLetter() const
67+{
68+ return m_filterLetter;
69+}
70+
71+void AppDrawerProxyModel::setFilterLetter(const QString &filterLetter)
72+{
73+ if (m_filterLetter != filterLetter) {
74+ m_filterLetter = filterLetter;
75+ Q_EMIT filterLetterChanged();
76+ invalidateFilter();
77+ }
78+}
79+
80+QString AppDrawerProxyModel::filterString() const
81+{
82+ return m_filterString;
83+}
84+
85+void AppDrawerProxyModel::setFilterString(const QString &filterString)
86+{
87+ if (m_filterString != filterString) {
88+ m_filterString = filterString;
89+ Q_EMIT filterStringChanged();
90+ invalidateFilter();
91+ }
92+}
93+
94+AppDrawerProxyModel::SortBy AppDrawerProxyModel::sortBy() const
95+{
96+ return m_sortBy;
97+}
98+
99+void AppDrawerProxyModel::setSortBy(AppDrawerProxyModel::SortBy sortBy)
100+{
101+ if (m_sortBy != sortBy) {
102+ m_sortBy = sortBy;
103+ Q_EMIT sortByChanged();
104+ setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
105+ sort(0);
106+ }
107+}
108+
109+int AppDrawerProxyModel::count() const
110+{
111+ return rowCount();
112+}
113+
114+QVariant AppDrawerProxyModel::data(const QModelIndex &index, int role) const
115+{
116+ QModelIndex idx = mapToSource(index);
117+ if (role == Qt::UserRole) {
118+ QString name = m_source->data(idx, AppDrawerModelInterface::RoleName).toString();
119+ return name.length() > 0 ? QString(name.at(0)).toUpper() : QChar();
120+ }
121+ return m_source->data(idx, role);
122+}
123+
124+QHash<int, QByteArray> AppDrawerProxyModel::roleNames() const
125+{
126+ if (m_source) {
127+ QHash<int, QByteArray> roles = m_source->roleNames();
128+ roles.insert(Qt::UserRole, "letter");
129+ return roles;
130+ }
131+ return QHash<int, QByteArray>();
132+}
133+
134+bool AppDrawerProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
135+{
136+ Q_UNUSED(source_parent)
137+
138+ if (m_group == GroupByAToZ && source_row > 0) {
139+ QString currentName = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString();
140+ QChar currentLetter = currentName.length() > 0 ? currentName.at(0) : QChar();
141+ QString previousName = m_source->data(m_source->index(source_row - 1,0 ), AppDrawerModelInterface::RoleName).toString();
142+ QChar previousLetter = previousName.length() > 0 ? previousName.at(0) : QChar();
143+ if (currentLetter.toLower() == previousLetter.toLower()) {
144+ return false;
145+ }
146+ } else if(m_group == GroupByAll && source_row > 0) {
147+ return false;
148+ }
149+ if (!m_filterLetter.isEmpty()) {
150+ QString currentName = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString();
151+ QString currentLetter = currentName.length() > 0 ? QString(currentName.at(0)) : QString();
152+ if (currentLetter.toLower() != m_filterLetter.toLower()) {
153+ return false;
154+ }
155+ }
156+ if (!m_filterString.isEmpty()) {
157+ QStringList allWords = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleKeywords).toStringList();
158+ allWords.prepend(m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString());
159+ bool found = false;
160+ Q_FOREACH (const QString currentWord, allWords) {
161+ if (currentWord.toLower().startsWith(m_filterString.toLower())) {
162+ found = true;
163+ break;
164+ }
165+ }
166+ if (!found) {
167+ return false;
168+ }
169+ }
170+ return true;
171+}
172+
173+QString AppDrawerProxyModel::appId(int index) const
174+{
175+ if (index >= 0 && index < rowCount()) {
176+ QModelIndex sourceIndex = mapToSource(this->index(index, 0));
177+
178+ AppDrawerModelInterface* adm = dynamic_cast<AppDrawerModelInterface*>(m_source);
179+ if (adm) {
180+ return adm->data(sourceIndex, AppDrawerModelInterface::RoleAppId).toString();
181+ }
182+
183+ AppDrawerProxyModel* adpm = qobject_cast<AppDrawerProxyModel*>(m_source);
184+ if (adpm) {
185+ return adpm->appId(sourceIndex.row());
186+ }
187+ }
188+ return nullptr;
189+}
plugins/Utils/appdrawerproxymodel.hnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright (C) 2016 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 <QSortFilterProxyModel>
18+
19+#include <unity/shell/launcher/AppDrawerModelInterface.h>
20+
21+using namespace unity::shell::launcher;
22+
23+class AppDrawerProxyModel: public QSortFilterProxyModel
24+{
25+ Q_OBJECT
26+ Q_ENUMS(GroupBy)
27+ Q_ENUMS(SortBy)
28+ Q_PROPERTY(QAbstractItemModel* source READ source WRITE setSource NOTIFY sourceChanged)
29+ Q_PROPERTY(GroupBy group READ group WRITE setGroup NOTIFY groupChanged)
30+ Q_PROPERTY(QString filterLetter READ filterLetter WRITE setFilterLetter NOTIFY filterLetterChanged)
31+ Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)
32+ Q_PROPERTY(SortBy sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged)
33+ Q_PROPERTY(int count READ count NOTIFY countChanged)
34+
35+public:
36+ enum GroupBy {
37+ GroupByNone,
38+ GroupByAll,
39+ GroupByAToZ
40+ };
41+ enum SortBy {
42+ SortByAToZ,
43+ SortByUsage
44+ };
45+
46+ AppDrawerProxyModel(QObject* parent = nullptr);
47+
48+ QAbstractItemModel* source() const;
49+ void setSource(QAbstractItemModel* source);
50+
51+ GroupBy group() const;
52+ void setGroup(GroupBy group);
53+
54+ QString filterLetter() const;
55+ void setFilterLetter(const QString &filterLetter);
56+
57+ QString filterString() const;
58+ void setFilterString(const QString &filterString);
59+
60+ SortBy sortBy() const;
61+ void setSortBy(SortBy sortBy);
62+
63+ int count() const;
64+
65+ QVariant data(const QModelIndex &index, int role) const override;
66+ QHash<int, QByteArray> roleNames() const override;
67+
68+ Q_INVOKABLE QString appId(int index) const;
69+
70+protected:
71+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
72+
73+Q_SIGNALS:
74+ void sourceChanged();
75+ void groupChanged();
76+ void filterLetterChanged();
77+ void filterStringChanged();
78+ void sortByChanged();
79+ void countChanged();
80+
81+private:
82+ QAbstractItemModel* m_source = nullptr;
83+ GroupBy m_group = GroupByNone;
84+ QString m_filterLetter;
85+ QString m_filterString;
86+ SortBy m_sortBy = SortByAToZ;
87+};
plugins/Utils/globalfunctions.cpp+5
@@ -51,3 +51,8 @@ QQuickItem *GlobalFunctions::itemAt(QQuickItem* parent, int x, int y, QJSValue m
5151 }
5252 return nullptr;
5353}
54+
55+bool GlobalFunctions::itemUnderMouse(QQuickItem* item)
56+{
57+ return item && item->isUnderMouse();
58+}
plugins/Utils/globalfunctions.h+2
@@ -37,6 +37,8 @@ public:
3737 int x,
3838 int y,
3939 QJSValue matcher);
40+
41+ static Q_INVOKABLE bool itemUnderMouse(QQuickItem* item);
4042};
4143
4244#endif // GLOBALFUNCTIONS_H
plugins/Utils/plugin.cpp+2
@@ -39,6 +39,7 @@
3939#include "deviceconfigparser.h"
4040#include "globalfunctions.h"
4141#include "URLDispatcher.h"
42+#include "appdrawerproxymodel.h"
4243
4344static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4445{
@@ -82,4 +83,5 @@ void UtilsPlugin::registerTypes(const char *uri)
8283 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8384 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
8485 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86+ qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
8587}
po/unity8.pot+80−43
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-11-29 09:43+0000\n"
11+"POT-Creation-Date: 2016-12-07 13:49+0000\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -23,7 +23,7 @@ msgid "Password: "
2323msgstr ""
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:50
26−#: plugins/Unity/Launcher/launcheritem.cpp:109
26+#: plugins/Unity/Launcher/launcheritem.cpp:122
2727msgid "Pin shortcut"
2828msgstr ""
2929
@@ -31,7 +31,7 @@ msgstr ""
3131msgid "Quit"
3232msgstr ""
3333
34−#: plugins/Unity/Launcher/launcheritem.cpp:109
34+#: plugins/Unity/Launcher/launcheritem.cpp:122
3535msgid "Unpin shortcut"
3636msgstr ""
3737
@@ -175,87 +175,95 @@ msgstr ""
175175msgid "Same as clicking on a launcher icon."
176176msgstr ""
177177
178−#: qml/Components/KeyboardShortcutsOverlay.qml:181
178+#: qml/Components/KeyboardShortcutsOverlay.qml:177
179+msgid "Super + A"
180+msgstr ""
181+
182+#: qml/Components/KeyboardShortcutsOverlay.qml:182
183+msgid "Opens the Application Drawer."
184+msgstr ""
185+
186+#: qml/Components/KeyboardShortcutsOverlay.qml:194
179187msgid "Scopes"
180188msgstr ""
181189
182−#: qml/Components/KeyboardShortcutsOverlay.qml:188
190+#: qml/Components/KeyboardShortcutsOverlay.qml:201
183191msgid "Super (Tap)"
184192msgstr ""
185193
186−#: qml/Components/KeyboardShortcutsOverlay.qml:193
194+#: qml/Components/KeyboardShortcutsOverlay.qml:206
187195msgid "Opens the Scopes home."
188196msgstr ""
189197
190−#: qml/Components/KeyboardShortcutsOverlay.qml:209
198+#: qml/Components/KeyboardShortcutsOverlay.qml:222
191199msgid "Switching"
192200msgstr ""
193201
194−#: qml/Components/KeyboardShortcutsOverlay.qml:216
202+#: qml/Components/KeyboardShortcutsOverlay.qml:229
195203msgid "Alt + Tab"
196204msgstr ""
197205
198−#: qml/Components/KeyboardShortcutsOverlay.qml:221
206+#: qml/Components/KeyboardShortcutsOverlay.qml:234
199207msgid "Switches between applications."
200208msgstr ""
201209
202−#: qml/Components/KeyboardShortcutsOverlay.qml:229
210+#: qml/Components/KeyboardShortcutsOverlay.qml:242
203211msgid "Super + W"
204212msgstr ""
205213
206−#: qml/Components/KeyboardShortcutsOverlay.qml:234
214+#: qml/Components/KeyboardShortcutsOverlay.qml:247
207215msgid "Opens the desktop spread."
208216msgstr ""
209217
210−#: qml/Components/KeyboardShortcutsOverlay.qml:242
218+#: qml/Components/KeyboardShortcutsOverlay.qml:255
211219msgid "Cursor Left or Right"
212220msgstr ""
213221
214−#: qml/Components/KeyboardShortcutsOverlay.qml:247
222+#: qml/Components/KeyboardShortcutsOverlay.qml:260
215223msgid "Moves the focus."
216224msgstr ""
217225
218−#: qml/Components/KeyboardShortcutsOverlay.qml:259
226+#: qml/Components/KeyboardShortcutsOverlay.qml:272
219227msgid "Windows"
220228msgstr ""
221229
222−#: qml/Components/KeyboardShortcutsOverlay.qml:266
230+#: qml/Components/KeyboardShortcutsOverlay.qml:279
223231msgid "Ctrl + Super + D"
224232msgstr ""
225233
226−#: qml/Components/KeyboardShortcutsOverlay.qml:271
234+#: qml/Components/KeyboardShortcutsOverlay.qml:284
227235msgid "Minimizes all windows."
228236msgstr ""
229237
230−#: qml/Components/KeyboardShortcutsOverlay.qml:279
238+#: qml/Components/KeyboardShortcutsOverlay.qml:292
231239msgid "Ctrl + Super + Up"
232240msgstr ""
233241
234−#: qml/Components/KeyboardShortcutsOverlay.qml:284
242+#: qml/Components/KeyboardShortcutsOverlay.qml:297
235243msgid "Maximizes the current window."
236244msgstr ""
237245
238−#: qml/Components/KeyboardShortcutsOverlay.qml:292
246+#: qml/Components/KeyboardShortcutsOverlay.qml:305
239247msgid "Ctrl + Super + Down"
240248msgstr ""
241249
242−#: qml/Components/KeyboardShortcutsOverlay.qml:297
250+#: qml/Components/KeyboardShortcutsOverlay.qml:310
243251msgid "Minimizes or restores the current window."
244252msgstr ""
245253
246−#: qml/Components/KeyboardShortcutsOverlay.qml:305
254+#: qml/Components/KeyboardShortcutsOverlay.qml:318
247255msgid "Ctrl + Super + Left or Right"
248256msgstr ""
249257
250−#: qml/Components/KeyboardShortcutsOverlay.qml:310
258+#: qml/Components/KeyboardShortcutsOverlay.qml:323
251259msgid "Semi-maximizes the current window."
252260msgstr ""
253261
254−#: qml/Components/KeyboardShortcutsOverlay.qml:318
262+#: qml/Components/KeyboardShortcutsOverlay.qml:331
255263msgid "Alt + F4"
256264msgstr ""
257265
258−#: qml/Components/KeyboardShortcutsOverlay.qml:323
266+#: qml/Components/KeyboardShortcutsOverlay.qml:336
259267msgid "Closes the current window."
260268msgstr ""
261269
@@ -311,6 +319,28 @@ msgstr ""
311319msgid "Preview Share Item"
312320msgstr ""
313321
322+#: qml/Components/VirtualTouchPad.qml:317
323+msgid ""
324+"Your device is now connected to an external display. Use this screen as a "
325+"touch pad to interact with the pointer."
326+msgstr ""
327+
328+#: qml/Components/VirtualTouchPad.qml:327
329+msgid "Tap left button to click."
330+msgstr ""
331+
332+#: qml/Components/VirtualTouchPad.qml:339
333+msgid "Tap right button to right click."
334+msgstr ""
335+
336+#: qml/Components/VirtualTouchPad.qml:351
337+msgid "Swipe with two fingers to scroll."
338+msgstr ""
339+
340+#: qml/Components/VirtualTouchPad.qml:396
341+msgid "Find more settings in the system settings."
342+msgstr ""
343+
314344#: qml/Dash/DashPageHeader.qml:361
315345msgctxt "Button: Open the Ubuntu Store"
316346msgid "Store"
@@ -408,12 +438,6 @@ msgstr ""
408438msgid "Also installed"
409439msgstr ""
410440
411−#: qml/DisabledScreenNotice.qml:109
412−msgid ""
413−"Your device is now connected to an external display. Use this screen as a "
414−"touch pad to interact with the pointer."
415−msgstr ""
416−
417441#: qml/Greeter/CoverPage.qml:127
418442msgid "Unlock"
419443msgstr ""
@@ -437,7 +461,7 @@ msgid_plural "Please wait %1 minutes and then try again…"
437461msgstr[0] ""
438462msgstr[1] ""
439463
440−#: qml/Greeter/Greeter.qml:596
464+#: qml/Greeter/Greeter.qml:592
441465msgid "Try again"
442466msgstr ""
443467
@@ -465,6 +489,19 @@ msgstr ""
465489msgid "Select desktop environment"
466490msgstr ""
467491
492+#: qml/Launcher/Drawer.qml:80
493+msgid "Search…"
494+msgstr ""
495+
496+#: qml/Launcher/Drawer.qml:101
497+msgctxt "Apps sorted alphabetically"
498+msgid "A-Z"
499+msgstr ""
500+
501+#: qml/Launcher/MoreAppsHeader.qml:32
502+msgid "More apps in the store"
503+msgstr ""
504+
468505#: qml/Notifications/NotificationMenuItemFactory.qml:124
469506msgid "Show password"
470507msgstr ""
@@ -477,62 +514,62 @@ msgstr ""
477514msgid "Conference"
478515msgstr ""
479516
480−#: qml/Panel/Indicators/MenuItemFactory.qml:847
517+#: qml/Panel/Indicators/MenuItemFactory.qml:868
481518msgid "Nothing is playing"
482519msgstr ""
483520
484−#: qml/Panel/Indicators/MenuItemFactory.qml:975
521+#: qml/Panel/Indicators/MenuItemFactory.qml:996
485522#, qt-format
486523msgid "%1 hour"
487524msgid_plural "%1 hours"
488525msgstr[0] ""
489526msgstr[1] ""
490527
491−#: qml/Panel/Indicators/MenuItemFactory.qml:979
528+#: qml/Panel/Indicators/MenuItemFactory.qml:1000
492529#, qt-format
493530msgid "%1 minute"
494531msgid_plural "%1 minutes"
495532msgstr[0] ""
496533msgstr[1] ""
497534
498−#: qml/Panel/Indicators/MenuItemFactory.qml:984
535+#: qml/Panel/Indicators/MenuItemFactory.qml:1005
499536#, qt-format
500537msgid "%1 second"
501538msgid_plural "%1 seconds"
502539msgstr[0] ""
503540msgstr[1] ""
504541
505−#: qml/Panel/Indicators/MenuItemFactory.qml:987
542+#: qml/Panel/Indicators/MenuItemFactory.qml:1008
506543msgid "0 seconds"
507544msgstr ""
508545
509546#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
510−#: qml/Panel/Indicators/MenuItemFactory.qml:989
547+#: qml/Panel/Indicators/MenuItemFactory.qml:1010
511548#, qt-format
512549msgid "%1 remaining"
513550msgstr ""
514551
515−#: qml/Panel/Indicators/MenuItemFactory.qml:995
552+#: qml/Panel/Indicators/MenuItemFactory.qml:1016
516553msgid "In queue…"
517554msgstr ""
518555
519−#: qml/Panel/Indicators/MenuItemFactory.qml:999
556+#: qml/Panel/Indicators/MenuItemFactory.qml:1020
520557msgid "Downloading"
521558msgstr ""
522559
523−#: qml/Panel/Indicators/MenuItemFactory.qml:1001
560+#: qml/Panel/Indicators/MenuItemFactory.qml:1022
524561msgid "Paused, tap to resume"
525562msgstr ""
526563
527−#: qml/Panel/Indicators/MenuItemFactory.qml:1003
564+#: qml/Panel/Indicators/MenuItemFactory.qml:1024
528565msgid "Canceled"
529566msgstr ""
530567
531−#: qml/Panel/Indicators/MenuItemFactory.qml:1005
568+#: qml/Panel/Indicators/MenuItemFactory.qml:1026
532569msgid "Finished"
533570msgstr ""
534571
535−#: qml/Panel/Indicators/MenuItemFactory.qml:1007
572+#: qml/Panel/Indicators/MenuItemFactory.qml:1028
536573msgid "Failed, tap to retry"
537574msgstr ""
538575
qml/Components/KeyboardShortcutsOverlay.qml+13
@@ -173,6 +173,19 @@ Rectangle {
173173 Layout.maximumWidth: maxTextSize
174174 }
175175
176+ Label {
177+ text: i18n.tr("Super + A")
178+ fontSize: "small"
179+ font.weight: Font.Medium
180+ }
181+ Label {
182+ text: i18n.tr("Opens the Application Drawer.")
183+ fontSize: "small"
184+ font.weight: Font.Light
185+ wrapMode: Text.Wrap
186+ Layout.maximumWidth: maxTextSize
187+ }
188+
176189
177190 // Scopes section
178191 Item { Layout.columnSpan: 2; height: units.gu(2) }
qml/Components/VirtualTouchPad.qml+269−1
@@ -15,20 +15,58 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Layouts 1.1
1819import Ubuntu.Components 1.3
20+import Qt.labs.settings 1.0
21+import Unity.Screens 0.1
1922import UInput 0.1
23+import "../Components"
2024
2125Item {
26+ id: root
2227 property var uinput: UInput {
2328 Component.onCompleted: createMouse();
2429 Component.onDestruction: removeMouse();
2530 }
2631
27− readonly property bool pressed: point1.pressed || point2.pressed
32+ Component.onCompleted: {
33+ if (!settings.touchpadTutorialHasRun) {
34+ root.runTutorial()
35+ }
36+ }
37+
38+ function runTutorial() {
39+ // If the tutorial animation is started too early, e.g. in Component.onCompleted,
40+ // root width & height might be reported as 0x0 still. As animations read their
41+ // values at startup and won't update them, lets make sure to only start once
42+ // we have some actual size.
43+ if (root.width > 0 && root.height > 0) {
44+ tutorial.start();
45+ } else {
46+ tutorialTimer.start();
47+ }
48+ }
49+
50+ Timer {
51+ id: tutorialTimer
52+ interval: 50
53+ repeat: false
54+ running: false
55+ onTriggered: root.runTutorial();
56+ }
57+
58+ readonly property bool pressed: point1.pressed || point2.pressed || leftButton.pressed || rightButton.pressed
59+
60+ property var settings: Settings {
61+ objectName: "virtualTouchPadSettings"
62+ property bool touchpadTutorialHasRun: false
63+ property bool oskEnabled: true
64+ }
2865
2966 MultiPointTouchArea {
3067 objectName: "touchPadArea"
3168 anchors.fill: parent
69+ enabled: !tutorial.running || tutorial.paused
3270
3371 // FIXME: Once we have Qt DPR support, this should be Qt.styleHints.startDragDistance
3472 readonly property int clickThreshold: internalGu * 1.5
@@ -37,6 +75,11 @@ Item {
3775 property bool isDrag: false
3876
3977 onPressed: {
78+ if (tutorial.paused) {
79+ tutorial.resume();
80+ return;
81+ }
82+
4083 // If double-tapping *really* fast, it could happen that we end up having only point2 pressed
4184 // Make sure we check for both combos, only point1 or only point2
4285 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
@@ -135,4 +178,229 @@ Item {
135178 }
136179 ]
137180 }
181+
182+ RowLayout {
183+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom; margins: -internalGu * 1 }
184+ height: internalGu * 10
185+ spacing: internalGu * 1
186+
187+ MouseArea {
188+ id: leftButton
189+ objectName: "leftButton"
190+ Layout.fillWidth: true
191+ Layout.fillHeight: true
192+ onPressed: uinput.pressMouse(UInput.ButtonLeft);
193+ onReleased: uinput.releaseMouse(UInput.ButtonLeft);
194+ property bool highlight: false
195+ UbuntuShape {
196+ anchors.fill: parent
197+ backgroundColor: leftButton.highlight || leftButton.pressed ? UbuntuColors.ash : UbuntuColors.inkstone
198+ Behavior on backgroundColor { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
199+ }
200+ }
201+
202+ MouseArea {
203+ id: rightButton
204+ objectName: "rightButton"
205+ Layout.fillWidth: true
206+ Layout.fillHeight: true
207+ onPressed: uinput.pressMouse(UInput.ButtonRight);
208+ onReleased: uinput.releaseMouse(UInput.ButtonRight);
209+ property bool highlight: false
210+ UbuntuShape {
211+ anchors.fill: parent
212+ backgroundColor: rightButton.highlight || rightButton.pressed ? UbuntuColors.ash : UbuntuColors.inkstone
213+ Behavior on backgroundColor { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
214+ }
215+ }
216+ }
217+
218+ AbstractButton {
219+ id: oskButton
220+ objectName: "oskButton"
221+ anchors { right: parent.right; top: parent.top; margins: internalGu * 2 }
222+ height: internalGu * 6
223+ width: height
224+
225+ onClicked: {
226+ settings.oskEnabled = !settings.oskEnabled
227+ }
228+
229+ Rectangle {
230+ anchors.fill: parent
231+ radius: width / 2
232+ color: UbuntuColors.inkstone
233+ }
234+
235+ Icon {
236+ anchors.fill: parent
237+ anchors.margins: internalGu * 1.5
238+ name: "input-keyboard-symbolic"
239+ }
240+ }
241+
242+ Screens {
243+ id: screens
244+ }
245+
246+ InputMethod {
247+ id: inputMethod
248+ // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
249+ enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
250+ objectName: "inputMethod"
251+ anchors.fill: parent
252+ }
253+
254+ Label {
255+ id: tutorialLabel
256+ objectName: "tutorialLabel"
257+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: internalGu * 4; topMargin: internalGu * 10 }
258+ opacity: 0
259+ visible: opacity > 0
260+ font.pixelSize: 2 * internalGu
261+ color: "white"
262+ wrapMode: Text.WordWrap
263+ }
264+
265+ Icon {
266+ id: tutorialImage
267+ objectName: "tutorialImage"
268+ height: internalGu * 8
269+ width: height
270+ name: "input-touchpad-symbolic"
271+ color: "white"
272+ opacity: 0
273+ visible: opacity > 0
274+ anchors { top: tutorialLabel.bottom; horizontalCenter: parent.horizontalCenter; margins: internalGu * 2 }
275+ }
276+
277+ Item {
278+ id: tutorialFinger1
279+ objectName: "tutorialFinger1"
280+ width: internalGu * 5
281+ height: width
282+ property real scale: 1
283+ opacity: 0
284+ visible: opacity > 0
285+ Rectangle {
286+ width: parent.width * parent.scale
287+ height: width
288+ anchors.centerIn: parent
289+ radius: width / 2
290+ color: UbuntuColors.inkstone
291+ }
292+ }
293+
294+ Item {
295+ id: tutorialFinger2
296+ objectName: "tutorialFinger2"
297+ width: internalGu * 5
298+ height: width
299+ property real scale: 1
300+ opacity: 0
301+ visible: opacity > 0
302+ Rectangle {
303+ width: parent.width * parent.scale
304+ height: width
305+ anchors.centerIn: parent
306+ radius: width / 2
307+ color: UbuntuColors.inkstone
308+ }
309+ }
310+
311+ SequentialAnimation {
312+ id: tutorial
313+ objectName: "tutorialAnimation"
314+
315+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: false }
316+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "opacity"; value: 0 }
317+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.") }
318+ UbuntuNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
319+ PropertyAction { target: tutorial; property: "paused"; value: true }
320+ PauseAnimation { duration: 500 } // it takes a bit until pausing actually takes effect
321+ UbuntuNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
322+
323+ UbuntuNumberAnimation { target: leftButton; property: "opacity"; to: 1 }
324+ UbuntuNumberAnimation { target: rightButton; property: "opacity"; to: 1 }
325+
326+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
327+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap left button to click.") }
328+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
329+ SequentialAnimation {
330+ loops: 2
331+ PropertyAction { target: leftButton; property: "highlight"; value: true }
332+ PauseAnimation { duration: UbuntuAnimation.FastDuration }
333+ PropertyAction { target: leftButton; property: "highlight"; value: false }
334+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
335+ }
336+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
337+
338+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
339+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap right button to right click.") }
340+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
341+ SequentialAnimation {
342+ loops: 2
343+ PropertyAction { target: rightButton; property: "highlight"; value: true }
344+ PauseAnimation { duration: UbuntuAnimation.FastDuration }
345+ PropertyAction { target: rightButton; property: "highlight"; value: false }
346+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
347+ }
348+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
349+
350+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
351+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Swipe with two fingers to scroll.") }
352+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
353+ PropertyAction { target: tutorialFinger1; property: "x"; value: root.width / 2 - tutorialFinger1.width - internalGu * 1 }
354+ PropertyAction { target: tutorialFinger2; property: "x"; value: root.width / 2 + tutorialFinger1.width + internalGu * 1 - tutorialFinger2.width }
355+ PropertyAction { target: tutorialFinger1; property: "y"; value: root.height / 2 - internalGu * 10 }
356+ PropertyAction { target: tutorialFinger2; property: "y"; value: root.height / 2 - internalGu * 10 }
357+ SequentialAnimation {
358+ ParallelAnimation {
359+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
360+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
361+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
362+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
363+ }
364+ ParallelAnimation {
365+ UbuntuNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 + internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
366+ UbuntuNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 + internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
367+ }
368+ ParallelAnimation {
369+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
370+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
371+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
372+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
373+ }
374+ PauseAnimation { duration: UbuntuAnimation.SlowDuration }
375+ ParallelAnimation {
376+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
377+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
378+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
379+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
380+ }
381+ ParallelAnimation {
382+ UbuntuNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 - internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
383+ UbuntuNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 - internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
384+ }
385+ ParallelAnimation {
386+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
387+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
388+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
389+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
390+ }
391+ PauseAnimation { duration: UbuntuAnimation.SlowDuration }
392+ }
393+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
394+
395+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
396+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Find more settings in the system settings.") }
397+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
398+ PauseAnimation { duration: 2000 }
399+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
400+
401+ UbuntuNumberAnimation { target: oskButton; property: "opacity"; to: 1 }
402+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: true }
403+
404+ PropertyAction { target: settings; property: "touchpadTutorialHasRun"; value: true }
405+ }
138406}
qml/Dash/Previews/PreviewAudioPlayback.qml+1
@@ -70,6 +70,7 @@ PreviewWidget {
7070 objectName: "playButton"
7171 width: trackRow.column1Width
7272 height: width
73+ activeFocusOnPress: false
7374
7475 onClicked: {
7576 if (trackItem.isPlayingItem) {
qml/DisabledScreenNotice.qml+3−55
@@ -18,15 +18,12 @@ import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import Unity.Session 0.1
21−import Unity.Screens 0.1
2221import QtQuick.Window 2.2
2322import "Components"
2423
2524Item {
2625 id: root
2726
28− property bool infoNoteDisplayed: true
29−
3027 // For testing
3128 property var screen: Screen
3229 property var orientationLock: OrientationLock
@@ -36,10 +33,6 @@ Item {
3633 name: applicationArguments.deviceName
3734 }
3835
39− Screens {
40− id: screens
41− }
42−
4336 Item {
4437 id: contentContainer
4538 objectName: "contentContainer"
@@ -75,58 +68,13 @@ Item {
7568 }
7669 transformOrigin: Item.Center
7770
78− VirtualTouchPad {
79− anchors.fill: parent
80−
81− onPressedChanged: {
82− if (pressed && infoNoteDisplayed) {
83− infoNoteDisplayed = false;
84− }
85− }
86− }
87−
8871 Rectangle {
8972 anchors.fill: parent
90− color: "#3b3b3b"
73+ color: UbuntuColors.jet
9174 }
9275
93− Item {
94− objectName: "infoNoticeArea"
95− anchors.fill: parent
96− opacity: infoNoteDisplayed ? 1 : 0
97− visible: opacity > 0
98− Behavior on opacity {
99− UbuntuNumberAnimation { }
100− }
101−
102− Column {
103− anchors.centerIn: parent
104− width: parent.width - (internalGu * 8)
105− spacing: internalGu * 4
106−
107− Label {
108− id: text
109− text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.")
110− color: "white"
111− width: parent.width
112− font.pixelSize: 2.5 * internalGu
113− wrapMode: Text.Wrap
114− }
115− Icon {
116− height: internalGu * 8
117− width: height
118− name: "input-touchpad-symbolic"
119− color: "white"
120− anchors.horizontalCenter: parent.horizontalCenter
121− }
122− }
123− }
124−
125− InputMethod {
126− id: inputMethod
127− // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
128− enabled: screens.count > 1
129− objectName: "inputMethod"
76+ VirtualTouchPad {
77+ objectName: "virtualTouchPad"
13078 anchors.fill: parent
13179 }
13280 }
qml/Greeter/Greeter.qml+1−5
@@ -145,11 +145,7 @@ Showable {
145145 }
146146 }
147147
148− if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
149− return LightDMService.greeter.defaultSession;
150− } else {
151− return "ubuntu"; // The default / fallback
152− }
148+ return LightDMService.greeter.defaultSession;
153149 }
154150
155151 QtObject {
qml/Greeter/LoginList.qml+1−1
@@ -165,7 +165,7 @@ StyledItem {
165165 }
166166
167167 delegate: Item {
168− width: parent.width
168+ width: userList.width
169169 height: root.cellHeight
170170
171171 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
qml/Greeter/WideView.qml+7−2
@@ -125,6 +125,8 @@ FocusScope {
125125 id: loginList
126126 objectName: "loginList"
127127
128+ property int selectedUserIndex: 0
129+
128130 width: units.gu(40)
129131 anchors {
130132 left: parent.left
@@ -139,9 +141,12 @@ FocusScope {
139141 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
140142
141143 model: root.userModel
142− currentSession: LightDMService.greeter.defaultSession
144+ currentSession: LightDMService.users.data(selectedUserIndex, LightDMService.userRoles.SessionRole);
143145 onResponded: root.responded(response)
144− onSelected: root.selected(index)
146+ onSelected: {
147+ root.selected(index)
148+ loginList.selectedUserIndex = index;
149+ }
145150 onSessionChooserButtonClicked: parent.state = "SessionsList"
146151
147152 Keys.forwardTo: [sessionChooserLoader.item]
qml/Launcher/BackgroundBlur.qmlnew file+81
@@ -0,0 +1,81 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import Ubuntu.Components 1.3
19+import QtGraphicalEffects 1.0
20+
21+Item {
22+ id: root
23+
24+ property int blurAmount: 32
25+ property Item sourceItem
26+ property rect blurRect: Qt.rect(0,0,0,0)
27+ property alias cached: fastBlur.cached
28+
29+ Rectangle {
30+ id: blurMask
31+ color: "yellow"
32+ x: blurRect.x
33+ y: blurRect.y
34+ width: blurRect.width
35+ height: blurRect.height
36+ }
37+
38+ ShaderEffect {
39+ id: maskedBlurEffect
40+ x: blurRect.x
41+ y: blurRect.y
42+ width: blurRect.width
43+ height: blurRect.height
44+
45+ property variant source: ShaderEffectSource {
46+ id: shaderEffectSource
47+ sourceItem: root.sourceItem
48+ hideSource: false
49+ sourceRect: root.blurRect
50+ }
51+
52+ property var mask: ShaderEffectSource {
53+ sourceItem: blurMask
54+ hideSource: true
55+ }
56+
57+ fragmentShader: "
58+ varying highp vec2 qt_TexCoord0;
59+ uniform sampler2D source;
60+ uniform sampler2D mask;
61+ void main(void)
62+ {
63+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
64+ highp vec4 maskColor = texture2D(mask, qt_TexCoord0);
65+
66+ sourceColor *= maskColor.a;
67+
68+ gl_FragColor = sourceColor;
69+ }"
70+ }
71+
72+ FastBlur {
73+ id: fastBlur
74+ x: blurRect.x
75+ y: blurRect.y
76+ width: blurRect.width
77+ height: blurRect.height
78+ source: maskedBlurEffect
79+ radius: Math.min(blurAmount, 128)
80+ }
81+ }
qml/Launcher/Drawer.qmlnew file+306
@@ -0,0 +1,306 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import Ubuntu.Components 1.3
19+import Unity.Launcher 0.1
20+import Utils 0.1
21+import "../Components"
22+import Qt.labs.settings 1.0
23+
24+FocusScope {
25+ id: root
26+
27+ property int panelWidth: 0
28+ readonly property bool moving: listLoader.item && listLoader.item.moving
29+
30+ signal applicationSelected(string appId)
31+
32+ property bool draggingHorizontally: false
33+ property int dragDistance: 0
34+
35+ onFocusChanged: {
36+ if (focus) {
37+ searchField.selectAll();
38+ }
39+ }
40+
41+ function focusInput() {
42+ searchField.selectAll();
43+ searchField.focus = true;
44+ }
45+
46+ Settings {
47+ property alias selectedTab: sections.selectedIndex
48+ }
49+
50+ MouseArea {
51+ anchors.fill: parent
52+ hoverEnabled: true
53+ acceptedButtons: Qt.AllButtons
54+ onWheel: wheel.accepted = true
55+ }
56+
57+ Rectangle {
58+ anchors.fill: parent
59+ color: "#BF000000"
60+
61+ AppDrawerModel {
62+ id: appDrawerModel
63+ }
64+
65+ AppDrawerProxyModel {
66+ id: sortProxyModel
67+ source: appDrawerModel
68+ filterString: searchField.displayText
69+ sortBy: AppDrawerProxyModel.SortByAToZ
70+ }
71+
72+ Item {
73+ id: contentContainer
74+ anchors.fill: parent
75+ anchors.leftMargin: root.panelWidth
76+
77+ TextField {
78+ id: searchField
79+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
80+ placeholderText: i18n.tr("Search…")
81+ focus: true
82+ onAccepted: {
83+ if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
84+ root.applicationSelected(listLoader.item.getFirstAppId());
85+ }
86+ }
87+ }
88+
89+ Item {
90+ id: sectionsContainer
91+ anchors { left: parent.left; top: searchField.bottom; right: parent.right; }
92+ height: sections.height
93+ clip: true
94+ z: 2
95+
96+ Sections {
97+ id: sections
98+ width: parent.width
99+ actions: [
100+ Action {
101+ text: i18n.ctr("Apps sorted alphabetically", "A-Z")
102+ // TODO: Disabling this for now as we don't get the right input from u-a-l yet.
103+// },
104+// Action {
105+// text: i18n.ctr("Most used apps", "Most used")
106+ }
107+ ]
108+
109+ Rectangle {
110+ anchors.bottom: parent.bottom
111+ height: units.dp(1)
112+ color: 'gray'
113+ width: contentContainer.width
114+ }
115+ }
116+ }
117+
118+ Loader {
119+ id: listLoader
120+ anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
121+ sourceComponent: {
122+ switch (sections.selectedIndex) {
123+ case 0: return aToZComponent;
124+ case 1: return mostUsedComponent;
125+ }
126+ }
127+ Binding {
128+ target: listLoader.item || null
129+ property: "objectName"
130+ value: "drawerItemList"
131+ }
132+ }
133+
134+ MouseArea {
135+ parent: listLoader.item ? listLoader.item : null
136+ anchors.fill: parent
137+ propagateComposedEvents: true
138+ property int oldX: 0
139+ onPressed: {
140+ oldX = mouseX;
141+ }
142+ onMouseXChanged: {
143+ var diff = oldX - mouseX;
144+ root.draggingHorizontally |= diff > units.gu(2);
145+ if (!root.draggingHorizontally) {
146+ return;
147+ }
148+ propagateComposedEvents = false;
149+ parent.interactive = false;
150+ root.dragDistance += diff;
151+ oldX = mouseX
152+ }
153+ onReleased: {
154+ if (root.draggingHorizontally) {
155+ root.draggingHorizontally = false;
156+ parent.interactive = true;
157+ }
158+ reactivateTimer.start();
159+ }
160+ Timer {
161+ id: reactivateTimer
162+ interval: 0
163+ onTriggered: parent.propagateComposedEvents = true;
164+ }
165+ }
166+
167+ Component {
168+ id: mostUsedComponent
169+ DrawerListView {
170+
171+ header: MoreAppsHeader {
172+ width: parent.width
173+ height: units.gu(6)
174+ }
175+
176+ model: AppDrawerProxyModel {
177+ source: sortProxyModel
178+ group: AppDrawerProxyModel.GroupByAll
179+ sortBy: AppDrawerProxyModel.SortByUsage
180+ }
181+
182+ delegate: UbuntuShape {
183+ width: parent.width
184+ color: "#20ffffff"
185+ aspect: UbuntuShape.Flat
186+ // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
187+ // which messes up the ListView.
188+ height: (Math.ceil(mostUsedGridView.model.count / mostUsedGridView.columns) * mostUsedGridView.delegateHeight) + units.gu(2)
189+
190+ readonly property string appId: model.appId
191+
192+ DrawerGridView {
193+ id: mostUsedGridView
194+ anchors.fill: parent
195+ topMargin: units.gu(1)
196+ bottomMargin: units.gu(1)
197+ clip: true
198+
199+ model: sortProxyModel
200+
201+ delegateWidth: units.gu(8)
202+ delegateHeight: units.gu(10)
203+ delegate: drawerDelegateComponent
204+ }
205+ }
206+ }
207+ }
208+
209+ Component {
210+ id: aToZComponent
211+ DrawerListView {
212+
213+ header: MoreAppsHeader {
214+ width: parent.width
215+ height: units.gu(6)
216+ }
217+
218+ model: AppDrawerProxyModel {
219+ source: sortProxyModel
220+ sortBy: AppDrawerProxyModel.SortByAToZ
221+ group: AppDrawerProxyModel.GroupByAToZ
222+ }
223+
224+ delegate: UbuntuShape {
225+ width: parent.width
226+ color: "#20ffffff"
227+ aspect: UbuntuShape.Flat
228+
229+ readonly property string appId: model.appId
230+
231+ // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
232+ // which messes up the ListView.
233+ height: (Math.ceil(gridView.model.count / gridView.columns) * gridView.delegateHeight) +
234+ categoryNameLabel.implicitHeight + units.gu(2)
235+
236+ Label {
237+ id: categoryNameLabel
238+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
239+ text: model.letter
240+ }
241+
242+ DrawerGridView {
243+ id: gridView
244+ anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
245+ height: rows * delegateHeight
246+
247+ interactive: false
248+
249+ model: AppDrawerProxyModel {
250+ id: categoryModel
251+ source: sortProxyModel
252+ filterLetter: model.letter
253+ }
254+ delegateWidth: units.gu(8)
255+ delegateHeight: units.gu(10)
256+ delegate: drawerDelegateComponent
257+ }
258+ }
259+ }
260+ }
261+ }
262+
263+ Component {
264+ id: drawerDelegateComponent
265+ AbstractButton {
266+ width: GridView.view.cellWidth
267+ height: units.gu(10)
268+ objectName: "drawerItem_" + model.appId
269+
270+ onClicked: root.applicationSelected(model.appId)
271+
272+ Column {
273+ width: units.gu(8)
274+ anchors.horizontalCenter: parent.horizontalCenter
275+ height: childrenRect.height
276+ spacing: units.gu(1)
277+
278+ UbuntuShape {
279+ id: appIcon
280+ width: units.gu(6)
281+ height: 7.5 / 8 * width
282+ anchors.horizontalCenter: parent.horizontalCenter
283+ backgroundMode: UbuntuShape.SolidColor
284+ backgroundColor: UbuntuColors.lightGrey
285+ radius: "medium"
286+ borderSource: 'undefined'
287+ source: Image {
288+ id: sourceImage
289+ sourceSize.width: appIcon.width
290+ source: model.icon
291+ }
292+ sourceFillMode: UbuntuShape.PreserveAspectCrop
293+ }
294+
295+ Label {
296+ text: model.name
297+ width: parent.width
298+ horizontalAlignment: Text.AlignHCenter
299+ fontSize: "small"
300+ elide: Text.ElideRight
301+ }
302+ }
303+ }
304+ }
305+ }
306+}
qml/Launcher/DrawerGridView.qmlnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import "../Components"
19+
20+Item {
21+ id: root
22+
23+ property int delegateWidth: units.gu(10)
24+ property int delegateHeight: units.gu(10)
25+ property alias delegate: gridView.delegate
26+ property alias model: gridView.model
27+ property alias interactive: gridView.interactive
28+
29+ property alias header: gridView.header
30+ property alias topMargin: gridView.topMargin
31+ property alias bottomMargin: gridView.bottomMargin
32+
33+ readonly property int columns: width / delegateWidth
34+ readonly property int rows: Math.ceil(gridView.model.count / root.columns)
35+
36+ GridView {
37+ id: gridView
38+ anchors.fill: parent
39+ leftMargin: spacing
40+
41+ readonly property int overflow: width - (root.columns * root.delegateWidth)
42+ readonly property real spacing: overflow / (root.columns)
43+
44+ cellWidth: root.delegateWidth + spacing
45+ cellHeight: root.delegateHeight
46+ }
47+}
qml/Launcher/DrawerListView.qmlnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import Ubuntu.Components 1.3
19+import "../Components"
20+
21+ListView {
22+ id: root
23+ anchors.fill: parent
24+ topMargin: units.gu(1)
25+ bottomMargin: units.gu(1)
26+ spacing: units.gu(1)
27+ clip: true
28+
29+ function getFirstAppId() {
30+ return model.appId(0);
31+ }
32+}
qml/Launcher/Launcher.qml+182−34
@@ -19,18 +19,23 @@ import "../Components"
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Launcher 0.1
22+import Utils 0.1 as Utils
2223
2324FocusScope {
2425 id: root
2526
27+ readonly property int ignoreHideIfMouseOverLauncher: 1
28+
2629 property bool autohideEnabled: false
2730 property bool lockedVisible: false
2831 property bool available: true // can be used to disable all interactions
2932 property alias inverted: panel.inverted
33+ property Item blurSource: null
34+ property int topPanelHeight: 0
35+ property bool drawerEnabled: true
3036
3137 property int panelWidth: units.gu(10)
3238 property int dragAreaWidth: units.gu(1)
33− property int minimizeDistance: units.gu(26)
3439 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
3540 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
3641
@@ -43,13 +48,11 @@ FocusScope {
4348 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
4449
4550 readonly property bool shown: panel.x > -panel.width
51+ readonly property bool drawerShown: drawer.x == 0
4652
4753 // emitted when an application is selected
4854 signal launcherApplicationSelected(string appId)
4955
50− // emitted when the apps dash should be shown because of a swipe gesture
51− signal dash()
52−
5356 // emitted when the dash icon in the launcher has been tapped
5457 signal showDashHome()
5558
@@ -62,6 +65,9 @@ FocusScope {
6265 }
6366
6467 onSuperPressedChanged: {
68+ if (state == "drawer")
69+ return;
70+
6571 if (superPressed) {
6672 superPressTimer.start();
6773 superLongPressTimer.start();
@@ -102,7 +108,10 @@ FocusScope {
102108 }
103109 }
104110
105− function hide() {
111+ function hide(flags) {
112+ if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
113+ return;
114+ }
106115 switchToNextState("")
107116 }
108117
@@ -132,7 +141,7 @@ FocusScope {
132141 }
133142
134143 function pushEdge(amount) {
135− if (root.state === "") {
144+ if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
136145 edgeBarrier.push(amount);
137146 }
138147 }
@@ -143,6 +152,22 @@ FocusScope {
143152 switchToNextState("visible")
144153 }
145154
155+ function openDrawer(focusInputField) {
156+ if (!drawerEnabled) {
157+ return;
158+ }
159+
160+ panel.shortcutHintsShown = false;
161+ superPressTimer.stop();
162+ superLongPressTimer.stop();
163+ root.focus = true;
164+ drawer.focus = true;
165+ if (focusInputField) {
166+ drawer.focusInput();
167+ }
168+ switchToNextState("drawer")
169+ }
170+
146171 Keys.onPressed: {
147172 switch (event.key) {
148173 case Qt.Key_Backtab:
@@ -278,7 +303,7 @@ FocusScope {
278303 InverseMouseArea {
279304 id: closeMouseArea
280305 anchors.fill: panel
281− enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
306+ enabled: root.state == "visible" || root.state == "drawer"
282307 visible: enabled
283308 onPressed: {
284309 mouse.accepted = false;
@@ -307,23 +332,61 @@ FocusScope {
307332 }
308333 }
309334
310− EdgeBarrier {
311− id: edgeBarrier
312− edge: Qt.LeftEdge
313− target: parent
314− enabled: root.available
315− onPassed: { root.switchToNextState("visibleTemporary"); }
316− material: Component {
317− Item {
318− Rectangle {
319− width: parent.height
320− height: parent.width
321− rotation: -90
322− anchors.centerIn: parent
323− gradient: Gradient {
324− GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
325− GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
326− }
335+ BackgroundBlur {
336+ id: backgroundBlur
337+ anchors.fill: parent
338+ anchors.topMargin: root.inverted ? 0 : -root.topPanelHeight
339+ visible: root.blurSource && drawer.x > -drawer.width
340+ blurAmount: units.gu(6)
341+ sourceItem: root.blurSource
342+ blurRect: Qt.rect(panel.width,
343+ root.topPanelHeight,
344+ drawer.width + drawer.x - panel.width,
345+ height - root.topPanelHeight)
346+ cached: drawer.moving
347+ }
348+
349+ Drawer {
350+ id: drawer
351+ objectName: "drawer"
352+ anchors {
353+ top: parent.top
354+ topMargin: root.inverted ? root.topPanelHeight : 0
355+ bottom: parent.bottom
356+ right: parent.left
357+ }
358+ width: Math.min(root.width, units.gu(90)) * .9
359+ panelWidth: panel.width
360+ visible: x > -width
361+
362+ Behavior on anchors.rightMargin {
363+ enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate && !drawer.draggingHorizontally
364+ NumberAnimation {
365+ duration: 300
366+ easing.type: Easing.OutCubic
367+ }
368+ }
369+
370+ onApplicationSelected: {
371+ root.hide();
372+ root.launcherApplicationSelected(appId)
373+ root.focus = false;
374+ }
375+
376+ Keys.onEscapePressed: {
377+ switchToNextState("");
378+ root.focus = false;
379+ }
380+
381+ onDragDistanceChanged: {
382+ anchors.rightMargin = Math.max(-drawer.width, anchors.rightMargin + dragDistance);
383+ }
384+ onDraggingHorizontallyChanged: {
385+ if (!draggingHorizontally) {
386+ if (drawer.x < -units.gu(10)) {
387+ root.hide();
388+ } else {
389+ root.openDrawer();
327390 }
328391 }
329392 }
@@ -332,7 +395,7 @@ FocusScope {
332395 LauncherPanel {
333396 id: panel
334397 objectName: "launcherPanel"
335− enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
398+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
336399 width: root.panelWidth
337400 anchors {
338401 top: parent.top
@@ -359,11 +422,11 @@ FocusScope {
359422 property bool animate: true
360423
361424 onApplicationSelected: {
362− root.hide();
425+ root.hide(ignoreHideIfMouseOverLauncher);
363426 launcherApplicationSelected(appId)
364427 }
365428 onShowDashHome: {
366− root.hide();
429+ root.hide(ignoreHideIfMouseOverLauncher);
367430 root.showDashHome();
368431 }
369432
@@ -394,6 +457,38 @@ FocusScope {
394457 }
395458 }
396459
460+ EdgeBarrier {
461+ id: edgeBarrier
462+ edge: Qt.LeftEdge
463+ target: parent
464+ enabled: root.available
465+ onProgressChanged: {
466+ if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
467+ root.switchToNextState("visibleTemporary");
468+ }
469+ }
470+ onPassed: {
471+ if (root.drawerEnabled) {
472+ root.switchToNextState("drawer");
473+ }
474+ }
475+
476+ material: Component {
477+ Item {
478+ Rectangle {
479+ width: parent.height
480+ height: parent.width
481+ rotation: -90
482+ anchors.centerIn: parent
483+ gradient: Gradient {
484+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
485+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
486+ }
487+ }
488+ }
489+ }
490+ }
491+
397492 SwipeArea {
398493 id: dragArea
399494 objectName: "launcherDragArea"
@@ -405,25 +500,62 @@ FocusScope {
405500 width: root.dragAreaWidth
406501 height: root.height
407502
503+ function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
504+
505+ property var lastDragPoints: []
506+
507+ function dragDirection() {
508+ if (lastDragPoints.length < 5) {
509+ return "unknown";
510+ }
511+
512+ var toRight = true;
513+ var toLeft = true;
514+ for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
515+ if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
516+ toRight = false;
517+ }
518+ if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
519+ toLeft = false;
520+ }
521+ }
522+ return toRight ? "right" : toLeft ? "left" : "unknown";
523+ }
524+
408525 onDistanceChanged: {
409− if (!dragging || launcher.state == "visible")
410− return;
526+ if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
527+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
528+ }
411529
412− panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
530+ if (root.drawerEnabled && dragging && launcher.state != "drawer") {
531+ lastDragPoints.push(distance)
532+ var drawerHintDistance = panel.width + units.gu(1)
533+ if (distance < drawerHintDistance) {
534+ drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
535+ } else {
536+ var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
537+ var linearDrawerProgress = linearDrawerX / (drawer.width)
538+ var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
539+ drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
540+ }
541+ }
413542 }
414543
415544 onDraggingChanged: {
416545 if (!dragging) {
417546 if (distance > panel.width / 2) {
418− root.switchToNextState("visible")
419− if (distance > minimizeDistance) {
420− root.dash();
547+ if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
548+ root.switchToNextState("drawer");
549+ root.focus = true;
550+ } else {
551+ root.switchToNextState("visible");
421552 }
422553 } else if (root.state === "") {
423554 // didn't drag far enough. rollback
424− root.switchToNextState("")
555+ root.switchToNextState("");
425556 }
426557 }
558+ lastDragPoints = [];
427559 }
428560 }
429561
@@ -434,6 +566,10 @@ FocusScope {
434566 target: panel
435567 x: -root.panelWidth
436568 }
569+ PropertyChanges {
570+ target: drawer
571+ anchors.rightMargin: 0
572+ }
437573 },
438574 State {
439575 name: "visible"
@@ -441,6 +577,18 @@ FocusScope {
441577 target: panel
442578 x: -root.x // so we never go past panelWidth, even when teased by tutorial
443579 }
580+ PropertyChanges {
581+ target: drawer
582+ anchors.rightMargin: 0
583+ }
584+ },
585+ State {
586+ name: "drawer"
587+ extend: "visible"
588+ PropertyChanges {
589+ target: drawer
590+ anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
591+ }
444592 },
445593 State {
446594 name: "visibleTemporary"
qml/Launcher/MoreAppsHeader.qmlnew file+46
@@ -0,0 +1,46 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+
4+AbstractButton {
5+ id: root
6+
7+ onClicked: {
8+ // TODO: Make this point to the snappy store as soon as we stop landing to vivid
9+ Qt.openUrlExternally("scope://com.canonical.scopes.clickstore")
10+ }
11+
12+ UbuntuShape {
13+ width: parent.width
14+ height: parent.height - units.gu(1)
15+ color: "#20ffffff"
16+ aspect: UbuntuShape.Flat
17+
18+ Row {
19+ anchors.fill: parent
20+ anchors.margins: units.gu(1)
21+ spacing: units.gu(1)
22+
23+ Icon {
24+ height: units.gu(2.2)
25+ width: height
26+ name: "stock_application"
27+ anchors.verticalCenter: parent.verticalCenter
28+ color: "white"
29+ }
30+
31+ Label {
32+ text: i18n.tr("More apps in the store")
33+ anchors.verticalCenter: parent.verticalCenter
34+ fontSize: "small"
35+ }
36+
37+ Icon {
38+ height: units.gu(2.5)
39+ width: height
40+ anchors.verticalCenter: parent.verticalCenter
41+ name: "go-next"
42+ color: "white"
43+ }
44+ }
45+ }
46+}
qml/Panel/Indicators/IndicatorMenuItemFactory.qml+22−1
@@ -107,7 +107,7 @@ Object {
107107 }
108108
109109 if (component === undefined) {
110− console.debug("Don't know how to make " + modelData.type + " for " + indicator);
110+ console.debug("Don't know how to make " + type + " for " + indicator);
111111 }
112112
113113 return component
@@ -399,7 +399,9 @@ Object {
399399 id: switchItem
400400 objectName: "switchMenu"
401401 property QtObject menuData: null
402+ property var menuModel: menuFactory.menuModel
402403 property int menuIndex: -1
404+ property var extendedData: menuData && menuData.ext || undefined
403405 property bool serverChecked: menuData && menuData.isToggled || false
404406
405407 text: menuData && menuData.label || ""
@@ -408,6 +410,25 @@ Object {
408410 checked: serverChecked
409411 highlightWhenPressed: false
410412
413+ property var subtitleAction: UnityMenuAction {
414+ model: menuModel
415+ index: menuIndex
416+ name: getExtendedProperty(extendedData, "xCanonicalSubtitleAction", "")
417+ }
418+ subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
419+
420+ onMenuModelChanged: {
421+ loadAttributes();
422+ }
423+ onMenuIndexChanged: {
424+ loadAttributes();
425+ }
426+
427+ function loadAttributes() {
428+ if (!menuModel || menuIndex == -1) return;
429+ menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-subtitle-action': 'string'});
430+ }
431+
411432 ServerPropertySynchroniser {
412433 objectName: "sync"
413434 syncTimeout: Utils.Constants.indicatorValueTimeout
qml/Shell.qml+11−35
@@ -124,7 +124,7 @@ StyledItem {
124124 greeter.notifyAppFocusRequested(appId);
125125
126126 panel.indicators.hide();
127− launcher.hide();
127+ launcher.hide(launcher.ignoreHideIfMouseOverLauncher);
128128 }
129129
130130 // For autopilot consumption
@@ -278,8 +278,6 @@ StyledItem {
278278
279279 dragAreaWidth: shell.edgeSize
280280 background: wallpaperResolver.background
281− leftEdgeDragProgress: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 :
282− Math.max(0, (launcher.dragDistance * (stage.width - launcher.panelWidth) / stage.width) - launcher.panelWidth)
283281
284282 applicationManager: ApplicationManager
285283 topLevelSurfaceList: topLevelSurfaceList
@@ -301,14 +299,12 @@ StyledItem {
301299
302300 interactive: (!greeter || !greeter.shown)
303301 && panel.indicators.fullyClosed
304− && launcher.progress == 0
305302 && !notifications.useModal
306303
307304 onInteractiveChanged: { if (interactive) { focus = true; } }
308305
309306 leftMargin: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
310307 suspended: greeter.shown
311− keepDashRunning: launcher.shown || launcher.dashSwipe
312308 altTabPressed: physicalKeysMapper.altTabPressed
313309 oskEnabled: shell.oskEnabled
314310 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
@@ -328,7 +324,7 @@ StyledItem {
328324 topMargin: panel.panelHeight
329325 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
330326 }
331− z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
327+ z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running || launcher.drawerShown ? overlay.z + 1 : overlay.z - 1
332328 }
333329
334330 Loader {
@@ -350,7 +346,6 @@ StyledItem {
350346 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
351347 hides: [launcher, panel.indicators, panel.applicationMenus]
352348 tabletMode: shell.usageScenario != "phone"
353− launcherOffset: launcher.progress
354349 forcedUnlock: wizard.active || shell.mode === "full-shell"
355350 background: wallpaperResolver.cachedBackground
356351 hasCustomBackground: wallpaperResolver.hasCustomBackground
@@ -439,19 +434,6 @@ StyledItem {
439434 }
440435 }
441436
442− function showDash() {
443− if (greeter.notifyShowingDashFromDrag()) {
444− launcher.fadeOut();
445− }
446−
447− if (!greeter.locked && tutorial.launcherLongSwipeEnabled
448− && (ApplicationManager.focusedApplicationId != "unity8-dash" || stage.spreadShown)) {
449− ApplicationManager.requestFocusApplication("unity8-dash")
450− launcher.fadeOut();
451− stage.closeSpread();
452− }
453− }
454−
455437 Item {
456438 id: overlay
457439 z: 10
@@ -506,15 +488,6 @@ StyledItem {
506488 id: launcher
507489 objectName: "launcher"
508490
509− /*
510− * Since the Dash doesn't have the same controll over surfaces that the
511− * Shell does, it can't slowly move the scope out of the way, as the shell
512− * does with apps, and the dash is show instantly. This allows for some
513− * leeway and prevents accidental home swipes.
514− */
515− readonly property real offset: shell.focusedApplicationId == "unity8-dash" ? units.gu(12) : 0
516− readonly property bool dashSwipe: progress > offset
517−
518491 anchors.top: parent.top
519492 anchors.topMargin: inverted ? 0 : panel.panelHeight
520493 anchors.bottom: parent.bottom
@@ -530,14 +503,11 @@ StyledItem {
530503 superTabPressed: physicalKeysMapper.superTabPressed
531504 panelWidth: units.gu(settings.launcherWidth)
532505 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
506+ blurSource: greeter.shown ? greeter : stages
507+ topPanelHeight: panel.panelHeight
508+ drawerEnabled: !greeter.shown
533509
534510 onShowDashHome: showHome()
535− onDash: showDash()
536− onDashSwipeChanged: {
537− if (dashSwipe) {
538− dash.setCurrentScope(0, false, true)
539− }
540− }
541511 onLauncherApplicationSelected: {
542512 greeter.notifyUserRequestedApp();
543513 shell.activateApplication(appId);
@@ -553,6 +523,12 @@ StyledItem {
553523 }
554524 }
555525
526+ GlobalShortcut {
527+ shortcut: Qt.MetaModifier | Qt.Key_A
528+ onTriggered: {
529+ launcher.openDrawer(true);
530+ }
531+ }
556532 GlobalShortcut {
557533 shortcut: Qt.AltModifier | Qt.Key_F1
558534 onTriggered: {
qml/Stage/MoveHandler.qml+2−2
@@ -90,8 +90,8 @@ QtObject {
9090 if (target.anyMaximized) {
9191 // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
9292 // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
93− priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
94− priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
93+ priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.normalWidth);
94+ priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.normalHeight);
9595 } else {
9696 priv.distanceX = pos.x;
9797 priv.distanceY = pos.y;
qml/Stage/Spread/WindowedRightEdgeMaths.qml+19−4
@@ -25,21 +25,31 @@ QtObject {
2525 // Input
2626 property int itemIndex: 0
2727 property int normalZ: 0
28− property real progress: 0
2928 property int startWidth: 0
3029 property int startHeight: 0
3130 property int startX: 0
3231 property int targetX: 0
3332 property int startY: 0
3433 property int targetY: 0
35−// property real startAngle: 40
3634 property real targetAngle: 0
3735 property int targetHeight: 0
3836 property real targetScale: 0
37+ property real swipeProgress: 0
38+ property real pushProgress: 0
3939
4040 // Config
4141 property real breakPoint: 0.4
4242
43+ // internal
44+ readonly property real progress: {
45+ if (pushProgress > 0) {
46+ // we don't do the full animation when pushing, just a little bit
47+ return MathUtils.linearAnimation(0, 1, 0, breakPoint + .1, pushProgress)
48+ } else {
49+ return swipeProgress;
50+ }
51+ }
52+
4353 // Output
4454
4555 readonly property real scaleToPreviewProgress: {
@@ -68,12 +78,17 @@ QtObject {
6878
6979 readonly property int animatedZ: {
7080 if (progress < breakPoint + (1 - breakPoint) / 2) {
71− return itemIndex == 1 ? normalZ + 2 : normalZ
81+ if (swipeProgress > 0) {
82+ return itemIndex == 1 ? normalZ + 2 : normalZ
83+ }
84+ if (pushProgress > 0) {
85+ return normalZ;
86+ }
7287 }
7388 return itemIndex
7489 }
7590
76− readonly property real opacityMask: itemIndex == 1 ? MathUtils.linearAnimation(0, breakPoint, 0, 1, progress) : 1
91+ readonly property real opacityMask: (swipeProgress > 0 && itemIndex == 1) ? MathUtils.linearAnimation(0, breakPoint, 0, 1, progress) : 1
7792
7893 readonly property real animatedScale: progress < breakPoint ? 1 : MathUtils.linearAnimation(breakPoint, 1, 1, targetScale, progress)
7994
qml/Stage/Stage.qml+66−38
@@ -36,7 +36,6 @@ FocusScope {
3636 property url background
3737 property int dragAreaWidth
3838 property bool interactive
39− property bool keepDashRunning: true
4039 property real nativeHeight
4140 property real nativeWidth
4241 property QtObject orientations
@@ -50,7 +49,6 @@ FocusScope {
5049
5150 // Configuration
5251 property string mode: "staged"
53− property real leftEdgeDragProgress: 0
5452
5553 // Used by the tutorial code
5654 readonly property real rightEdgeDragProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0 // How far left the stage has been dragged
@@ -429,7 +427,7 @@ FocusScope {
429427 // in staged mode, when it switches to Windowed mode it will suddenly
430428 // resume all those apps at once. We might want to avoid that.
431429 value: root.mode === "windowed"
432− || (isDash && root.keepDashRunning)
430+ || isDash
433431 || (!root.suspended && model.application && priv.focusedAppDelegate &&
434432 (priv.focusedAppDelegate.appId === model.application.appId ||
435433 priv.mainStageAppId === model.application.appId ||
@@ -492,7 +490,7 @@ FocusScope {
492490 visible: true
493491 blurRadius: 32
494492 brightness: .65
495− opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.progress, edgeBarrier.progress))
493+ opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, edgeBarrier.progress))
496494 }
497495 },
498496 State {
@@ -726,6 +724,29 @@ FocusScope {
726724 property real restoredX
727725 property real restoredY
728726
727+ // Keeps track of the window geometry while in normal or restored state
728+ // Useful when returning from some maxmized state or when saving the geometry while maximized
729+ // FIXME: find a better solution
730+ property real normalX: 0
731+ property real normalY: 0
732+ property real normalWidth: 0
733+ property real normalHeight: 0
734+ function updateNormalGeometry() {
735+ if (appDelegate.state == "normal" || appDelegate.state == "restored") {
736+ normalX = appDelegate.requestedX;
737+ normalY = appDelegate.requestedY;
738+ normalWidth = appDelegate.width;
739+ normalHeight = appDelegate.height;
740+ }
741+ }
742+ Connections {
743+ target: appDelegate
744+ onXChanged: appDelegate.updateNormalGeometry();
745+ onYChanged: appDelegate.updateNormalGeometry();
746+ onWidthChanged: appDelegate.updateNormalGeometry();
747+ onHeightChanged: appDelegate.updateNormalGeometry();
748+ }
749+
729750 Binding {
730751 target: appDelegate
731752 property: "y"
@@ -866,6 +887,16 @@ FocusScope {
866887 MirFocusController.focusedSurface = null;
867888 }
868889 }
890+
891+ WindowStateSaver {
892+ id: windowStateSaver
893+ target: appDelegate
894+ screenWidth: appContainer.width
895+ screenHeight: appContainer.height
896+ leftMargin: root.leftMargin
897+ minimumY: PanelState.panelHeight
898+ }
899+
869900 Component.onCompleted: {
870901 if (application && application.rotatesWindowContents) {
871902 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -877,7 +908,7 @@ FocusScope {
877908 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
878909 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
879910 // Now load any saved state. This needs to happen *after* the cascading!
880− resizeArea.loadWindowState();
911+ windowStateSaver.load();
881912
882913 // NB: We're differentiating if this delegate was created in response to a new entry in the model
883914 // or if the Repeater is just populating itself with delegates to match the model it received.
@@ -894,6 +925,8 @@ FocusScope {
894925 _constructing = false;
895926 }
896927 Component.onDestruction: {
928+ windowStateSaver.save();
929+
897930 if (!root.parent) {
898931 // This stage is about to be destroyed. Don't mess up with the model at this point
899932 return;
@@ -1075,7 +1108,6 @@ FocusScope {
10751108 sideStageX: sideStage.x
10761109 itemIndex: appDelegate.itemIndex
10771110 nextInStack: priv.nextInStack
1078− leftEdgeDragProgress: root.leftEdgeDragProgress
10791111 }
10801112
10811113 StagedRightEdgeMaths {
@@ -1178,7 +1210,8 @@ FocusScope {
11781210 when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || edgeBarrier.progress > 0)
11791211 PropertyChanges {
11801212 target: windowedRightEdgeMaths
1181− progress: Math.max(rightEdgeDragArea.progress, edgeBarrier.progress)
1213+ swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1214+ pushProgress: edgeBarrier.progress
11821215 }
11831216 PropertyChanges {
11841217 target: appDelegate
@@ -1496,27 +1529,22 @@ FocusScope {
14961529 id: resizeArea
14971530 objectName: "windowResizeArea"
14981531
1532+ anchors.fill: appDelegate
1533+
14991534 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
15001535 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
15011536
15021537 target: appDelegate
1538+ minimumY: PanelState.panelHeight // disallow resizing up past Panel
15031539 minWidth: units.gu(10)
15041540 minHeight: units.gu(10)
15051541 borderThickness: units.gu(2)
1506− windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
1507− screenWidth: appContainer.width
1508− screenHeight: appContainer.height
1509− leftMargin: root.leftMargin
15101542 enabled: false
15111543 visible: enabled
15121544
15131545 onPressed: {
15141546 appDelegate.focus = true;
15151547 }
1516−
1517− Component.onDestruction: {
1518− saveWindowState();
1519− }
15201548 }
15211549
15221550 DecoratedWindow {
@@ -1666,10 +1694,10 @@ FocusScope {
16661694 }
16671695 }
16681696
1669− Image {
1670− id: closeImage
1697+ MouseArea {
1698+ id: closeMouseArea
1699+ objectName: "closeMouseArea"
16711700 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1672− source: "graphics/window-close.svg"
16731701 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
16741702 visible: !appDelegate.isDash && dragArea.distance == 0
16751703 && index == spreadItem.highlightedIndex
@@ -1677,20 +1705,20 @@ FocusScope {
16771705 && mousePos.y > -units.gu(4)
16781706 && mousePos.x > -units.gu(4)
16791707 && mousePos.x < (decoratedWindow.width * 2 / 3)
1680− height: units.gu(2)
1708+ height: units.gu(6)
16811709 width: height
1682− sourceSize.width: width
1683− sourceSize.height: height
1684−
1685− MouseArea {
1686− id: closeMouseArea
1687− objectName: "closeMouseArea"
1688− anchors.fill: closeImage
1689− anchors.margins: -units.gu(2)
1690− onClicked: {
1691− priv.closingIndex = index;
1692− appDelegate.close();
1693− }
1710+
1711+ onClicked: {
1712+ priv.closingIndex = index;
1713+ appDelegate.close();
1714+ }
1715+ Image {
1716+ id: closeImage
1717+ source: "graphics/window-close.svg"
1718+ anchors.fill: closeMouseArea
1719+ anchors.margins: units.gu(2)
1720+ sourceSize.width: width
1721+ sourceSize.height: height
16941722 }
16951723 }
16961724 }
@@ -1749,13 +1777,13 @@ FocusScope {
17491777 }
17501778
17511779 // Find the hovered item and mark it active
1752− var mapped = mapToItem(appContainer, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1753− var itemUnder = appContainer.childAt(mapped.x, mapped.y)
1754− if (itemUnder) {
1755− mapped = mapToItem(itemUnder, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1756− var delegateChild = itemUnder.childAt(mapped.x, mapped.y)
1757− if (delegateChild && (delegateChild.objectName === "dragArea" || delegateChild.objectName === "windowInfoItem")) {
1758− spreadItem.highlightedIndex = appRepeater.indexOf(itemUnder)
1780+ for (var i = appRepeater.count - 1; i >= 0; i--) {
1781+ var appDelegate = appRepeater.itemAt(i);
1782+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1783+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1784+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1785+ spreadItem.highlightedIndex = i;
1786+ break;
17591787 }
17601788 }
17611789
qml/Stage/StageMaths.qml+2−7
@@ -12,7 +12,6 @@ QtObject {
1212 property int sideStageWidth: 0
1313 property int sideStageX: sceneWidth
1414 property bool animateX: false
15− property int leftEdgeDragProgress: 0
1615
1716 property int stage: ApplicationInfoInterface.MainStage
1817 property var thisDelegate: null
@@ -26,10 +25,6 @@ QtObject {
2625 // of the last focused order.
2726 readonly property int itemZ: {
2827 // only shuffle when we've got a main and side stage
29− if (thisDelegate.isDash && thisDelegate != mainStageDelegate && leftEdgeDragProgress > 0) {
30− return -1; // Keep the dash behind all other apps for the left edge gesture
31− }
32−
3328 if (!sideStageDelegate) return itemIndex;
3429
3530 // don't shuffle indexes greater than "actives or next"
@@ -64,12 +59,12 @@ QtObject {
6459
6560 property int itemX: {
6661 if (mainStageDelegate == thisDelegate) {
67− return thisDelegate.isDash ? 0 : leftEdgeDragProgress;
62+ return 0
6863 }
6964 if (sideStageDelegate == thisDelegate) {
7065 return sideStageX;
7166 }
72− return thisDelegate.isDash && leftEdgeDragProgress > 0 ? 0 : sceneWidth;
67+ return sceneWidth;
7368 }
7469 Behavior on itemX { enabled: root.animateX; UbuntuNumberAnimation {} }
7570
qml/Stage/WindowResizeArea.qml+2−70
@@ -18,91 +18,23 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Utils 0.1
2020import Unity.Application 0.1 // for Mir.cursorName
21−import "../Components/PanelState"
2221
2322MouseArea {
2423 id: root
2524
26− anchors.fill: target
2725 anchors.margins: -borderThickness
2826
2927 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
3028
31− property var windowStateStorage: WindowStateStorage
3229 readonly property alias dragging: d.dragging
33− readonly property alias normalWidth: priv.normalWidth
34− readonly property alias normalHeight: priv.normalHeight
3530
3631 // The target item managed by this. Must be a parent or a sibling
3732 // The area will anchor to it and manage resize events
3833 property Item target: null
39− property string windowId: ""
4034 property int borderThickness: 0
35+ property real minimumY: -100000000 // By default, impose no limit
4136 property int minWidth: 0
4237 property int minHeight: 0
43− property int defaultWidth: units.gu(60)
44− property int defaultHeight: units.gu(50)
45− property int screenWidth: 0
46− property int screenHeight: 0
47− property int leftMargin: 0
48−
49− QtObject {
50− id: priv
51− objectName: "priv"
52−
53− property int normalX: 0
54− property int normalY: 0
55− property int normalWidth: 0
56− property int normalHeight: 0
57−
58− function updateNormalGeometry() {
59− if (root.target.state == "normal" || root.target.state == "restored") {
60− normalX = root.target.requestedX
61− normalY = root.target.requestedY
62− normalWidth = root.target.width
63− normalHeight = root.target.height
64− }
65− }
66− }
67−
68− Connections {
69− target: root.target
70− onXChanged: priv.updateNormalGeometry();
71− onYChanged: priv.updateNormalGeometry();
72− onWidthChanged: priv.updateNormalGeometry();
73− onHeightChanged: priv.updateNormalGeometry();
74− }
75−
76− function loadWindowState() {
77− var windowGeometry = windowStateStorage.getGeometry(root.windowId,
78− Qt.rect(target.windowedX, target.windowedY, defaultWidth, defaultHeight));
79−
80−
81− target.windowedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
82− target.windowedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
83− root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
84− target.windowedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.windowedWidth),
85− (target.fullscreen ? 0 : root.leftMargin)); });
86− target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.windowedHeight), PanelState.panelHeight); });
87−
88− var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
89− target.restore(false /* animated */, windowState);
90−
91− priv.updateNormalGeometry();
92−
93− // initialize the x/y to restore to
94− target.restoredX = priv.normalX;
95− target.restoredY = priv.normalY;
96− }
97−
98− function saveWindowState() {
99− var state = target.windowState;
100− if (state === WindowStateStorage.WindowStateRestored) {
101− state = WindowStateStorage.WindowStateNormal;
102− }
103− windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
104− windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
105− }
10638
10739 QtObject {
10840 id: d
@@ -311,7 +243,7 @@ MouseArea {
311243 }
312244
313245 if (d.topBorder) {
314− var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
246+ var newTargetY = Math.max(d.startY + deltaY, root.minimumY);
315247 var bottomBorderY = target.windowedY + target.height;
316248 if (bottomBorderY > newTargetY + d.minimumHeight) {
317249 if (bottomBorderY < newTargetY + d.maximumHeight) {
qml/Stage/WindowStateSaver.qmlnew file+63
@@ -0,0 +1,63 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import Ubuntu.Components 1.3
19+import Utils 0.1
20+
21+QtObject {
22+ id: root
23+
24+ // set from outside
25+ property var target
26+ property int screenWidth: 0
27+ property int screenHeight: 0
28+ property int leftMargin: 0
29+ property int minimumY: 0
30+
31+ function load() {
32+ var defaultWidth = units.gu(60);
33+ var defaultHeight = units.gu(50);
34+ var windowGeometry = WindowStateStorage.getGeometry(target.appId,
35+ Qt.rect(target.windowedX, target.windowedY, defaultWidth, defaultHeight));
36+
37+ target.windowedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, target.minimumWidth), screenWidth - root.leftMargin); });
38+ target.windowedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, target.minimumHeight),
39+ screenHeight - (target.fullscreen ? 0 : minimumY)); });
40+ target.windowedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, screenWidth - root.leftMargin - target.windowedWidth),
41+ (target.fullscreen ? 0 : root.leftMargin)); });
42+ target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
43+
44+ var windowState = WindowStateStorage.getState(target.appId, WindowStateStorage.WindowStateNormal)
45+ target.restore(false /* animated */, windowState);
46+
47+ target.updateNormalGeometry();
48+
49+ // initialize the x/y to restore to
50+ target.restoredX = target.normalX;
51+ target.restoredY = target.normalY;
52+ }
53+
54+ function save() {
55+ var state = target.windowState;
56+ if (state === WindowStateStorage.WindowStateRestored) {
57+ state = WindowStateStorage.WindowStateNormal;
58+ }
59+
60+ WindowStateStorage.saveState(target.appId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
61+ WindowStateStorage.saveGeometry(target.appId, Qt.rect(target.normalX, target.normalY, target.normalWidth, target.normalHeight));
62+ }
63+}
qml/Tutorial/TutorialLeftLong.qml+1−1
@@ -25,7 +25,7 @@ TutorialPage {
2525
2626 // Unlike other tutorials, this one can't be skipped before we show it, so
2727 // only set opacityOverride if we're already shown.
28− opacityOverride: shown ? 1 - launcher.dragDistance / launcher.minimizeDistance : 1
28+ opacityOverride: shown ? 1 - launcher.dragDistance / (launcher.panelWidth * 3) : 1
2929
3030 mouseArea {
3131 anchors.leftMargin: launcher.dragAreaWidth
src/ShellApplication.cpp+1
@@ -36,6 +36,7 @@ ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
3636{
3737
3838 setApplicationName(QStringLiteral("unity8"));
39+ setOrganizationName(QStringLiteral("Canonical"));
3940
4041 connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
4142
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+1−1
@@ -22,6 +22,7 @@ namespace QLightDM
2222
2323SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
2424 : testScenario("singleSession")
25+ , q_ptr(parent)
2526 , m_availableSessions(
2627 {
2728 {"ubuntu", "", "Ubuntu", ""},
@@ -36,7 +37,6 @@ SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
3637 {"xterm", "", "Recovery Console", ""},
3738 {"", "", "Unknown?", ""}
3839 })
39− , q_ptr(parent)
4040{
4141 numSessions = numAvailableSessions();
4242 resetEntries();
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+23−23
@@ -73,7 +73,7 @@ void UsersModelPrivate::resetEntries_single()
7373{
7474 entries =
7575 {
76− { "single", "Single User", 0, 0, false, false, "ubuntu", 0 },
76+ { "single", "Single User", 0, 0, false, false, "ubuntu", 0, 0 },
7777 };
7878}
7979
@@ -81,7 +81,7 @@ void UsersModelPrivate::resetEntries_singlePassphrase()
8181{
8282 entries =
8383 {
84− { "single", "Single User", 0, 0, false, false, "ubuntu", 0 },
84+ { "single", "Single User", 0, 0, false, false, "ubuntu", 0, 0 },
8585 };
8686}
8787
@@ -89,7 +89,7 @@ void UsersModelPrivate::resetEntries_singlePin()
8989{
9090 entries =
9191 {
92− { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0 },
92+ { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0, 0 },
9393 };
9494}
9595
@@ -97,27 +97,27 @@ void UsersModelPrivate::resetEntries_full()
9797{
9898 entries =
9999 {
100− { "has-password", "Has Password", 0, 0, false, false, "ubuntu", 0 },
101− { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0 },
102− { "different-prompt", "Different Prompt", 0, 0, false, false, "ubuntu", 0 },
103− { "no-password", "No Password", 0, 0, false, false, "ubuntu", 0 },
104− { "auth-error", "Auth Error", 0, 0, false, false, "ubuntu", 0 },
105− { "two-factor", "Two Factor", 0, 0, false, false, "ubuntu", 0 },
106− { "info-prompt", "Info Prompt", 0, 0, false, false, "ubuntu", 0 },
107− { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, "ubuntu", 0 },
108− { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, "ubuntu", 0 },
109− { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
110− { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
111− { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0 },
112− { "color-background", "Color Background", "#E95420", 0, false, false, "ubuntu", 0 },
100+ { "has-password", "Has Password", 0, 0, false, false, "ubuntu", 0, 0 },
101+ { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0, 0 },
102+ { "different-prompt", "Different Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
103+ { "no-password", "No Password", 0, 0, false, false, "ubuntu", 0, 0 },
104+ { "auth-error", "Auth Error", 0, 0, false, false, "ubuntu", 0, 0 },
105+ { "two-factor", "Two Factor", 0, 0, false, false, "ubuntu", 0, 0 },
106+ { "info-prompt", "Info Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
107+ { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
108+ { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
109+ { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
110+ { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0, 0 },
111+ { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0, 0 },
112+ { "color-background", "Color Background", "#E95420", 0, false, false, "ubuntu", 0, 0 },
113113 // white and black are a bit redundant, but useful for manually testing if UI is still readable
114− { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
115− { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
116− { "no-background", "No Background", "", 0, false, false, "ubuntu", 0 },
117− { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
118− { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
119− { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
120− { "active", "Active Account", 0, 0, true, false, "ubuntu", 0 },
114+ { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0, 0 },
115+ { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0, 0 },
116+ { "no-background", "No Background", "", 0, false, false, "ubuntu", 0, 0 },
117+ { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0, 0 },
118+ { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0, 0 },
119+ { "empty-name", "", 0, 0, false, false, "ubuntu", 0, 0 },
120+ { "active", "Active Account", 0, 0, true, false, "ubuntu", 0, 0 },
121121 };
122122}
123123
tests/mocks/QtMultimedia/videooutput.h+1−1
@@ -31,7 +31,7 @@ public:
3131 QObject *source() const { return m_source.data(); }
3232 void setSource(QObject *source);
3333
34− void itemChange(ItemChange change, const ItemChangeData & value);
34+ void itemChange(ItemChange change, const ItemChangeData & value) override;
3535
3636Q_SIGNALS:
3737 void sourceChanged();
tests/mocks/Unity/Launcher/CMakeLists.txt+5−1
@@ -1,7 +1,8 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
22
33include_directories(
44 ${CMAKE_CURRENT_SOURCE_DIR}
5+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/
56)
67
78set(MockLauncherModel_SOURCES
@@ -9,10 +10,13 @@ set(MockLauncherModel_SOURCES
910 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
1011 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
1112 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
13+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
1214 plugin.cpp
1315 MockLauncherModel.cpp
1416 MockLauncherItem.cpp
1517 MockQuickListModel.cpp
18+ MockAppDrawerModel.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
1620)
1721
1822add_library(MockLauncherPlugin MODULE ${MockLauncherModel_SOURCES})
tests/mocks/Unity/Launcher/MockAppDrawerModel.cppnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2016 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 "MockAppDrawerModel.h"
18+
19+#include <QDebug>
20+#include <QDateTime>
21+
22+MockAppDrawerModel::MockAppDrawerModel(QObject *parent):
23+ AppDrawerModelInterface(parent)
24+{
25+ MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
26+ m_list.append(item);
27+ item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
28+ m_list.append(item);
29+ item = new MockLauncherItem("camera-app2", "/usr/share/applications/camera-app2.desktop", "Camera2", "camera", this);
30+ m_list.append(item);
31+ item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
32+ m_list.append(item);
33+ item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
34+ m_list.append(item);
35+ item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
36+ m_list.append(item);
37+ item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
38+ m_list.append(item);
39+ item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
40+ m_list.append(item);
41+ item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
42+ m_list.append(item);
43+ item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
44+ m_list.append(item);
45+ item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
46+ m_list.append(item);
47+ item = new MockLauncherItem("calendar-app", "/usr/share/applications/calendar-app.desktop","Calendar", "calendar", this);
48+ m_list.append(item);
49+ item = new MockLauncherItem("libreoffice", "/usr/share/applications/libreoffice.desktop","Libre Office", "libreoffice", this);
50+ m_list.append(item);
51+ qDebug() << "mock model created";
52+
53+ qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
54+}
55+
56+int MockAppDrawerModel::rowCount(const QModelIndex &parent) const
57+{
58+ return m_list.count();
59+}
60+
61+QVariant MockAppDrawerModel::data(const QModelIndex &index, int role) const
62+{
63+ switch (role) {
64+ case RoleAppId:
65+ return m_list.at(index.row())->appId();
66+ case RoleName:
67+ return m_list.at(index.row())->name();
68+ case RoleIcon:
69+ return m_list.at(index.row())->icon();
70+ case RoleUsage:
71+ return qrand();
72+ }
73+
74+ return QVariant();
75+}
tests/mocks/Unity/Launcher/MockAppDrawerModel.hnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2016 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 <unity/shell/launcher/AppDrawerModelInterface.h>
18+
19+#include "MockLauncherItem.h"
20+
21+class MockAppDrawerModel: public AppDrawerModelInterface
22+{
23+ Q_OBJECT
24+public:
25+ MockAppDrawerModel(QObject* parent = nullptr);
26+
27+ int rowCount(const QModelIndex &parent) const override;
28+ QVariant data(const QModelIndex &index, int role) const override;
29+
30+private:
31+ QList<MockLauncherItem*> m_list;
32+};
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+13
@@ -67,6 +67,11 @@ QString MockLauncherItem::icon() const
6767 return m_icon;
6868}
6969
70+QStringList MockLauncherItem::keywords() const
71+{
72+ return m_keywords;
73+}
74+
7075bool MockLauncherItem::pinned() const
7176{
7277 return m_pinned;
@@ -200,3 +205,11 @@ unity::shell::launcher::QuickListModelInterface *MockLauncherItem::quickList() c
200205{
201206 return m_quickList;
202207}
208+
209+void MockLauncherItem::setKeywords(const QStringList &keywords)
210+{
211+ if (m_keywords != keywords) {
212+ m_keywords = keywords;
213+ Q_EMIT keywordsChanged(keywords);
214+ }
215+}
tests/mocks/Unity/Launcher/MockLauncherItem.h+6
@@ -22,6 +22,8 @@
2222
2323#include <unity/shell/launcher/LauncherItemInterface.h>
2424
25+#include <QStringList>
26+
2527class MockQuickListModel;
2628
2729using namespace unity::shell::launcher;
@@ -37,6 +39,7 @@ public:
3739 QString desktopFile() const;
3840 QString name() const override;
3941 QString icon() const override;
42+ QStringList keywords() const override;
4043
4144 bool pinned() const override;
4245 bool running() const override;
@@ -51,6 +54,7 @@ public:
5154 unity::shell::launcher::QuickListModelInterface *quickList() const override;
5255
5356private:
57+ void setKeywords(const QStringList &keywords);
5458 void setPinned(bool pinned);
5559 void setRunning(bool running);
5660 void setRecent(bool recent);
@@ -65,6 +69,7 @@ private:
6569 QString m_desktopFile;
6670 QString m_name;
6771 QString m_icon;
72+ QStringList m_keywords;
6873 bool m_pinned;
6974 bool m_running;
7075 bool m_recent;
@@ -77,6 +82,7 @@ private:
7782 MockQuickListModel *m_quickList;
7883
7984 friend class MockLauncherModel;
85+ friend class MockAppDrawerModel;
8086};
8187
8288#endif // MOCKLAUNCHERITEM_H
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3
@@ -36,6 +36,9 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3636 item = new MockLauncherItem("camera-app2", "/usr/share/applications/camera-app2.desktop", "Camera2", "camera", this);
3737 item->setPinned(true);
3838 m_list.append(item);
39+ item = new MockLauncherItem("camera-app3", "/usr/share/applications/camera-app2.desktop", "Camera2", "camera", this);
40+ item->setPinned(true);
41+ m_list.append(item);
3942 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
4043 item->setProgress(50);
4144 item->setCountVisible(true);
tests/mocks/Unity/Launcher/plugin.cpp+4
@@ -21,9 +21,11 @@
2121#include "MockLauncherModel.h"
2222#include "MockLauncherItem.h"
2323#include "MockQuickListModel.h"
24+#include "MockAppDrawerModel.h"
2425
2526#include <unity/shell/launcher/LauncherModelInterface.h>
2627#include <unity/shell/launcher/LauncherItemInterface.h>
28+#include <unity/shell/launcher/AppDrawerModelInterface.h>
2729
2830#include <QtQml/qqml.h>
2931
@@ -41,8 +43,10 @@ void TestLauncherPlugin::registerTypes(const char* uri)
4143 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
4244 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
4345 qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListModelInterface", "Abstract Interface. Cannot be instantiated.");
46+ qmlRegisterUncreatableType<AppDrawerModelInterface>(uri, 0, 1, "AppDrawerModelInterface", "Abstract Interface. Cannot be instantiated.");
4447
4548 qmlRegisterSingletonType<MockLauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
4649 qmlRegisterUncreatableType<MockLauncherItem>(uri, 0, 1, "LauncherItem", "Can't create LauncherItems in QML. Get them from the LauncherModel");
4750 qmlRegisterUncreatableType<MockQuickListModel>(uri, 0, 1, "QuickListModel", "Can't create QuickLists in QML. Get them from the LauncherItems");
51+ qmlRegisterType<MockAppDrawerModel>(uri, 0, 1, "AppDrawerModel");
4852}
tests/mocks/Utils/CMakeLists.txt+4
@@ -24,11 +24,15 @@ set(QMLPLUGIN_SRC
2424 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
2525 ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
2626 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
27+ ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
2728 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
2829 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
2930 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
3031 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
3132 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
33+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
34+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
35+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
3236 constants.cpp
3337 plugin.cpp
3438 windowstatestorage.cpp
tests/mocks/Utils/plugin.cpp+2
@@ -40,6 +40,7 @@
4040#include <inputeventgenerator.h>
4141#include <deviceconfigparser.h>
4242#include <globalfunctions.h>
43+#include <appdrawerproxymodel.h>
4344
4445static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4546{
@@ -82,4 +83,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
8283 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8384 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
8485 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86+ qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
8587}
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
22
33include_directories(
44 ${CMAKE_CURRENT_SOURCE_DIR}
tests/plugins/Unity/Launcher/CMakeLists.txt+2−1
@@ -1,5 +1,5 @@
11pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
33
44include_directories(
55 ${CMAKE_CURRENT_SOURCE_DIR}
@@ -30,6 +30,7 @@ add_executable(launchermodeltestExec
3030 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
3131 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
3232 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistentry.cpp
33+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/ualwrapper.cpp
3334 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
3435 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
3536 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
tests/plugins/Utils/WindowInputMonitorTest.cpp+76−9
@@ -25,14 +25,15 @@ class FakeElapsedTimer : public AbstractElapsedTimer {
2525public:
2626 static qint64 msecsSinceEpoch;
2727
28− FakeElapsedTimer() { start(); }
28+ FakeElapsedTimer() {}
2929
30− void start() override { m_msecsSinceReference = msecsSinceEpoch; }
30+ void start() override { m_msecsSinceReference = msecsSinceEpoch; m_valid = true; }
3131 qint64 msecsSinceReference() const override { return m_msecsSinceReference; }
32− qint64 elapsed() const override { return msecsSinceEpoch - m_msecsSinceReference; }
32+ qint64 elapsed() const override { return m_valid ? msecsSinceEpoch - m_msecsSinceReference : qrand(); }
3333
3434private:
3535 qint64 m_msecsSinceReference;
36+ bool m_valid{false};
3637};
3738qint64 FakeElapsedTimer::msecsSinceEpoch = 0;
3839
@@ -48,11 +49,14 @@ public:
4849 int interval() const override;
4950 void setInterval(int msecs) override;
5051 void start() override;
52+ void stop() override;
53+ bool isRunning() const override;
5154 bool isSingleShot() const override;
5255 void setSingleShot(bool value) override;
5356private:
5457 int m_interval;
5558 bool m_singleShot;
59+ bool m_isRunning;
5660 qint64 m_nextTimeoutTime;
5761};
5862
@@ -85,6 +89,8 @@ private Q_SLOTS:
8589 void tapWhileTouching();
8690 void multipleHomeKeys();
8791
92+ void repeatedSuperPress();
93+
8894private:
8995 void passTime(qint64 timeSpanMs);
9096
@@ -98,11 +104,10 @@ void WindowInputMonitorTest::init()
98104
99105void WindowInputMonitorTest::cleanup()
100106{
101− delete m_fakeTimerFactory;
107+ delete m_fakeTimerFactory;
102108 m_fakeTimerFactory = nullptr;
103109}
104110
105−
106111void WindowInputMonitorTest::passTime(qint64 timeSpanMs)
107112{
108113 qint64 finalTime = FakeElapsedTimer::msecsSinceEpoch + timeSpanMs;
@@ -133,7 +138,7 @@ void WindowInputMonitorTest::touchTapTouch()
133138 QFETCH(int, silenceAfterTap);
134139 QFETCH(int, expectedActivatedCount);
135140 QFETCH(Qt::Key, key);
136− WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(), new FakeElapsedTimer);
141+ WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
137142 QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
138143 QVERIFY(activatedSpy.isValid());
139144
@@ -194,7 +199,7 @@ void WindowInputMonitorTest::touchTapTouch()
194199
195200void WindowInputMonitorTest::tapWhileTouching()
196201{
197− WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(), new FakeElapsedTimer);
202+ WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
198203 QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
199204 QVERIFY(activatedSpy.isValid());
200205
@@ -229,7 +234,7 @@ void WindowInputMonitorTest::tapWhileTouching()
229234 */
230235void WindowInputMonitorTest::multipleHomeKeys()
231236{
232− WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(), new FakeElapsedTimer);
237+ WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
233238 QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
234239 QVERIFY(activatedSpy.isValid());
235240
@@ -258,12 +263,64 @@ void WindowInputMonitorTest::multipleHomeKeys()
258263 QCOMPARE(activatedSpy.count(), 1);
259264}
260265
266+// regression test for lp:1607427
267+void WindowInputMonitorTest::repeatedSuperPress()
268+{
269+ WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
270+ QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
271+ QVERIFY(activatedSpy.isValid());
272+
273+ // 1st try
274+ passTime(1000);
275+ {
276+ QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
277+ homeKeyWatcher.update(&keyEvent);
278+ }
279+ passTime(50);
280+ {
281+ QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
282+ homeKeyWatcher.update(&keyEvent);
283+ }
284+ passTime(1000);
285+ QCOMPARE(activatedSpy.count(), 1);
286+
287+ // a touch event in between
288+ {
289+ QTouchEvent touchEvent(QEvent::TouchBegin);
290+ homeKeyWatcher.update(&touchEvent);
291+ }
292+ {
293+ QTouchEvent touchEvent(QEvent::TouchUpdate);
294+ homeKeyWatcher.update(&touchEvent);
295+ }
296+ {
297+ QTouchEvent touchEvent(QEvent::TouchEnd);
298+ homeKeyWatcher.update(&touchEvent);
299+ }
300+
301+ passTime(1000);
302+ // 2nd try
303+ activatedSpy.clear();
304+ {
305+ QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
306+ homeKeyWatcher.update(&keyEvent);
307+ }
308+ passTime(50);
309+ {
310+ QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
311+ homeKeyWatcher.update(&keyEvent);
312+ }
313+ passTime(1000);
314+ QCOMPARE(activatedSpy.count(), 1);
315+}
316+
261317/////////////////////////////////// FakeTimer //////////////////////////////////
262318
263319FakeTimer::FakeTimer(QObject *parent)
264320 : UnityUtil::AbstractTimer(parent)
265321 , m_interval(0)
266322 , m_singleShot(true)
323+ , m_isRunning(false)
267324{
268325}
269326
@@ -285,10 +342,20 @@ void FakeTimer::update()
285342
286343void FakeTimer::start()
287344{
288− AbstractTimer::start();
345+ m_isRunning = true;
289346 m_nextTimeoutTime = FakeElapsedTimer::msecsSinceEpoch + (qint64)interval();
290347}
291348
349+void FakeTimer::stop()
350+{
351+ m_isRunning = false;
352+}
353+
354+bool FakeTimer::isRunning() const
355+{
356+ return m_isRunning;
357+}
358+
292359int FakeTimer::interval() const
293360{
294361 return m_interval;
tests/qmltests/CMakeLists.txt+1
@@ -80,6 +80,7 @@ add_unity8_qmltest(Greeter NarrowView)
8080add_unity8_qmltest(Greeter WideView)
8181add_unity8_qmltest_data(Greeter TestView)
8282add_unity8_qmltest(Launcher Launcher)
83+add_unity8_qmltest(Launcher Drawer)
8384add_unity8_qmltest(Notifications Notifications)
8485add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
8586add_unity8_qmltest(Notifications OptionToggle)
tests/qmltests/Components/tst_VirtualTouchPad.qml+55−1
@@ -21,14 +21,20 @@ import UInput 0.1
2121import "../../../qml/Components"
2222
2323
24−Item {
24+Rectangle {
2525 id: root
2626 width: units.gu(70)
2727 height: units.gu(70)
28+ color: "black"
2829
2930 VirtualTouchPad {
3031 id: touchScreenPad
3132 anchors.fill: parent
33+ property int internalGu: units.gu(1)
34+ settings: QtObject {
35+ property bool touchpadTutorialHasRun: true
36+ property bool oskEnabled: true
37+ }
3238 }
3339
3440 SignalSpy {
@@ -139,5 +145,53 @@ Item {
139145 event.release(1, startX2, startY + units.gu(11));
140146 event.commit();
141147 }
148+
149+ function test_tutorial() {
150+ var oskButton = findChild(touchScreenPad, "oskButton");
151+ var leftButton = findChild(touchScreenPad, "leftButton");
152+ var rightButton = findChild(touchScreenPad, "rightButton");
153+ var touchPad = findChild(touchScreenPad, "virtualTouchPad")
154+ var tutorial = findInvisibleChild(touchScreenPad, "tutorialAnimation")
155+ var tutorialFinger1 = findChild(touchScreenPad, "tutorialFinger1");
156+ var tutorialFinger2 = findChild(touchScreenPad, "tutorialFinger2");
157+ var tutorialLabel = findChild(touchScreenPad, "tutorialLabel");
158+ var tutorialImage = findChild(touchScreenPad, "tutorialImage");
159+
160+ mouseEventSpy1.signalName = "mousePressed"
161+ mouseEventSpy2.signalName = "mouseReleased"
162+
163+ mouseEventSpy1.clear();
164+ mouseEventSpy2.clear();
165+
166+ // run the tutorial
167+ touchScreenPad.runTutorial();
168+ tryCompare(tutorial, "running", true)
169+
170+ // Wait for it to pause
171+ tryCompare(tutorial, "paused", true)
172+
173+ // Click somewhere to make it continue
174+ mouseClick(root, root.width / 2, root.height /2)
175+ tryCompare(tutorial, "paused", false)
176+
177+ // Verify that the touchpad does NOT accept input while the tutorial is running
178+ mouseClick(root, root.width / 2, root.height /2)
179+ compare(mouseEventSpy1.count, 0)
180+ compare(mouseEventSpy2.count, 0)
181+
182+ // Wait for it to finish
183+ tryCompare(tutorial, "running", false, 60000)
184+
185+ // Make sure after the tutorial, all the visible states are proper
186+ tryCompare(oskButton, "visible", true)
187+ tryCompare(leftButton, "visible", true)
188+ tryCompare(rightButton, "visible", true)
189+
190+ tryCompare(tutorialFinger1, "visible", false)
191+ tryCompare(tutorialFinger2, "visible", false)
192+ tryCompare(tutorialImage, "visible", false)
193+ tryCompare(tutorialLabel, "visible", false)
194+ }
195+
142196 }
143197}
tests/qmltests/Dash/Previews/tst_Preview.qml+63
@@ -37,6 +37,51 @@ Rectangle {
3737 id: mockPreviewModel
3838 }
3939
40+ ListModel {
41+ id: tracksModel
42+ Component.onCompleted:
43+ {
44+ tracksModel.append( { "type" : "audio1",
45+ "widgetId" : "audiopw1",
46+ "properties" : { "tracks" : [
47+ { title: "Some track name", length: "30", source: "/not/existinga/path/testsound1" }
48+ ]
49+ }
50+ }
51+ );
52+ tracksModel.append(
53+ { "type" : "audio",
54+ "widgetId" : "audiopw",
55+ "properties" : { "tracks" : [
56+ { title: "Some track name", length: "30", source: "/not/existing/path/testsound1" },
57+ { title: "Some other track name", subtitle: "Subtitle", length: "83", source: "/not/existing/path/testsound2" },
58+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound3" },
59+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound4" },
60+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound5" },
61+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound6" },
62+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound7" },
63+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound8" },
64+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound9" },
65+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound10" },
66+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound11" },
67+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound12" },
68+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound13" },
69+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound14" },
70+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound15" },
71+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound16" },
72+ { title: "And another one", length: "7425", source: "/not/existing/path/testsound17" }
73+ ]
74+ }
75+ } );
76+ }
77+ }
78+
79+ QtObject {
80+ id: audiosModel
81+ property int widgetColumnCount: 1
82+ property var columnModel: { tracksModel }
83+ }
84+
4085 Preview {
4186 id: preview
4287 anchors.fill: parent
@@ -57,6 +102,7 @@ Rectangle {
57102 function init() {
58103 var widget = findChild(preview, "previewListRow0");
59104 widget.positionViewAtBeginning();
105+ preview.previewModel = mockPreviewModel;
60106 }
61107
62108 function test_triggered() {
@@ -147,5 +193,22 @@ Rectangle {
147193 skip("preview %1 or widget %2 have been destroyed, thus we can't safely continue this test".arg(preview_str).arg(widget_str))
148194 }
149195 }
196+
197+ function test_audios() {
198+ preview.previewModel = audiosModel;
199+ waitForRendering(preview);
200+
201+ // Scroll down
202+ var previewListRow0 = findChild(preview, "previewListRow0");
203+ flickToYEnd(previewListRow0);
204+
205+ var previewsContentY = previewListRow0.contentY;
206+
207+ var trackItem = findChild(preview, "trackItem16");
208+ mouseClick(findChild(trackItem, "playButton"));
209+
210+ expectFail("", "Clicking on a track should not change contentY.");
211+ tryCompareFunction(function () { return previewsContentY != previewListRow0.contentY; }, true);
212+ }
150213 }
151214}
tests/qmltests/Dash/tst_Dash.qml+4−3
@@ -310,8 +310,9 @@ Item {
310310 var stopX = units.gu(1)
311311 var stopY = startY;
312312 waitForRendering(dashContentList)
313− mouseFlick(dash, startX, startY, stopX, stopY);
314− mouseFlick(dash, startX, startY, stopX, stopY);
313+ mouseFlick(dash, startX, startY, stopX, stopY, true, true, units.gu(200));
314+ compare(dashContentList.currentIndex, 1, "Could not flick to scope id 1");
315+ mouseFlick(dash, startX, startY, stopX, stopY, units.gu(200));
315316 compare(dashContentList.currentIndex, 2, "Could not flick to scope id 2");
316317 var dashCommunicatorService = findInvisibleChild(dash, "dashCommunicatorService");
317318 dashCommunicatorService.mockSetCurrentScope(0, true, false);
@@ -411,7 +412,7 @@ Item {
411412 compare(dashContentList.currentItem.scopeId, "clickscope");
412413
413414 // Move to second scope
414− mouseFlick(dash, dash.width / 2, units.gu(2), dash.width / 5, units.gu(2));
415+ mouseFlick(dash, dash.width / 2, units.gu(2), dash.width / 5, units.gu(2), true, true, units.gu(20));
415416 tryCompare(dashContentList, "currentIndex", 1);
416417 compare(dashContentList.currentItem.scopeId, "MockScope1");
417418 }
tests/qmltests/Dash/tst_DashShell.qml−29
@@ -112,34 +112,5 @@ Item {
112112 mouseClick(buttonShowDashHome);
113113 tryCompare(dashContentList, "currentIndex", 0);
114114 }
115−
116− function test_setShortLauncherMoveResetNonActiveDash() {
117− var dashContentList = findChild(dash, "dashContentList");
118− var startX = dash.width - units.gu(1);
119− var startY = dash.height / 2;
120− var stopX = units.gu(1)
121− var stopY = startY;
122− waitForRendering(dashContentList);
123− touchFlick(dash, startX, startY, stopX, stopY);
124− touchFlick(dash, startX, startY, stopX, stopY);
125− compare(dashContentList.currentIndex, 2, "Could not flick to scope id 2");
126−
127− // Pretend the dash is not active
128− dash.windowActive = false;
129−
130− // Flick the greeter away
131− var startX = shell.width - units.gu(1);
132− var startY = shell.height / 2;
133− var stopX = units.gu(1)
134− var stopY = startY;
135− touchFlick(shell, startX, startY, stopX, stopY);
136−
137− var greeter = findChild(shell, "greeter");
138− tryCompare(greeter, "shown", false);
139−
140− // Now do a small launcher movement
141− touchFlick(shell, stopX, startY, startX, stopY);
142− compare(dashContentList.currentIndex, 0, "Small launcher move did not reset dash on non active window");
143− }
144115 }
145116}
tests/qmltests/Launcher/tst_Drawer.qmlnew file+264
@@ -0,0 +1,264 @@
1+/*
2+ * Copyright 2013-2016 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.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import ".."
21+import "../../../qml/Launcher"
22+import Unity.Launcher 0.1
23+import Utils 0.1 // For EdgeBarrierSettings
24+import Unity.Test 0.1
25+
26+StyledItem {
27+ id: root
28+ theme.name: "Ubuntu.Components.Themes.SuruDark"
29+
30+ width: units.gu(140)
31+ height: units.gu(70)
32+ Rectangle {
33+ anchors.fill: parent
34+ color: UbuntuColors.graphite // something neither white nor black
35+ }
36+
37+ Launcher {
38+ id: launcher
39+ x: 0
40+ y: 0
41+ width: units.gu(40)
42+ height: root.height
43+
44+ lockedVisible: lockedVisibleCheckBox.checked
45+
46+ property string lastSelectedApplication
47+ onLauncherApplicationSelected: {
48+ lastSelectedApplication = appId
49+ }
50+
51+ Component.onCompleted: {
52+ launcher.focus = true
53+ edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
54+ }
55+ }
56+
57+ ColumnLayout {
58+ anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
59+ spacing: units.gu(1)
60+ width: childrenRect.width
61+
62+ RowLayout {
63+ CheckBox {
64+ id: lockedVisibleCheckBox
65+ checked: false
66+ }
67+ Label {
68+ text: "Launcher always visible"
69+ }
70+ }
71+
72+ Slider {
73+ id: widthSlider
74+ Layout.fillWidth: true
75+ minimumValue: 6
76+ maximumValue: 12
77+ value: 10
78+ }
79+
80+ MouseTouchEmulationCheckbox {}
81+
82+ EdgeBarrierControls {
83+ id: edgeBarrierControls
84+ text: "Drag here to pull out launcher"
85+ onDragged: { launcher.pushEdge(amount); }
86+ }
87+ }
88+
89+ UnityTestCase {
90+ id: testCase
91+ when: windowShown
92+ name: "Drawer"
93+
94+ function dragDrawerIntoView() {
95+ var startX = launcher.dragAreaWidth/2;
96+ var startY = launcher.height/2;
97+ touchFlick(launcher,
98+ startX, startY,
99+ startX+units.gu(35), startY);
100+
101+ var drawer = findChild(launcher, "drawer");
102+ verify(!!drawer);
103+
104+ // wait until it gets fully extended
105+ // a tryCompare doesn't work since
106+ // compare(-0.000005917593600024418, 0);
107+ // is true and in this case we want exactly 0 or will have pain later on
108+ tryCompareFunction( function(){ return drawer.x === 0; }, true );
109+ tryCompare(launcher, "state", "drawer");
110+ tryCompare(launcher, "drawerShown", true);
111+ }
112+
113+ function revealByEdgePush() {
114+ // Place the mouse against the window/screen edge and push beyond the barrier threshold
115+ // If the mouse did not move between two revealByEdgePush(), we need it to move out the
116+ // area to make sure we're not just accumulating to the previous push
117+ mouseMove(root, root.width, root.height / 2);
118+ mouseMove(root, 1, root.height / 2);
119+
120+ launcher.pushEdge(EdgeBarrierSettings.pushThreshold * 1.1);
121+
122+ var drawer = findChild(launcher, "drawer");
123+ verify(!!drawer);
124+
125+ // wait until it gets fully extended
126+ tryCompare(drawer, "x", 0);
127+ tryCompare(launcher, "state", "drawer");
128+ tryCompare(launcher, "drawerShown", true);
129+ }
130+
131+ function init() {
132+ launcher.lastSelectedApplication = "";
133+ launcher.lockedVisible = false;
134+ launcher.hide();
135+ var drawer = findChild(launcher, "drawer");
136+ tryCompare(drawer, "x", -drawer.width);
137+ }
138+
139+ function test_revealByEdgeDrag() {
140+ dragDrawerIntoView();
141+ }
142+
143+ function test_revealByEdgePush_data() {
144+ return [
145+ { tag: "autohide launcher", autohide: true },
146+ { tag: "locked launcher", autohide: false }
147+ ]
148+ }
149+
150+ function test_revealByEdgePush(data) {
151+ launcher.lockedVisible = !data.autohide;
152+
153+ var panel = findChild(launcher, "launcherPanel");
154+ tryCompare(panel, "x", data.autohide ? -panel.width : 0);
155+ tryCompare(launcher, "state", data.autohide ? "" : "visible");
156+ waitForRendering(launcher)
157+
158+ revealByEdgePush();
159+ }
160+
161+ function test_hideByDraggingDrawer_data() {
162+ return [
163+ {tag: "autohide", autohide: true, endState: ""},
164+ {tag: "locked", autohide: false, endState: "visible"}
165+ ]
166+ }
167+
168+ function test_hideByDraggingDrawer(data) {
169+ launcher.lockedVisible = !data.autohide;
170+
171+ dragDrawerIntoView();
172+ waitForRendering(launcher);
173+
174+ var drawer = findChild(launcher, "drawer");
175+
176+ mouseFlick(root, drawer.width - units.gu(1), drawer.height / 2, units.gu(2), drawer.height / 2, true, true);
177+
178+ tryCompare(drawer.anchors, "rightMargin", 0);
179+ tryCompare(launcher, "state", data.endState);
180+ launcher.lockedVisible = false;
181+ }
182+
183+ function test_hideByClickingOutside() {
184+ dragDrawerIntoView();
185+
186+ var drawer = findChild(launcher, "drawer");
187+
188+ mouseClick(root, drawer.width + units.gu(1), root.height / 2);
189+
190+ tryCompare(launcher, "state", "");
191+ }
192+
193+ function test_launchAppFromDrawer() {
194+ dragDrawerIntoView();
195+
196+ var drawerList = findChild(launcher, "drawerItemList");
197+ var dialerApp = findChild(drawerList, "drawerItem_dialer-app");
198+ mouseClick(dialerApp, dialerApp.width / 2, dialerApp.height / 2);
199+
200+ tryCompare(launcher, "lastSelectedApplication", "dialer-app");
201+
202+ tryCompare(launcher, "state", "");
203+ }
204+
205+ function test_launcherGivesUpFocusAfterLaunchingFromDrawer() {
206+ dragDrawerIntoView();
207+
208+ tryCompare(launcher, "focus", true);
209+
210+ var drawerList = findChild(launcher, "drawerItemList");
211+ var dialerApp = findChild(drawerList, "drawerItem_dialer-app");
212+ mouseClick(dialerApp, dialerApp.width / 2, dialerApp.height / 2);
213+
214+ tryCompare(launcher, "focus", false);
215+ }
216+
217+ function test_drawerDisabled() {
218+ launcher.drawerEnabled = false;
219+
220+ var startX = launcher.dragAreaWidth/2;
221+ var startY = launcher.height/2;
222+ touchFlick(launcher,
223+ startX, startY,
224+ startX+units.gu(35), startY);
225+
226+ var drawer = findChild(launcher, "drawer");
227+ verify(!!drawer);
228+
229+ tryCompare(launcher, "state", "visible");
230+ tryCompare(launcher, "drawerShown", false);
231+
232+ launcher.drawerEnabled = true;
233+ }
234+
235+ function test_search() {
236+ compare(launcher.lastSelectedApplication, "");
237+
238+ launcher.openDrawer(true)
239+ typeString("cam");
240+ keyClick(Qt.Key_Enter);
241+
242+ compare(launcher.lastSelectedApplication, "camera-app");
243+ }
244+
245+ function test_dragDirectionOnLeftEdgeDrag_data() {
246+ return [
247+ { tag: "reveal", direction: "right", endState: "drawer" },
248+ { tag: "cancel", direction: "left", endState: "visible" },
249+ ]
250+ }
251+
252+ function test_dragDirectionOnLeftEdgeDrag(data) {
253+ var startX = launcher.dragAreaWidth/2;
254+ var startY = launcher.height/2;
255+ var stopX = startX + units.gu(35);
256+ var endX = stopX + (units.gu(4) * (data.direction === "left" ? -1 : 1))
257+
258+ touchFlick(launcher, startX, startY, stopX, startY, true, false);
259+ touchFlick(launcher, stopX, startY, endX, startY, false, true);
260+
261+ tryCompare(launcher, "state", data.endState)
262+ }
263+ }
264+}
tests/qmltests/Launcher/tst_Launcher.qml+4−4
@@ -322,7 +322,7 @@ Rectangle {
322322 function revealByEdgePush() {
323323 // Place the mouse against the window/screen edge and push beyond the barrier threshold
324324 mouseMove(root, 1, root.height / 2);
325− launcher.pushEdge(EdgeBarrierSettings.pushThreshold * 1.1);
325+ launcher.pushEdge(EdgeBarrierSettings.pushThreshold * .6);
326326
327327 var panel = findChild(launcher, "launcherPanel");
328328 verify(!!panel);
@@ -824,7 +824,7 @@ Rectangle {
824824
825825 // Dragging a bit (> 1.5 gu)
826826 newMouseX -= units.gu(2)
827− mouseFlick(launcher, currentMouseX, currentMouseY, newMouseX, newMouseY, false, false, 100)
827+ mouseFlick(launcher, currentMouseX, currentMouseY, newMouseX, newMouseY, false, false)
828828 currentMouseX = newMouseX
829829
830830 // Other items need to expand and become 0.6 opaque
@@ -832,8 +832,8 @@ Rectangle {
832832 tryCompare(item0, "itemOpacity", 0.6)
833833
834834 // Dragging a bit more
835− newMouseY += initialItemHeight * 1.5
836− mouseFlick(launcher, currentMouseX, currentMouseY, newMouseX, newMouseY, false, false, 100)
835+ newMouseY += initialItemHeight * 1.25
836+ mouseFlick(launcher, currentMouseX, currentMouseY, newMouseX, newMouseY, false, false)
837837 currentMouseY = newMouseY
838838
839839 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
tests/qmltests/Panel/Indicators/tst_IndicatorMenuItemFactory.qml+6−1
@@ -370,7 +370,8 @@ Item {
370370 function test_create_switchMenu_data() {
371371 return [
372372 {label: "testLabel1", enabled: true, checked: false, icon: "file:///testIcon1" },
373− {label: "testLabel2", enabled: false, checked: true, icon: "file:///testIcon2" },
373+ {label: "testLabel2", enabled: false, checked: true, icon: "file:///testIcon2", subtitleAction: "action::subtitle1" },
374+ {label: "testLabel2", enabled: false, checked: true, icon: "file:///testIcon3", subtitleAction: "action::subtitle2" },
374375 ];
375376 }
376377
@@ -380,6 +381,9 @@ Item {
380381 menuData.sensitive = data.enabled;
381382 menuData.icon = data.icon;
382383 menuData.isToggled = data.checked;
384+ menuData.ext = {
385+ 'xCanonicalSubtitleAction': data.subtitleAction,
386+ };
383387
384388 loader.data = menuData;
385389 loader.sourceComponent = factory.load(menuData);
@@ -390,6 +394,7 @@ Item {
390394 compare(loader.item.iconSource, data.icon, "Icon does not match data");
391395 compare(loader.item.checked, data.checked, "Checked does not match data");
392396 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
397+ compare(loader.item.subtitleAction.name, data.subtitleAction === undefined ? "" : data.subtitleAction, "Subtitle action does not match data");
393398 }
394399
395400 function test_create_alarmMenu_data() {
tests/qmltests/Stage/tst_ApplicationWindow.qml+2−20
@@ -53,6 +53,8 @@ Rectangle {
5353 surfaceOrientationAngle: 0
5454 interactive: true
5555 focus: true
56+ requestedWidth: width
57+ requestedHeight: height
5658 Component.onDestruction: {
5759 applicationWindowLoader.itemDestroyed = true;
5860 }
@@ -150,26 +152,6 @@ Rectangle {
150152 }
151153 }
152154 }
153−
154− Button {
155− anchors { left: parent.left; right: parent.right }
156− activeFocusOnPress: false
157− text: "Rotate device \u27F3"
158− onClicked: {
159− var orientation = applicationWindowLoader.item.orientation
160− if (orientation == Qt.PortraitOrientation) {
161− orientation = Qt.LandscapeOrientation;
162− } else if (orientation == Qt.LandscapeOrientation) {
163− orientation = Qt.InvertedPortraitOrientation;
164− } else if (orientation == Qt.InvertedPortraitOrientation) {
165− orientation = Qt.InvertedLandscapeOrientation;
166− } else {
167− orientation = Qt.PortraitOrientation;
168− }
169− applicationWindowLoader.item.orientation = orientation;
170− }
171− }
172−
173155 }
174156 }
175157
tests/qmltests/Stage/tst_DesktopStage.qml+71
@@ -751,6 +751,77 @@ Item {
751751 tryCompare(dialerAppDelegate, "state", "maximized");
752752 }
753753
754+ function test_saveRestoreSize() {
755+ var originalWindowCount = topSurfaceList.count;
756+ var appDelegate = startApplication("dialer-app");
757+ compare(topSurfaceList.count, originalWindowCount + 1);
758+
759+ var initialWindowX = appDelegate.windowedX;
760+ var initialWindowY = appDelegate.windowedY;
761+ var initialWindowWidth = appDelegate.width
762+ var initialWindowHeight = appDelegate.height
763+
764+ var resizeDelta = units.gu(5)
765+ var startDragX = initialWindowX + initialWindowWidth + 1
766+ var startDragY = initialWindowY + initialWindowHeight + 1
767+ mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
768+
769+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
770+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
771+
772+ // Close the window and restart the application
773+ var closeButton = findChild(appDelegate, "closeWindowButton");
774+ appDelegate = null;
775+ verify(closeButton);
776+ mouseClick(closeButton);
777+ tryCompare(topSurfaceList, "count", originalWindowCount);
778+ wait(100); // plus some spare room
779+ appDelegate = startApplication("dialer-app");
780+
781+ // Make sure its size is again the same as before
782+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
783+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
784+ }
785+
786+ function test_saveRestoreMaximized() {
787+ var originalWindowCount = topSurfaceList.count;
788+ var appDelegate = startApplication("dialer-app");
789+ compare(topSurfaceList.count, originalWindowCount + 1);
790+
791+ var initialWindowX = appDelegate.windowedX;
792+ var initialWindowY = appDelegate.windowedY;
793+
794+ var moveDelta = units.gu(5);
795+
796+ appDelegate.windowedX = initialWindowX + moveDelta
797+ appDelegate.windowedY = initialWindowY + moveDelta
798+
799+ // Now change the state to maximized. The window should not keep updating the stored values
800+ maximizeAppDelegate(appDelegate);
801+
802+ // Close the window and restart the application
803+ var closeButton = findChild(appDelegate, "closeWindowButton");
804+ appDelegate = null;
805+ verify(closeButton);
806+ mouseClick(closeButton);
807+ closeButton = null;
808+ tryCompare(topSurfaceList, "count", originalWindowCount);
809+ wait(100); // plus some spare room
810+ appDelegate = startApplication("dialer-app");
811+
812+ // Make sure it's again where we left it in normal state before destroying
813+ tryCompare(appDelegate, "windowedX", initialWindowX + moveDelta)
814+ tryCompare(appDelegate, "windowedY", initialWindowY + moveDelta)
815+
816+ // Make sure maximize() has been called after restoring
817+ tryCompare(appDelegate, "state", "maximized")
818+
819+ // clean up
820+ // click on restore button (same one as maximize)
821+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
822+ mouseClick(maximizeButton);
823+ }
824+
754825 function test_grabbingCursorOnDecorationPress() {
755826 var appDelegate = startApplication("dialer-app");
756827 verify(appDelegate);
tests/qmltests/Stage/tst_PhoneStage.qml−26
@@ -336,32 +336,6 @@ Item {
336336 tryCompare(firstApp, "focused", true);
337337 }
338338
339− function test_leftEdge_data() {
340− return [
341− { tag: "normal", inSpread: false, leftEdgeDragWidth: units.gu(20), shouldMoveApp: true },
342− { tag: "inSpread", inSpread: true, leftEdgeDragWidth: units.gu(5), shouldMoveApp: false }
343− ]
344− }
345−
346− function test_leftEdge(data) {
347− addApps(2);
348−
349− if (data.inSpread) {
350− performEdgeSwipeToShowAppSpread();
351− }
352−
353− var focusedDelegate = findChild(stage, "appDelegate_" + topLevelSurfaceList.idAt(0));
354− var currentX = focusedDelegate.x;
355−
356− stage.leftEdgeDragProgress = data.leftEdgeDragWidth;
357−
358− tryCompare(focusedDelegate, "x", data.shouldMoveApp ? data.leftEdgeDragWidth : currentX);
359−
360− stage.leftEdgeDragProgress = 0;
361−
362− tryCompare(focusedDelegate, "x", currentX);
363− }
364−
365339 function test_focusedAppIsTheOnlyRunningApp() {
366340 addApps(2);
367341
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−66
@@ -88,22 +88,11 @@ Item {
8888
8989 WindowResizeArea {
9090 id: windowResizeArea
91+ anchors.fill: parent
9192 target: fakeWindow
9293 borderThickness: units.gu(2)
9394 minWidth: units.gu(15)
9495 minHeight: units.gu(10)
95− defaultWidth: units.gu(20)
96− defaultHeight: units.gu(20)
97− windowId: "test-window-id"
98− screenWidth: root.width
99− screenHeight: root.height
100−
101− Component.onCompleted: {
102− loadWindowState();
103− }
104− Component.onDestruction: {
105− saveWindowState();
106− }
10796 }
10897
10998 Rectangle {
@@ -312,30 +301,6 @@ Item {
312301 compare(fakeWindow.requestedY, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
313302 }
314303
315− function test_saveRestoreSize() {
316− var initialWindowX = fakeWindow.windowedX;
317− var initialWindowY = fakeWindow.windowedY;
318− var initialWindowWidth = fakeWindow.width
319− var initialWindowHeight = fakeWindow.height
320−
321− var resizeDelta = units.gu(5)
322− var startDragX = initialWindowX + initialWindowWidth + 1
323− var startDragY = initialWindowY + initialWindowHeight + 1
324− mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
325−
326− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.resizeAreaMinWidth));
327− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.resizeAreaMinHeight));
328−
329− // This will destroy the window and recreate it
330− windowLoader.active = false;
331− waitForRendering(root);
332− windowLoader.active = true;
333−
334− // Make sure its size is again the same as before
335− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.resizeAreaMinWidth));
336− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.resizeAreaMinHeight));
337− }
338−
339304 // This tests if dragging smaller than minSize and then larger again, will keep the edge sticking
340305 // to the mouse, instead of immediately making the window grow again when switching direction
341306 function test_resizeSmallerAndLarger_data() {
@@ -361,36 +326,6 @@ Item {
361326 tryCompare(fakeWindow, "height", initialWindowHeight);
362327 }
363328
364− function test_saveRestoreMaximized() {
365− var initialWindowX = fakeWindow.windowedX;
366− var initialWindowY = fakeWindow.windowedY;
367−
368− var moveDelta = units.gu(5);
369−
370− fakeWindow.windowedX = initialWindowX + moveDelta
371− fakeWindow.windowedY = initialWindowY + moveDelta
372−
373− // Now change the state to maximized. The window should not keep updating the stored values
374− fakeWindow.maximize()
375− fakeWindow.windowedX = 31415 // 0 is too risky to pass the test even when broken
376− fakeWindow.windowedY = 31415
377−
378− // This will destroy the window and recreate it
379− windowLoader.active = false;
380− waitForRendering(root);
381− windowLoader.active = true;
382−
383− // Make sure it's again where we left it in normal state before destroying
384− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
385− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
386−
387− // Make sure maximize() has been called after restoring
388− tryCompare(fakeWindow, "state", "maximized")
389−
390− // clean up
391− fakeWindow.restoreFromMaximized()
392− }
393−
394329 function test_restoreMovesIntoBounds_data() {
395330 return [
396331 {tag: "left off", x: -units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
tests/qmltests/tst_DisabledScreenNotice.qml+23−9
@@ -32,6 +32,12 @@ Item {
3232 anchors.fill: parent
3333 anchors.rightMargin: units.gu(40)
3434
35+ // Mock some things here
36+ property int internalGu: units.gu(1)
37+ property var applicationArguments: QtObject {
38+ property string deviceName: "mako"
39+ }
40+
3541 screen: QtObject {
3642 property int orientation: {
3743 switch (orientationSelector.selectedIndex) {
@@ -52,8 +58,9 @@ Item {
5258 }
5359
5460 Column {
55− anchors.fill: parent
56− anchors.leftMargin: units.gu(40)
61+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
62+ width: units.gu(40)
63+ spacing: units.gu(1)
5764
5865 ItemSelector {
5966 id: orientationSelector
@@ -71,6 +78,20 @@ Item {
7178 Layout.fillWidth: true
7279 }
7380 }
81+ Button {
82+ text: "Reset first run settings"
83+ onClicked: {
84+ var settings = testCase.findInvisibleChild(touchScreenPad, "virtualTouchPadSettings");
85+ settings.touchpadTutorialHasRun = false;
86+ }
87+ }
88+ Button {
89+ text: "Run tutorial now"
90+ onClicked: {
91+ var touchPad = testCase.findChild(touchScreenPad, "virtualTouchPad")
92+ touchPad.runTutorial();
93+ }
94+ }
7495 }
7596
7697 UnityTestCase {
@@ -78,13 +99,6 @@ Item {
7899 name: "DisabledScreenNotice"
79100 when: windowShown
80101
81− function test_mouseAreaHidesOnFirstTap() {
82− var noticeArea = findChild(touchScreenPad, "infoNoticeArea")
83− compare(noticeArea.visible, true)
84− tap(root)
85− tryCompare(noticeArea, "visible", false)
86− }
87−
88102 function test_rotation_data() {
89103 return [
90104 {tag: "portrait", selectedOrientation: 0, expectedAngle: 0},
tests/qmltests/tst_OrientedShell.qml+1−13
@@ -999,7 +999,7 @@ Rectangle {
999999 var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
10001000 waitUntilTransitionsEnd(rotationStates);
10011001
1002− performLeftEdgeSwipeToSwitchToDash();
1002+ ApplicationManager.requestFocusApplication("unity8-dash");
10031003
10041004 // Should be back to portrait
10051005 tryCompare(shell, "transformRotationAngle", 0);
@@ -1391,18 +1391,6 @@ Rectangle {
13911391 waitUntilTransitionsEnd(rotationStates);
13921392 }
13931393
1394− function performLeftEdgeSwipeToSwitchToDash() {
1395− var swipeLength = shell.width * 0.7;
1396−
1397− var touchStartX = 1;
1398− var touchStartY = shell.height / 2;
1399− touchFlick(shell,
1400− touchStartX, touchStartY,
1401− touchStartX + swipeLength, touchStartY);
1402−
1403− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
1404− }
1405−
14061394 function performEdgeSwipeToSwitchToPreviousApp() {
14071395 // swipe just enough to ensure an app switch action.
14081396 // If we swipe too much we will trigger the spread mode
tests/qmltests/tst_Shell.qml+6−150
@@ -609,38 +609,6 @@ Rectangle {
609609 compare(ApplicationManager.get(0).appId, "unity8-dash");
610610 }
611611
612− function test_phoneLeftEdgeDrag_data() {
613− return [
614− {tag: "without launcher",
615− revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
616− launcherHides: true, greeterShown: false},
617−
618− {tag: "with launcher",
619− revealLauncher: true, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
620− launcherHides: true, greeterShown: false},
621−
622− {tag: "small swipe",
623− revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app",
624− launcherHides: false, greeterShown: false},
625−
626− {tag: "long swipe",
627− revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
628− launcherHides: true, greeterShown: false},
629−
630− {tag: "small swipe with greeter",
631− revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app",
632− launcherHides: false, greeterShown: true},
633−
634− {tag: "long swipe with greeter",
635− revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
636− launcherHides: true, greeterShown: true},
637−
638− {tag: "swipe over dash",
639− revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "unity8-dash",
640− launcherHides: false, greeterShown: false},
641− ];
642− }
643−
644612 function test_snapDecisionDismissalReturnsFocus() {
645613 loadShell("phone");
646614 swipeAwayGreeter();
@@ -705,80 +673,6 @@ Rectangle {
705673 mockNotificationsModel.append(n)
706674 }
707675
708− function test_phoneLeftEdgeDrag(data) {
709− loadShell("phone");
710− swipeAwayGreeter();
711− dragLauncherIntoView();
712− var appSurfaceId = topLevelSurfaceList.nextId;
713− tapOnAppIconInLauncher();
714− waitUntilAppWindowIsFullyLoaded(appSurfaceId);
715−
716− var greeter = findChild(shell, "greeter");
717− if (data.greeterShown) {
718− showGreeter();
719− }
720−
721− if (data.revealLauncher) {
722− dragLauncherIntoView();
723− }
724−
725− swipeFromLeftEdge(data.swipeLength);
726− if (data.appHides) {
727− waitUntilDashIsFocused();
728− tryCompare(greeter, "shown", false);
729− } else {
730− tryCompare(greeter, "fullyShown", data.greeterShown);
731− }
732−
733− var launcher = findChild(shell, "launcherPanel");
734− tryCompare(launcher, "x", data.launcherHides ? -launcher.width : 0)
735−
736− //FIXME: This check fails. Don't understand the rationale behind it.
737− /*
738− // Make sure the helper for sliding out the launcher wasn't touched. We want to fade it out here.
739− var animateTimer = findInvisibleChild(shell, "animateTimer");
740− compare(animateTimer.nextState, "visible");
741− */
742− }
743−
744− function test_tabletLeftEdgeDrag_data() {
745− return [
746− {tag: "without password", user: "no-password", loggedIn: true},
747− {tag: "with password", user: "has-password", loggedIn: false},
748− ]
749− }
750−
751− function test_tabletLeftEdgeDrag(data) {
752− setLightDMMockMode("full");
753− loadShell("tablet");
754−
755− selectUser(data.user)
756−
757− swipeFromLeftEdge(shell.width * 0.75)
758− wait(500) // to give time to handle dash() signal from Launcher
759− confirmLoggedIn(data.loggedIn)
760− }
761−
762− function test_longLeftEdgeSwipeTakesToAppsAndResetSearchString() {
763− loadShell("phone");
764− swipeAwayGreeter();
765− dragLauncherIntoView();
766− dashCommunicatorSpy.clear();
767−
768− var appSurfaceId = topLevelSurfaceList.nextId;
769− tapOnAppIconInLauncher();
770− waitUntilAppWindowIsFullyLoaded(appSurfaceId);
771−
772− tryCompareFunction(function() { return ApplicationManager.focusedApplicationId !== "unity8-dash"; }, true);
773−
774− //Long left swipe
775− swipeFromLeftEdge(units.gu(30));
776−
777− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
778−
779− compare(dashCommunicatorSpy.count, 1);
780− }
781−
782676 function test_ClickUbuntuIconInLauncherTakesToAppsAndResetSearchString() {
783677 loadShell("phone");
784678 swipeAwayGreeter();
@@ -917,39 +811,6 @@ Rectangle {
917811 LightDM.Users.mockMode = mode;
918812 }
919813
920− /*
921− Regression test for bug https://bugs.launchpad.net/touch-preview-images/+bug/1193419
922−
923− When the user minimizes an application (left-edge swipe) he should always end up in the
924− "Applications" scope view.
925−
926− Steps:
927− - reveal launcher and launch an app that covers the dash
928− - perform long left edge swipe to go minimize the app and go back to the dash.
929− - verify the setCurrentScope() D-Bus call to the dash has been called for the correct scope id.
930− */
931− function test_minimizingAppTakesToDashApps() {
932− loadShell("phone");
933− swipeAwayGreeter();
934− dragLauncherIntoView();
935−
936− // Launch an app from the launcher
937− var appSurfaceId = topLevelSurfaceList.nextId;
938− tapOnAppIconInLauncher();
939− waitUntilAppWindowIsFullyLoaded(appSurfaceId);
940−
941− verify(ApplicationManager.focusedApplicationId !== "unity8-dash")
942−
943− dashCommunicatorSpy.clear();
944− // Minimize the application we just launched
945− swipeFromLeftEdge(shell.width * 0.75);
946−
947− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
948−
949− compare(dashCommunicatorSpy.count, 1);
950− compare(dashCommunicatorSpy.signalArguments[0][0], 0);
951− }
952−
953814 function test_showInputMethod() {
954815 loadShell("phone");
955816 swipeAwayGreeter();
@@ -1170,7 +1031,7 @@ Rectangle {
11701031
11711032 // Place the mouse against the window/screen edge and push beyond the barrier threshold
11721033 mouseMove(shell, 0, shell.height / 2);
1173− launcher.pushEdge(EdgeBarrierSettings.pushThreshold * 1.1);
1034+ launcher.pushEdge(EdgeBarrierSettings.pushThreshold * .8);
11741035
11751036 var panel = findChild(launcher, "launcherPanel");
11761037 verify(panel);
@@ -1190,9 +1051,7 @@ Rectangle {
11901051 waitUntilAppWindowIsFullyLoaded(appSurfaceId);
11911052
11921053 // Minimize the application we just launched
1193− swipeFromLeftEdge(shell.width * 0.75);
1194−
1195− waitUntilDashIsFocused();
1054+ swipeFromLeftEdge(units.gu(15));
11961055
11971056 showGreeter();
11981057
@@ -2002,14 +1861,11 @@ Rectangle {
20021861 var appRepeater = findChild(shell, "appRepeater");
20031862 var appDelegate = appRepeater.itemAt(0);
20041863
2005− var resizeArea = findChild(appDelegate, "windowResizeArea");
2006− var priv = findInvisibleChild(resizeArea, "priv");
2007−
20081864 // Make sure windows are at 0,0 or greater and they have a size that's > 0
2009− compare(priv.normalX >= 0, true)
2010− compare(priv.normalY >= 0, true)
2011− compare(priv.normalWidth > 0, true)
2012− compare(priv.normalHeight > 0, true)
1865+ compare(appDelegate.normalX >= 0, true)
1866+ compare(appDelegate.normalY >= 0, true)
1867+ compare(appDelegate.normalWidth > 0, true)
1868+ compare(appDelegate.normalHeight > 0, true)
20131869 }
20141870
20151871 // bug http://pad.lv/1431566
tests/qmltests/tst_ShellWithPin.qml−66
@@ -549,71 +549,5 @@ Item {
549549 var lockscreen = findChild(shell, "lockscreen");
550550 verify(lockscreen.shown);
551551 }
552−
553− /*
554− Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1393447
555−
556− Do a left edge drag that is long enough to start displacing the greeter
557− but short engough so that the greeter comes back into place once the
558− finger is lifted.
559−
560− In this situation the launcher should remaing fully shown and hide itself
561− only after its idle timeout is triggered.
562− */
563− function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
564− showGreeter();
565−
566− var launcher = testCase.findChild(shell, "launcher")
567− var launcherPanel = testCase.findChild(launcher, "launcherPanel");
568−
569− var toX = shell.width * 0.45;
570− touchFlick(shell,
571− 1 /* fromX */, shell.height * 0.5 /* fromY */,
572− toX /* toX */, shell.height * 0.5 /* toY */,
573− true /* beginTouch */, false /* endTouch */,
574− 50, 100);
575−
576− // Launcher must be fully shown by now
577− tryCompare(launcherPanel, "x", 0);
578−
579− // Greeter should be displaced
580− var coverPage = findChild(shell, "coverPage");
581− tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
582−
583− touchRelease(shell, toX, shell.height * 0.5);
584−
585− // Upon release the greeter should have slid back into full view
586− tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x === 0; }, true);
587−
588− // And the launcher should stay fully shown
589− for (var i = 0; i < 10; ++i) {
590− wait(50);
591− compare(launcherPanel.x, 0);
592− }
593− }
594−
595− function test_longLeftEdgeDrags() {
596− var coverPage = findChild(shell, "coverPage");
597− var lockscreen = findChild(shell, "lockscreen");
598− var launcher = findChild(shell, "launcherPanel");
599− var galleryApp = ApplicationManager.startApplication("gallery-app");
600− tryCompare(shell, "mainApp", galleryApp);
601−
602− // Show greeter
603− showGreeter();
604−
605− // Swipe cover page away
606− touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);
607− tryCompare(launcher, "x", -launcher.width);
608− tryCompare(coverPage, "showProgress", 0);
609− compare(lockscreen.shown, true);
610− tryCompare(shell, "mainApp", galleryApp);
611−
612− // Now attempt a swipe on lockscreen
613− touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);
614− tryCompare(launcher, "x", 0);
615− compare(lockscreen.shown, true);
616− tryCompare(shell, "mainApp", galleryApp);
617− }
618552 }
619553}
tests/uqmlscene/CMakeLists.txt+5−1
@@ -9,10 +9,14 @@ pkg_check_modules(XCB REQUIRED xcb)
99include_directories(
1010 ${CMAKE_CURRENT_BINARY_DIR}
1111 ${CMAKE_SOURCE_DIR}/src
12− ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
1312 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
1413)
1514
15+include_directories(
16+ SYSTEM
17+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
18+)
19+
1620if (NOT "${XCB_INCLUDE_DIRS}" STREQUAL "")
1721 set_target_properties(uqmlscene PROPERTIES INCLUDE_DIRECTORIES ${XCB_INCLUDE_DIRS})
1822endif()
tests/utils/modules/Unity/Test/UnityTestCase.qml+4−4
@@ -163,9 +163,9 @@ TestCase {
163163 if (i === iterations - 1) {
164164 // Avoid any rounding errors by making the last move be at precisely
165165 // the point specified
166− mouseMove(item, toX, toY, iterations / speed)
166+ mouseMove(item, toX, toY, timeStep)
167167 } else {
168− mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
168+ mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
169169 }
170170 }
171171 if (releaseMouse) {
@@ -346,12 +346,12 @@ TestCase {
346346 if (i === iterations - 1) {
347347 // Avoid any rounding errors by making the last move be at precisely
348348 // the point specified
349− wait(iterations / speed)
349+ wait(timeStep)
350350 var event = touchEvent(item)
351351 event.move(0 /* touchId */, rootTo.x, rootTo.y)
352352 event.commit()
353353 } else {
354− wait(iterations / speed)
354+ wait(timeStep)
355355 var event = touchEvent(item)
356356 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
357357 event.commit()