on xenial help sign in
merge trunk
commit7f844e9be4338f398e5624a450a4efdf00b2301e authorMichael Zanetti <michael.zanetti@canonical.com> dateWed 14 Dec 2016, 16:50 (10 years ago) parent0a8daf6497f799
40 files changed+938−375
CMakeLists.txt+13 debian/changelog+33 plugins/LightDM/FullLightDM/CMakeLists.txt+5−1 plugins/Unity/Indicators/CMakeLists.txt+3 plugins/Utils/ElapsedTimer.h+1−1 plugins/Utils/Timer.cpp+5−2 plugins/Utils/Timer.h+5−6 plugins/Utils/globalfunctions.cpp+5 plugins/Utils/globalfunctions.h+2 po/unity8.pot+80−43 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/Launcher.qml+9−3 qml/Panel/Indicators/MenuItemFactory.qml+22−1 qml/Shell.qml+1−1 qml/Stage/MoveHandler.qml+2−2 qml/Stage/Stage.qml+62−32 qml/Stage/WindowResizeArea.qml+2−70 qml/Stage/WindowStateSaver.qml+63 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/plugins/Utils/WindowInputMonitorTest.cpp+76−9 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/Launcher/tst_Launcher.qml+3−3 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+6−1 tests/qmltests/Stage/tst_ApplicationWindow.qml+2−20 tests/qmltests/Stage/tst_DesktopStage.qml+71 tests/qmltests/Stage/tst_WindowResizeArea.qml+1−66 tests/qmltests/tst_DisabledScreenNotice.qml+23−9 tests/qmltests/tst_Shell.qml+4−7 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")
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 ]
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/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/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
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/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/Launcher.qml+9−3
@@ -19,10 +19,13 @@ 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
@@ -105,7 +108,10 @@ FocusScope {
105108 }
106109 }
107110
108− function hide() {
111+ function hide(flags) {
112+ if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
113+ return;
114+ }
109115 switchToNextState("")
110116 }
111117
@@ -416,11 +422,11 @@ FocusScope {
416422 property bool animate: true
417423
418424 onApplicationSelected: {
419− root.hide();
425+ root.hide(ignoreHideIfMouseOverLauncher);
420426 launcherApplicationSelected(appId)
421427 }
422428 onShowDashHome: {
423− root.hide();
429+ root.hide(ignoreHideIfMouseOverLauncher);
424430 root.showDashHome();
425431 }
426432
qml/Panel/Indicators/MenuItemFactory.qml+22−1
@@ -106,7 +106,7 @@ Item {
106106 }
107107
108108 if (component === undefined) {
109− console.debug("Don't know how to make " + modelData.type + " for " + indicator);
109+ console.debug("Don't know how to make " + type + " for " + indicator);
110110 }
111111
112112 return component
@@ -398,7 +398,9 @@ Item {
398398 id: switchItem
399399 objectName: "switchMenu"
400400 property QtObject menuData: null
401+ property var menuModel: menuFactory.menuModel
401402 property int menuIndex: -1
403+ property var extendedData: menuData && menuData.ext || undefined
402404 property bool serverChecked: menuData && menuData.isToggled || false
403405
404406 text: menuData && menuData.label || ""
@@ -407,6 +409,25 @@ Item {
407409 checked: serverChecked
408410 highlightWhenPressed: false
409411
412+ property var subtitleAction: UnityMenuAction {
413+ model: menuModel
414+ index: menuIndex
415+ name: getExtendedProperty(extendedData, "xCanonicalSubtitleAction", "")
416+ }
417+ subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
418+
419+ onMenuModelChanged: {
420+ loadAttributes();
421+ }
422+ onMenuIndexChanged: {
423+ loadAttributes();
424+ }
425+
426+ function loadAttributes() {
427+ if (!menuModel || menuIndex == -1) return;
428+ menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-subtitle-action': 'string'});
429+ }
430+
410431 ServerPropertySynchroniser {
411432 objectName: "sync"
412433 syncTimeout: Utils.Constants.indicatorValueTimeout
qml/Shell.qml+1−1
@@ -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
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/Stage.qml+62−32
@@ -712,6 +712,29 @@ FocusScope {
712712 property real restoredX
713713 property real restoredY
714714
715+ // Keeps track of the window geometry while in normal or restored state
716+ // Useful when returning from some maxmized state or when saving the geometry while maximized
717+ // FIXME: find a better solution
718+ property real normalX: 0
719+ property real normalY: 0
720+ property real normalWidth: 0
721+ property real normalHeight: 0
722+ function updateNormalGeometry() {
723+ if (appDelegate.state == "normal" || appDelegate.state == "restored") {
724+ normalX = appDelegate.requestedX;
725+ normalY = appDelegate.requestedY;
726+ normalWidth = appDelegate.width;
727+ normalHeight = appDelegate.height;
728+ }
729+ }
730+ Connections {
731+ target: appDelegate
732+ onXChanged: appDelegate.updateNormalGeometry();
733+ onYChanged: appDelegate.updateNormalGeometry();
734+ onWidthChanged: appDelegate.updateNormalGeometry();
735+ onHeightChanged: appDelegate.updateNormalGeometry();
736+ }
737+
715738 Binding {
716739 target: appDelegate
717740 property: "y"
@@ -852,6 +875,16 @@ FocusScope {
852875 MirFocusController.focusedSurface = null;
853876 }
854877 }
878+
879+ WindowStateSaver {
880+ id: windowStateSaver
881+ target: appDelegate
882+ screenWidth: appContainer.width
883+ screenHeight: appContainer.height
884+ leftMargin: root.leftMargin
885+ minimumY: PanelState.panelHeight
886+ }
887+
855888 Component.onCompleted: {
856889 if (application && application.rotatesWindowContents) {
857890 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -863,7 +896,7 @@ FocusScope {
863896 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
864897 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
865898 // Now load any saved state. This needs to happen *after* the cascading!
866− resizeArea.loadWindowState();
899+ windowStateSaver.load();
867900
868901 // NB: We're differentiating if this delegate was created in response to a new entry in the model
869902 // or if the Repeater is just populating itself with delegates to match the model it received.
@@ -880,6 +913,8 @@ FocusScope {
880913 _constructing = false;
881914 }
882915 Component.onDestruction: {
916+ windowStateSaver.save();
917+
883918 if (!root.parent) {
884919 // This stage is about to be destroyed. Don't mess up with the model at this point
885920 return;
@@ -1482,27 +1517,22 @@ FocusScope {
14821517 id: resizeArea
14831518 objectName: "windowResizeArea"
14841519
1520+ anchors.fill: appDelegate
1521+
14851522 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
14861523 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
14871524
14881525 target: appDelegate
1526+ minimumY: PanelState.panelHeight // disallow resizing up past Panel
14891527 minWidth: units.gu(10)
14901528 minHeight: units.gu(10)
14911529 borderThickness: units.gu(2)
1492− windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
1493− screenWidth: appContainer.width
1494− screenHeight: appContainer.height
1495− leftMargin: root.leftMargin
14961530 enabled: false
14971531 visible: enabled
14981532
14991533 onPressed: {
15001534 appDelegate.focus = true;
15011535 }
1502−
1503− Component.onDestruction: {
1504− saveWindowState();
1505− }
15061536 }
15071537
15081538 DecoratedWindow {
@@ -1652,10 +1682,10 @@ FocusScope {
16521682 }
16531683 }
16541684
1655− Image {
1656− id: closeImage
1685+ MouseArea {
1686+ id: closeMouseArea
1687+ objectName: "closeMouseArea"
16571688 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1658− source: "graphics/window-close.svg"
16591689 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
16601690 visible: !appDelegate.isDash && dragArea.distance == 0
16611691 && index == spreadItem.highlightedIndex
@@ -1663,20 +1693,20 @@ FocusScope {
16631693 && mousePos.y > -units.gu(4)
16641694 && mousePos.x > -units.gu(4)
16651695 && mousePos.x < (decoratedWindow.width * 2 / 3)
1666− height: units.gu(2)
1696+ height: units.gu(6)
16671697 width: height
1668− sourceSize.width: width
1669− sourceSize.height: height
1670−
1671− MouseArea {
1672− id: closeMouseArea
1673− objectName: "closeMouseArea"
1674− anchors.fill: closeImage
1675− anchors.margins: -units.gu(2)
1676− onClicked: {
1677− priv.closingIndex = index;
1678− appDelegate.close();
1679− }
1698+
1699+ onClicked: {
1700+ priv.closingIndex = index;
1701+ appDelegate.close();
1702+ }
1703+ Image {
1704+ id: closeImage
1705+ source: "graphics/window-close.svg"
1706+ anchors.fill: closeMouseArea
1707+ anchors.margins: units.gu(2)
1708+ sourceSize.width: width
1709+ sourceSize.height: height
16801710 }
16811711 }
16821712 }
@@ -1711,13 +1741,13 @@ FocusScope {
17111741 }
17121742
17131743 // Find the hovered item and mark it active
1714− var mapped = mapToItem(appContainer, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1715− var itemUnder = appContainer.childAt(mapped.x, mapped.y)
1716− if (itemUnder) {
1717− mapped = mapToItem(itemUnder, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1718− var delegateChild = itemUnder.childAt(mapped.x, mapped.y)
1719− if (delegateChild && (delegateChild.objectName === "dragArea" || delegateChild.objectName === "windowInfoItem")) {
1720− spreadItem.highlightedIndex = appRepeater.indexOf(itemUnder)
1744+ for (var i = appRepeater.count - 1; i >= 0; i--) {
1745+ var appDelegate = appRepeater.itemAt(i);
1746+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1747+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1748+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1749+ spreadItem.highlightedIndex = i;
1750+ break;
17211751 }
17221752 }
17231753
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+}
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/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/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/Launcher/tst_Launcher.qml+3−3
@@ -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_MenuItemFactory.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
@@ -798,6 +798,77 @@ Item {
798798 tryCompare(dialerAppDelegate, "state", "maximized");
799799 }
800800
801+ function test_saveRestoreSize() {
802+ var originalWindowCount = topSurfaceList.count;
803+ var appDelegate = startApplication("dialer-app");
804+ compare(topSurfaceList.count, originalWindowCount + 1);
805+
806+ var initialWindowX = appDelegate.windowedX;
807+ var initialWindowY = appDelegate.windowedY;
808+ var initialWindowWidth = appDelegate.width
809+ var initialWindowHeight = appDelegate.height
810+
811+ var resizeDelta = units.gu(5)
812+ var startDragX = initialWindowX + initialWindowWidth + 1
813+ var startDragY = initialWindowY + initialWindowHeight + 1
814+ mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
815+
816+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
817+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
818+
819+ // Close the window and restart the application
820+ var closeButton = findChild(appDelegate, "closeWindowButton");
821+ appDelegate = null;
822+ verify(closeButton);
823+ mouseClick(closeButton);
824+ tryCompare(topSurfaceList, "count", originalWindowCount);
825+ wait(100); // plus some spare room
826+ appDelegate = startApplication("dialer-app");
827+
828+ // Make sure its size is again the same as before
829+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
830+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
831+ }
832+
833+ function test_saveRestoreMaximized() {
834+ var originalWindowCount = topSurfaceList.count;
835+ var appDelegate = startApplication("dialer-app");
836+ compare(topSurfaceList.count, originalWindowCount + 1);
837+
838+ var initialWindowX = appDelegate.windowedX;
839+ var initialWindowY = appDelegate.windowedY;
840+
841+ var moveDelta = units.gu(5);
842+
843+ appDelegate.windowedX = initialWindowX + moveDelta
844+ appDelegate.windowedY = initialWindowY + moveDelta
845+
846+ // Now change the state to maximized. The window should not keep updating the stored values
847+ maximizeAppDelegate(appDelegate);
848+
849+ // Close the window and restart the application
850+ var closeButton = findChild(appDelegate, "closeWindowButton");
851+ appDelegate = null;
852+ verify(closeButton);
853+ mouseClick(closeButton);
854+ closeButton = null;
855+ tryCompare(topSurfaceList, "count", originalWindowCount);
856+ wait(100); // plus some spare room
857+ appDelegate = startApplication("dialer-app");
858+
859+ // Make sure it's again where we left it in normal state before destroying
860+ tryCompare(appDelegate, "windowedX", initialWindowX + moveDelta)
861+ tryCompare(appDelegate, "windowedY", initialWindowY + moveDelta)
862+
863+ // Make sure maximize() has been called after restoring
864+ tryCompare(appDelegate, "state", "maximized")
865+
866+ // clean up
867+ // click on restore button (same one as maximize)
868+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
869+ mouseClick(maximizeButton);
870+ }
871+
801872 function test_grabbingCursorOnDecorationPress() {
802873 var appDelegate = startApplication("dialer-app");
803874 verify(appDelegate);
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_Shell.qml+4−7
@@ -1857,14 +1857,11 @@ Rectangle {
18571857 var appRepeater = findChild(shell, "appRepeater");
18581858 var appDelegate = appRepeater.itemAt(0);
18591859
1860− var resizeArea = findChild(appDelegate, "windowResizeArea");
1861− var priv = findInvisibleChild(resizeArea, "priv");
1862−
18631860 // Make sure windows are at 0,0 or greater and they have a size that's > 0
1864− compare(priv.normalX >= 0, true)
1865− compare(priv.normalY >= 0, true)
1866− compare(priv.normalWidth > 0, true)
1867− compare(priv.normalHeight > 0, true)
1861+ compare(appDelegate.normalX >= 0, true)
1862+ compare(appDelegate.normalY >= 0, true)
1863+ compare(appDelegate.normalWidth > 0, true)
1864+ compare(appDelegate.normalHeight > 0, true)
18681865 }
18691866
18701867 // bug http://pad.lv/1431566
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()