on xenial help sign in
Merge remote-tracking branch 'screens-workspaces-switcher' into xenial_-_edge_-_multiscreen
e961d7cMarius Gripsgard
CMakeLists.txt+5−1
@@ -72,6 +72,7 @@ find_package(Qt5Sql 5.6 REQUIRED)
72find_package(Qt5Test 5.6 REQUIRED)
72find_package(Qt5Test 5.6 REQUIRED)
73
73
74pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
74pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
75+pkg_check_modules(QTMIRSERVER REQUIRED qtmirserver>=0.6.0)
75pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
76pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
76pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
77pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
77pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
78pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
@@ -84,6 +85,7 @@ pkg_check_modules(QTDBUSTEST REQUIRED
84 libqtdbustest-1
85 libqtdbustest-1
85 libqtdbusmock-1
86 libqtdbusmock-1
86 )
87 )
88+pkg_check_modules(MIRAL REQUIRED miral)
87
89
88### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
90### Check UbuntuGestures private headers. No pkg-config (.pc) file is provided for them
89find_path(UBUNTUGESTUREPRIV
91find_path(UBUNTUGESTUREPRIV
@@ -118,7 +120,9 @@ if(SHELL_PLUGINDIR STREQUAL "")
118 message(FATAL_ERROR "Could not determine plugin import dir.")
120 message(FATAL_ERROR "Could not determine plugin import dir.")
119endif()
121endif()
120
122
121−set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
123+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-permissive -pedantic -Wall -Wextra")
124+
125+set (CMAKE_CXX_STANDARD 14)
122
126
123if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
127if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo")
124 option(Werror "Treat warnings as errors" ON)
128 option(Werror "Treat warnings as errors" ON)
debian/control+6
@@ -33,6 +33,7 @@ Build-Depends: cmake,
33 libqtdbustest1-dev,
33 libqtdbustest1-dev,
34 libqt5svg5-dev,
34 libqt5svg5-dev,
35 libqt5xmlpatterns5-dev,
35 libqt5xmlpatterns5-dev,
36+ libqtmirserver-dev (>= 0.6.0),
36 libsystemsettings-dev,
37 libsystemsettings-dev,
37 libubuntu-app-launch2-dev,
38 libubuntu-app-launch2-dev,
38 libubuntu-download-manager-common-dev,
39 libubuntu-download-manager-common-dev,
@@ -47,6 +48,7 @@ Build-Depends: cmake,
47 libxcb1-dev[!arm64 !armhf],
48 libxcb1-dev[!arm64 !armhf],
48 libxi-dev[!arm64 !armhf],
49 libxi-dev[!arm64 !armhf],
49# End of X11 libs
50# End of X11 libs
51+ mirtest-dev,
50 pkg-config,
52 pkg-config,
51 python3-all:any,
53 python3-all:any,
52 python3-setuptools,
54 python3-setuptools,
@@ -75,6 +77,9 @@ Build-Depends: cmake,
75 ttf-ubuntu-font-family,
77 ttf-ubuntu-font-family,
76 xvfb,
78 xvfb,
77 android-headers,
79 android-headers,
80+# mirtest pkgconfig requires these, but doesn't have a deb dependency. Bug lp:1633537
81+ libboost-filesystem-dev,
82+ libboost-system-dev,
78Standards-Version: 3.9.4
83Standards-Version: 3.9.4
79Homepage: http://launchpad.net/unity
84Homepage: http://launchpad.net/unity
80# If you aren't a member of ~unity-team but need to upload
85# If you aren't a member of ~unity-team but need to upload
@@ -123,6 +128,7 @@ Depends: dmz-cursor-theme,
123# qml-module-ubuntu-web,
128# qml-module-ubuntu-web,
124 qtdeclarative5-qtmir-plugin (>= 0.4.8),
129 qtdeclarative5-qtmir-plugin (>= 0.4.8),
125 qtdeclarative5-ubuntu-telephony0.1,
130 qtdeclarative5-ubuntu-telephony0.1,
131+ qtmir-desktop (>= 0.6.0) | qtmir-android (>= 0.6.0),
126 ubuntu-system-settings (>= 0.4),
132 ubuntu-system-settings (>= 0.4),
127 unity-launcher-impl-13,
133 unity-launcher-impl-13,
128 unity8-common (= ${source:Version}),
134 unity8-common (= ${source:Version}),
debian/unity8.install+5
@@ -13,7 +13,12 @@ usr/share/unity8/Rotation
13usr/share/unity8/DeviceConfiguration.qml
13usr/share/unity8/DeviceConfiguration.qml
14usr/share/unity8/OrientedShell.qml
14usr/share/unity8/OrientedShell.qml
15usr/share/unity8/DisabledScreenNotice.qml
15usr/share/unity8/DisabledScreenNotice.qml
16+usr/share/unity8/ErrorApplication.qml
17+usr/share/unity8/qmldir
16usr/share/unity8/Shell.qml
18usr/share/unity8/Shell.qml
19+usr/share/unity8/ShellApplication.qml
20+usr/share/unity8/ShellNotifier.qml
21+usr/share/unity8/ShellScreen.qml
17usr/share/unity8/Stage
22usr/share/unity8/Stage
18usr/share/unity8/Tutorial
23usr/share/unity8/Tutorial
19usr/share/unity8/Wizard
24usr/share/unity8/Wizard
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 CursorImageInfo.cpp
15 CursorImageInfo.cpp
16 CursorImageProvider.cpp
16 CursorImageProvider.cpp
17 MousePointer.cpp
17 MousePointer.cpp
18+ InputDispatcherFilter.cpp
18 # We need to run moc on this header
19 # We need to run moc on this header
19 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
20 )
21 )
plugins/Cursor/InputDispatcherFilter.cppnew file+175
@@ -0,0 +1,175 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "InputDispatcherFilter.h"
18+#include "MousePointer.h"
19+
20+#include <QEvent>
21+#include <QGuiApplication>
22+#include <QQuickWindow>
23+#include <QScreen>
24+#include <QtMath>
25+#include <qpa/qplatformnativeinterface.h>
26+#include <qpa/qplatformscreen.h>
27+
28+InputDispatcherFilter *InputDispatcherFilter::instance()
29+{
30+ static InputDispatcherFilter filter;
31+ return &filter;
32+}
33+
34+InputDispatcherFilter::InputDispatcherFilter(QObject *parent)
35+ : QObject(parent)
36+ , m_pushing(false)
37+{
38+ QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface();
39+ m_inputDispatcher = static_cast<QObject*>(ni->nativeResourceForIntegration("InputDispatcher"));
40+ if (m_inputDispatcher) {
41+ m_inputDispatcher->installEventFilter(this);
42+ }
43+}
44+
45+void InputDispatcherFilter::registerPointer(MousePointer *pointer)
46+{
47+ // allow first registered pointer to be visible.
48+ m_pointers.insert(pointer);
49+}
50+
51+void InputDispatcherFilter::unregisterPointer(MousePointer *pointer)
52+{
53+ m_pointers.remove(pointer);
54+}
55+
56+bool InputDispatcherFilter::eventFilter(QObject *o, QEvent *e)
57+{
58+ if (o != m_inputDispatcher) return false;
59+
60+ switch (e->type()) {
61+ case QEvent::MouseMove:
62+ case QEvent::MouseButtonPress:
63+ case QEvent::MouseButtonRelease:
64+ {
65+ // if we don't have any pointers, filter all mouse events.
66+ auto pointer = currentPointer();
67+ if (!pointer) return true;
68+
69+ QMouseEvent* me = static_cast<QMouseEvent*>(e);
70+
71+ // Local position gives relative change of mouse pointer.
72+ QPointF movement = me->localPos();
73+
74+ // Adjust the position
75+ QPointF oldPos = pointer->window()->geometry().topLeft() + pointer->position();
76+ QPointF newPos = adjustedPositionForMovement(oldPos, movement);
77+
78+ QScreen* currentScreen = screenAt(newPos);
79+ if (currentScreen) {
80+ QRect screenRect = currentScreen->geometry();
81+ qreal newX = (oldPos + movement).x();
82+ qreal newY = (oldPos + movement).y();
83+
84+ if (newX <= screenRect.left() && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top left corner
85+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY- screenRect.top() - pointer->topBoundaryOffset(), 2));
86+ Q_EMIT pushedTopLeftCorner(currentScreen, qAbs(distance), me->buttons());
87+ m_pushing = true;
88+ } else if (newX >= screenRect.right()-1 && newY < screenRect.top() + pointer->topBoundaryOffset()) { // top right corner
89+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY - screenRect.top() - pointer->topBoundaryOffset(), 2));
90+ Q_EMIT pushedTopRightCorner(currentScreen, qAbs(distance), me->buttons());
91+ m_pushing = true;
92+ } else if (newX < 0 && newY >= screenRect.bottom()-1) { // bottom left corner
93+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-screenRect.bottom(), 2));
94+ Q_EMIT pushedBottomLeftCorner(currentScreen, qAbs(distance), me->buttons());
95+ m_pushing = true;
96+ } else if (newX >= screenRect.right()-1 && newY >= screenRect.bottom()-1) { // bottom right corner
97+ const auto distance = qSqrt(qPow(newX-screenRect.right(), 2) + qPow(newY-screenRect.bottom(), 2));
98+ Q_EMIT pushedBottomRightCorner(currentScreen, qAbs(distance), me->buttons());
99+ m_pushing = true;
100+ } else if (newX < screenRect.left()) { // left edge
101+ Q_EMIT pushedLeftBoundary(currentScreen, qAbs(newX), me->buttons());
102+ m_pushing = true;
103+ } else if (newX >= screenRect.right()) { // right edge
104+ Q_EMIT pushedRightBoundary(currentScreen, newX - (screenRect.right() - 1), me->buttons());
105+ m_pushing = true;
106+ } else if (newY < screenRect.top() + pointer->topBoundaryOffset()) { // top edge
107+ Q_EMIT pushedTopBoundary(currentScreen, qAbs(newY - screenRect.top() - pointer->topBoundaryOffset()), me->buttons());
108+ m_pushing = true;
109+ } else if (Q_LIKELY(newX > 0 && newX < screenRect.right()-1 && newY > 0 && newY < screenRect.bottom()-1)) { // normal pos, not pushing
110+ if (m_pushing) {
111+ Q_EMIT pushStopped(currentScreen);
112+ m_pushing = false;
113+ }
114+ }
115+ }
116+
117+ // Send the event
118+ QMouseEvent eCopy(me->type(), me->localPos(), newPos, me->button(), me->buttons(), me->modifiers());
119+ eCopy.setTimestamp(me->timestamp());
120+ o->event(&eCopy);
121+ return true;
122+ }
123+ default:
124+ break;
125+ }
126+ return false;
127+}
128+
129+QPointF InputDispatcherFilter::adjustedPositionForMovement(const QPointF &pt, const QPointF &movement) const
130+{
131+ QPointF adjusted = pt + movement;
132+
133+ auto screen = screenAt(adjusted); // first check if our move was to a screen with an enabled pointer.
134+ if (screen) {
135+ return adjusted;
136+ } else if ((screen = screenAt(pt))) { // then check if our old position was valid
137+ QRectF screenBounds = screen->geometry();
138+ // bound the new position to the old screen geometry
139+ adjusted.rx() = qMax(screenBounds.left(), qMin(adjusted.x(), screenBounds.right()-1));
140+ adjusted.ry() = qMax(screenBounds.top(), qMin(adjusted.y(), screenBounds.bottom()-1));
141+ } else {
142+ auto screens = QGuiApplication::screens();
143+
144+ // center of first screen with a pointer.
145+ Q_FOREACH(QScreen* screen, screens) {
146+ Q_FOREACH(MousePointer* pointer, m_pointers) {
147+ if (pointer->isEnabled() && pointer->screen() == screen) {
148+ return screen->geometry().center();
149+ }
150+ }
151+ }
152+ }
153+ return adjusted;
154+}
155+
156+QScreen *InputDispatcherFilter::screenAt(const QPointF &pt) const
157+{
158+ Q_FOREACH(MousePointer* pointer, m_pointers) {
159+ if (!pointer->isEnabled()) continue;
160+
161+ QScreen* screen = pointer->screen();
162+ if (screen && screen->geometry().contains(pt.toPoint()))
163+ return screen;
164+ }
165+ return nullptr;
166+}
167+
168+MousePointer *InputDispatcherFilter::currentPointer() const
169+{
170+ Q_FOREACH(MousePointer* pointer, m_pointers) {
171+ if (!pointer->isEnabled()) continue;
172+ return pointer;
173+ }
174+ return nullptr;
175+}
plugins/Cursor/InputDispatcherFilter.hnew 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 it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef INPUTDISPATCHERFILTER_H
18+#define INPUTDISPATCHERFILTER_H
19+
20+#include <QObject>
21+#include <QPointF>
22+#include <QSet>
23+
24+class MousePointer;
25+class QScreen;
26+
27+class InputDispatcherFilter : public QObject
28+{
29+ Q_OBJECT
30+public:
31+ static InputDispatcherFilter *instance();
32+
33+ void registerPointer(MousePointer* pointer);
34+ void unregisterPointer(MousePointer* pointer);
35+
36+Q_SIGNALS:
37+ void pushedLeftBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
38+ void pushedRightBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
39+ void pushedTopBoundary(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
40+ void pushedTopLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
41+ void pushedTopRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
42+ void pushedBottomLeftCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
43+ void pushedBottomRightCorner(QScreen* screen, qreal amount, Qt::MouseButtons buttons);
44+ void pushStopped(QScreen* screen);
45+
46+protected:
47+ InputDispatcherFilter(QObject* parent = nullptr);
48+
49+ bool eventFilter(QObject *o, QEvent *e) override;
50+
51+ QPointF adjustedPositionForMovement(const QPointF& pt, const QPointF& movement) const;
52+ QScreen* screenAt(const QPointF& pt) const;
53+
54+ MousePointer* currentPointer() const;
55+
56+private:
57+ QObject* m_inputDispatcher;
58+ QSet<MousePointer*> m_pointers;
59+ bool m_pushing;
60+};
61+
62+#endif // INPUTDISPATCHERFILTER_H
plugins/Cursor/MousePointer.cpp+63−78
@@ -16,84 +16,74 @@
16
16
17#include "MousePointer.h"
17#include "MousePointer.h"
18#include "CursorImageProvider.h"
18#include "CursorImageProvider.h"
19−
19+#include "InputDispatcherFilter.h"
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
22
20
23#include <QQuickWindow>
21#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
26
22
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
28
25
29MousePointer::MousePointer(QQuickItem *parent)
26MousePointer::MousePointer(QQuickItem *parent)
30 : MirMousePointerInterface(parent)
27 : MirMousePointerInterface(parent)
31 , m_cursorName(QStringLiteral("left_ptr"))
28 , m_cursorName(QStringLiteral("left_ptr"))
32 , m_themeName(QStringLiteral("default"))
29 , m_themeName(QStringLiteral("default"))
33{
30{
34−}
31+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
35−
32+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
33+ if (window() && window()->screen() == screen) {
37− Qt::KeyboardModifiers modifiers)
34+ Q_EMIT pushedLeftBoundary(amount, buttons);
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
44− Q_EMIT mouseMoved();
45− }
35+ }
46−
36+ });
47− m_accumulatedMovement += movement;
37+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
48− // don't apply the fractional part
38+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
39+ if (window() && window()->screen() == screen) {
50− m_accumulatedMovement -= appliedMovement;
40+ Q_EMIT pushedRightBoundary(amount, buttons);
51−
41+ }
52− qreal newX = x() + appliedMovement.x();
42+ });
53− qreal newY = y() + appliedMovement.y();
43+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
54− const qreal sceneWidth = parentItem()->width();
44+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
55− const qreal sceneHeight = parentItem()->height();
45+ if (window() && window()->screen() == screen) {
56−
46+ Q_EMIT pushedTopBoundary(amount, buttons);
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
47+ }
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
48+ });
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
49+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
60− m_pushing = true;
50+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
51+ if (window() && window()->screen() == screen) {
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
52+ Q_EMIT pushedTopLeftCorner(amount, buttons);
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
53+ }
64− m_pushing = true;
54+ });
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
55+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
56+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
57+ if (window() && window()->screen() == screen) {
68− m_pushing = true;
58+ Q_EMIT pushedTopRightCorner(amount, buttons);
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
59+ }
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
60+ });
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
61+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
72− m_pushing = true;
62+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
73− } else if (newX < 0) { // left edge
63+ if (window() && window()->screen() == screen) {
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
64+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
75− m_pushing = true;
65+ }
76− } else if (newX >= sceneWidth) { // right edge
66+ });
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
67+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
78− m_pushing = true;
68+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
79− } else if (newY < m_topBoundaryOffset) { // top edge
69+ if (window() && window()->screen() == screen) {
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
70+ Q_EMIT pushedBottomRightCorner(amount, buttons);
81− m_pushing = true;
71+ }
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
72+ });
83− if (m_pushing) {
73+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
74+ this, [this](QScreen* screen) {
75+ if (window() && window()->screen() == screen) {
84 Q_EMIT pushStopped();
76 Q_EMIT pushStopped();
85− m_pushing = false;
86 }
77 }
87− }
78+ });
88
79
89− applyItemConfinement(newX, newY);
80+ InputDispatcherFilter::instance()->registerPointer(this);
90−
81+}
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
93
82
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
83+MousePointer::~MousePointer()
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
84+{
96− buttons, modifiers);
85+ registerScreen(nullptr);
86+ InputDispatcherFilter::instance()->unregisterPointer(this);
97}
87}
98
88
99void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
89void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121 }
111 }
122}
112}
123
113
124−void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
125−{
126− if (!parentItem()) {
127− return;
128− }
129−
130− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
131− QWindowSystemInterface::handleWheelEvent(window(), timestamp, scenePosition /* local */, scenePosition /* global */,
132− QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
133−}
134−
135int MousePointer::topBoundaryOffset() const
114int MousePointer::topBoundaryOffset() const
136{
115{
137 return m_topBoundaryOffset;
116 return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
182 if (m_registeredScreen) {
161 if (m_registeredScreen) {
183 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
162 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184 if (previousCursor) {
163 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
164+ previousCursor->unregisterMousePointer(this);
186 } else {
165 } else {
187 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
166 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188 }
167 }
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
193 if (m_registeredScreen) {
172 if (m_registeredScreen) {
194 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
173 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195 if (cursor) {
174 if (cursor) {
196− cursor->setMousePointer(this);
175+ cursor->registerMousePointer(this);
197 } else {
176 } else {
198 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
177 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199 }
178 }
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
216 }
195 }
217}
196}
218
197
198+void MousePointer::moveTo(const QPoint &position)
199+{
200+ setPosition(position);
201+ Q_EMIT mouseMoved();
202+}
203+
219void MousePointer::setCustomCursor(const QCursor &customCursor)
204void MousePointer::setCustomCursor(const QCursor &customCursor)
220{
205{
221 CursorImageProvider::instance()->setCustomCursor(customCursor);
206 CursorImageProvider::instance()->setCustomCursor(customCursor);
plugins/Cursor/MousePointer.h+5−4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
32public:
32public:
33 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
34
35
35 void setCursorName(const QString &qtCursorName) override;
36 void setCursorName(const QString &qtCursorName) override;
36 QString cursorName() const override { return m_cursorName; }
37 QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ public:
38 void setThemeName(const QString &themeName) override;
39 void setThemeName(const QString &themeName) override;
39 QString themeName() const override { return m_themeName; }
40 QString themeName() const override { return m_themeName; }
40
41
42+ void moveTo(const QPoint& position) override;
43+
41 void setCustomCursor(const QCursor &) override;
44 void setCustomCursor(const QCursor &) override;
42
45
43 QQuickItem* confiningItem() const;
46 QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ public:
46 int topBoundaryOffset() const;
49 int topBoundaryOffset() const;
47 void setTopBoundaryOffset(int topBoundaryOffset);
50 void setTopBoundaryOffset(int topBoundaryOffset);
48
51
49−public Q_SLOTS:
52+ QScreen* screen() const { return m_registeredScreen; }
50− void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
51− Qt::KeyboardModifiers modifiers) override;
52− void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers) override;
53
53
54Q_SIGNALS:
54Q_SIGNALS:
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private:
79 QPointer<QScreen> m_registeredScreen;
79 QPointer<QScreen> m_registeredScreen;
80 QString m_cursorName;
80 QString m_cursorName;
81 QString m_themeName;
81 QString m_themeName;
82+ bool m_active;
82
83
83 // Accumulated, unapplied, mouse movement.
84 // Accumulated, unapplied, mouse movement.
84 QPointF m_accumulatedMovement;
85 QPointF m_accumulatedMovement;
plugins/GlobalShortcut/globalshortcut.cpp−15
@@ -56,11 +56,6 @@ void GlobalShortcut::setActive(bool active)
56 Q_EMIT activeChanged(active);
56 Q_EMIT activeChanged(active);
57}
57}
58
58
59−void GlobalShortcut::componentComplete()
60−{
61− connect(this, &QQuickItem::windowChanged, this, &GlobalShortcut::setupFilterOnWindow);
62−}
63−
64void GlobalShortcut::keyPressEvent(QKeyEvent * event)
59void GlobalShortcut::keyPressEvent(QKeyEvent * event)
65{
60{
66 if (!m_active) return;
61 if (!m_active) return;
@@ -76,13 +71,3 @@ void GlobalShortcut::keyReleaseEvent(QKeyEvent * event)
76 event->accept();
71 event->accept();
77 Q_EMIT released(m_shortcut.toString());
72 Q_EMIT released(m_shortcut.toString());
78}
73}
79−
80−void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
81−{
82− if (!window) {
83−// qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
84− return;
85− }
86−
87− registry->setupFilterOnWindow((qulonglong) window->winId());
88−}
plugins/GlobalShortcut/globalshortcut.h−4
@@ -53,7 +53,6 @@ public:
53 void setActive(bool active);
53 void setActive(bool active);
54
54
55protected:
55protected:
56− void componentComplete() override;
57 void keyPressEvent(QKeyEvent * event) override;
56 void keyPressEvent(QKeyEvent * event) override;
58 void keyReleaseEvent(QKeyEvent * event) override;
57 void keyReleaseEvent(QKeyEvent * event) override;
59
58
@@ -69,9 +68,6 @@ Q_SIGNALS:
69 void released(const QString &shortcut);
68 void released(const QString &shortcut);
70 void activeChanged(bool active);
69 void activeChanged(bool active);
71
70
72−private Q_SLOTS:
73− void setupFilterOnWindow(QQuickWindow* window);
74−
75private:
71private:
76 QVariant m_shortcut;
72 QVariant m_shortcut;
77 bool m_active = true;
73 bool m_active = true;
plugins/GlobalShortcut/globalshortcutregistry.cpp+23−15
@@ -21,11 +21,25 @@
21
21
22#include "globalshortcutregistry.h"
22#include "globalshortcutregistry.h"
23
23
24−static qulonglong s_windowId = 0;
24+namespace {
25+QWindow* windowForShortcut(GlobalShortcut *sc) {
26+ QObject* parent= sc;
27+ while(parent) {
28+ if (auto item = qobject_cast<QQuickItem*>(parent)) {
29+ auto window = item->window();
30+ if (window) return window;
31+ }
32+ parent = parent->parent();
33+ }
34+ return nullptr;
35+}
36+} // namespace
25
37
26GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
38GlobalShortcutRegistry::GlobalShortcutRegistry(QObject *parent)
27 : QObject(parent)
39 : QObject(parent)
28{
40{
41+ connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &GlobalShortcutRegistry::setupFilterOnWindow);
42+ setupFilterOnWindow(qGuiApp->focusWindow());
29}
43}
30
44
31GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
45GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
@@ -91,7 +105,10 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
91 const auto shortcuts = m_shortcuts.value(seq);
105 const auto shortcuts = m_shortcuts.value(seq);
92 Q_FOREACH(const auto &shortcut, shortcuts) {
106 Q_FOREACH(const auto &shortcut, shortcuts) {
93 if (shortcut) {
107 if (shortcut) {
108+ auto window = windowForShortcut(shortcut);
109+ if (!window || window == obj) { // accept shortcut if it's not attached to a window or it's window is active.
94− qApp->sendEvent(shortcut, &eCopy);
110+ qApp->sendEvent(shortcut, &eCopy);
111+ }
95 }
112 }
96 }
113 }
97 }
114 }
@@ -102,24 +119,15 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
102 return QObject::eventFilter(obj, event);
119 return QObject::eventFilter(obj, event);
103}
120}
104
121
105−void GlobalShortcutRegistry::setupFilterOnWindow(qulonglong wid)
122+void GlobalShortcutRegistry::setupFilterOnWindow(QWindow* window)
106{
123{
107− if (wid == s_windowId) {
108− return;
109− }
110−
111 if (m_filteredWindow) {
124 if (m_filteredWindow) {
112 m_filteredWindow->removeEventFilter(this);
125 m_filteredWindow->removeEventFilter(this);
113 m_filteredWindow.clear();
126 m_filteredWindow.clear();
114− s_windowId = 0;
115 }
127 }
116
128
117− Q_FOREACH(QWindow *window, qApp->allWindows()) {
118− if (window && window->winId() == wid) {
129+ if (window) {
119− m_filteredWindow = window;
130+ m_filteredWindow = window;
120− window->installEventFilter(this);
131+ window->installEventFilter(this);
121− s_windowId = wid;
122− break;
123− }
124 }
132 }
125}
133}
plugins/GlobalShortcut/globalshortcutregistry.h+2−2
@@ -52,9 +52,9 @@ public:
52 void addShortcut(const QVariant &seq, GlobalShortcut * sc);
52 void addShortcut(const QVariant &seq, GlobalShortcut * sc);
53
53
54 /**
54 /**
55− * Sets up key events filtering on window @p wid
55+ * Sets up key events filtering on window @p window
56 */
56 */
57− void setupFilterOnWindow(qulonglong wid);
57+ void setupFilterOnWindow(QWindow* window);
58
58
59protected:
59protected:
60 bool eventFilter(QObject *obj, QEvent *event) override;
60 bool eventFilter(QObject *obj, QEvent *event) override;
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
22void UInputPlugin::registerTypes(const char *uri)
28void UInputPlugin::registerTypes(const char *uri)
23{
29{
24 Q_ASSERT(uri == QLatin1String("UInput"));
30 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
32}
plugins/WindowManager/CMakeLists.txt+28−7
@@ -1,25 +1,46 @@
1+include_directories(
2+ SYSTEM
3+ ${QTMIRSERVER_INCLUDE_DIRS}
4+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
5+)
6+
7+include_directories(
8+ ${CMAKE_CURRENT_SOURCE_DIR}
9+ ${libunity8-private_SOURCE_DIR}
10+)
11+
1set(WINDOWMANAGER_SRC
12set(WINDOWMANAGER_SRC
2 AvailableDesktopArea.cpp
13 AvailableDesktopArea.cpp
3 TopLevelWindowModel.cpp
14 TopLevelWindowModel.cpp
4 Window.cpp
15 Window.cpp
5 WindowManagerPlugin.cpp
16 WindowManagerPlugin.cpp
6 WindowMargins.cpp
17 WindowMargins.cpp
18+ Screen.cpp
19+ ScreenAttached.cpp
20+ Screens.cpp
21+ ScreensConfiguration.cpp
22+ ScreenWindow.cpp
23+ WindowManagerObjects.cpp
24+ Workspace.cpp
25+ WorkspaceManager.cpp
26+ WorkspaceModel.cpp
7 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
27 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
28+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
10 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
11 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
32 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
12− )
33+)
13
34
14add_definitions(-DWINDOWMANAGERQML_LIBRARY)
35add_definitions(-DWINDOWMANAGERQML_LIBRARY)
15
36
16−include_directories(
17− SYSTEM
18− ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
19−)
20−
21add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
37add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
22
38
39+target_link_libraries(windowmanager-qml
40+ ${QTMIRSERVER_LDFLAGS}
41+ unity8-private
42+)
43+
23qt5_use_modules(windowmanager-qml Qml Quick Gui)
44qt5_use_modules(windowmanager-qml Qml Quick Gui)
24
45
25−add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
46+add_unity8_plugin(WindowManager 1.0 WindowManager TARGETS windowmanager-qml)
plugins/WindowManager/Screen.cppnew file+316
@@ -0,0 +1,316 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Screen.h"
18+#include "Screens.h"
19+#include "WorkspaceManager.h"
20+#include "Workspace.h"
21+
22+Screen::Screen(QObject *parent)
23+ : QObject(parent)
24+{
25+}
26+
27+void Screen::connectToScreen(qtmir::Screen *screen)
28+{
29+ m_wrapped = screen;
30+ connect(screen, &qtmir::Screen::usedChanged, this, &Screen::usedChanged);
31+ connect(screen, &qtmir::Screen::nameChanged, this, &Screen::nameChanged);
32+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeChanged);
33+ connect(screen, &qtmir::Screen::outputTypeChanged, this, &Screen::outputTypeNameChanged);
34+ connect(screen, &qtmir::Screen::scaleChanged, this, &Screen::scaleChanged);
35+ connect(screen, &qtmir::Screen::formFactorChanged, this, &Screen::formFactorChanged);
36+ connect(screen, &qtmir::Screen::physicalSizeChanged, this, &Screen::physicalSizeChanged);
37+ connect(screen, &qtmir::Screen::positionChanged, this, &Screen::positionChanged);
38+ connect(screen, &qtmir::Screen::activeChanged, this, &Screen::activeChanged);
39+ connect(screen, &qtmir::Screen::currentModeIndexChanged, this, &Screen::currentModeIndexChanged);
40+ connect(screen, &qtmir::Screen::availableModesChanged, this, &Screen::availableModesChanged);
41+}
42+
43+void Screen::connectToScreen(Screen *screen)
44+{
45+ connectToScreen(screen->wrapped());
46+ connect(screen, &Screen::currentWorkspaceChanged, this, &Screen::currentWorkspaceChanged);
47+}
48+
49+void Screen::setCurrentWorkspace2(Workspace *workspace)
50+{
51+ // Make sure we use the correct concrete class. Don't want to use a Proxy.
52+ workspace->setCurrentOn(this);
53+}
54+
55+bool Screen::used() const
56+{
57+ if (!m_wrapped) return false;
58+ return m_wrapped->used();
59+}
60+
61+QString Screen::name() const
62+{
63+ if (!m_wrapped) return QString();
64+ return m_wrapped->name();
65+}
66+
67+float Screen::scale() const
68+{
69+ if (!m_wrapped) return 1.0;
70+ return m_wrapped->scale();
71+}
72+
73+QSizeF Screen::physicalSize() const
74+{
75+ if (!m_wrapped) return QSizeF();
76+ return m_wrapped->physicalSize();
77+}
78+
79+qtmir::FormFactor Screen::formFactor() const
80+{
81+ if (!m_wrapped) return qtmir::FormFactorUnknown;
82+ return m_wrapped->formFactor();
83+}
84+
85+qtmir::OutputTypes Screen::outputType() const
86+{
87+ if (!m_wrapped) return qtmir::Unknown;
88+ return m_wrapped->outputType();
89+}
90+
91+MirPowerMode Screen::powerMode() const
92+{
93+ if (!m_wrapped) return mir_power_mode_on;
94+ return m_wrapped->powerMode();
95+}
96+
97+Qt::ScreenOrientation Screen::orientation() const
98+{
99+ if (!m_wrapped) return Qt::PrimaryOrientation;
100+ return m_wrapped->orientation();
101+}
102+
103+QPoint Screen::position() const
104+{
105+ if (!m_wrapped) return QPoint();
106+ return m_wrapped->position();
107+}
108+
109+QQmlListProperty<qtmir::ScreenMode> Screen::availableModes()
110+{
111+ if (!m_wrapped) return QQmlListProperty<qtmir::ScreenMode>();
112+ return m_wrapped->availableModes();
113+}
114+
115+uint Screen::currentModeIndex() const
116+{
117+ if (!m_wrapped) return -1;
118+ return m_wrapped->currentModeIndex();
119+}
120+
121+bool Screen::isActive() const
122+{
123+ if (!m_wrapped) return false;
124+ return m_wrapped->isActive();
125+}
126+
127+void Screen::activate()
128+{
129+ setActive(true);
130+}
131+
132+void Screen::setActive(bool active)
133+{
134+ if (!m_wrapped) return;
135+ m_wrapped->setActive(active);
136+}
137+
138+QScreen *Screen::qscreen() const
139+{
140+ if (!m_wrapped) return nullptr;
141+ return m_wrapped->qscreen();
142+}
143+
144+ScreenConfig *Screen::beginConfiguration() const
145+{
146+ if (!m_wrapped) return nullptr;
147+ return new ScreenConfig(m_wrapped->beginConfiguration());
148+}
149+
150+bool Screen::applyConfiguration(ScreenConfig *configuration)
151+{
152+ if (!m_wrapped) return false;
153+ return m_wrapped->applyConfiguration(configuration->m_config);
154+}
155+
156+QString Screen::outputTypeName() const
157+{
158+ switch (m_wrapped->outputType()) {
159+ case qtmir::Unknown:
160+ return tr("Unknown");
161+ case qtmir::VGA:
162+ return tr("VGA");
163+ case qtmir::DVII:
164+ case qtmir::DVID:
165+ case qtmir::DVIA:
166+ return tr("DVI");
167+ case qtmir::Composite:
168+ return tr("Composite");
169+ case qtmir::SVideo:
170+ return tr("S-Video");
171+ case qtmir::LVDS:
172+ case qtmir::NinePinDIN:
173+ case qtmir::EDP:
174+ return tr("Internal");
175+ case qtmir::Component:
176+ return tr("Component");
177+ case qtmir::DisplayPort:
178+ return tr("DisplayPort");
179+ case qtmir::HDMIA:
180+ case qtmir::HDMIB:
181+ return tr("HDMI");
182+ case qtmir::TV:
183+ return tr("TV");
184+ }
185+ return QString();
186+}
187+
188+bool Screen::isSameAs(Screen *screen) const
189+{
190+ if (!screen) return false;
191+ if (screen == this) return true;
192+ return wrapped() == screen->wrapped();
193+}
194+
195+void Screen::sync(Screen *proxy)
196+{
197+ if (!proxy) return;
198+ workspaces()->sync(proxy->workspaces());
199+}
200+
201+ConcreteScreen::ConcreteScreen(qtmir::Screen* wrapped)
202+ : m_workspaces(new WorkspaceModel)
203+{
204+ connectToScreen(wrapped);
205+
206+ // Connect the active workspace to activate the screen.
207+ connect(m_workspaces.data(), &WorkspaceModel::workspaceInserted, this, [this](int, Workspace* workspace) {
208+ connect(workspace, &Workspace::activeChanged, this, [this, workspace](bool active) {
209+ if (active) {
210+ setCurrentWorkspace(workspace);
211+ activate();
212+ }
213+ });
214+ if (workspace->isActive()) {
215+ activate();
216+ setCurrentWorkspace(workspace);
217+ }
218+ if (!m_currentWorspace) {
219+ setCurrentWorkspace(workspace);
220+ }
221+ });
222+ connect(m_workspaces.data(), &WorkspaceModel::workspaceRemoved, this, [this](Workspace* workspace) {
223+ disconnect(workspace, &Workspace::activeChanged, this, 0);
224+ if (workspace == m_currentWorspace) {
225+ resetCurrentWorkspace();
226+ }
227+ });
228+ connect(this, &ConcreteScreen::activeChanged, this, [this](bool active) {
229+ if (active && m_currentWorspace) {
230+ m_currentWorspace->activate();
231+ }
232+ });
233+}
234+
235+void ConcreteScreen::resetCurrentWorkspace()
236+{
237+ auto newCurrent = m_workspaces->rowCount() > 0 ? m_workspaces->get(0) : nullptr;
238+ if (m_currentWorspace != newCurrent) {
239+ m_currentWorspace = newCurrent;
240+ Q_EMIT currentWorkspaceChanged(newCurrent);
241+ }
242+}
243+
244+
245+WorkspaceModel *ConcreteScreen::workspaces() const
246+{
247+ return m_workspaces.data();
248+}
249+
250+Workspace *ConcreteScreen::currentWorkspace() const
251+{
252+ return m_currentWorspace.data();
253+}
254+
255+void ConcreteScreen::setCurrentWorkspace(Workspace *workspace)
256+{
257+ if (m_currentWorspace != workspace) {
258+ m_currentWorspace = workspace;
259+ Q_EMIT currentWorkspaceChanged(workspace);
260+ }
261+}
262+
263+ProxyScreen::ProxyScreen(Screen *const screen, ProxyScreens* screens)
264+ : m_workspaces(new ProxyWorkspaceModel(screen->workspaces(), this))
265+ , m_original(screen)
266+ , m_screens(screens)
267+{
268+ connectToScreen(screen);
269+
270+ auto updateCurrentWorkspaceFn = [this](Workspace* realWorkspace) {
271+ Q_FOREACH(Workspace* workspace, m_workspaces->list()) {
272+ auto p = qobject_cast<ProxyWorkspace*>(workspace);
273+ if (p && p->proxyObject() == realWorkspace) {
274+ if (m_currentWorspace != p) {
275+ m_currentWorspace = p;
276+ Q_EMIT currentWorkspaceChanged(p);
277+ }
278+ }
279+ }
280+ };
281+ connect(screen, &Screen::currentWorkspaceChanged, this, updateCurrentWorkspaceFn);
282+ updateCurrentWorkspaceFn(screen->currentWorkspace());
283+}
284+
285+WorkspaceModel *ProxyScreen::workspaces() const
286+{
287+ return m_workspaces.data();
288+}
289+
290+Workspace *ProxyScreen::currentWorkspace() const
291+{
292+ return m_currentWorspace.data();
293+}
294+
295+void ProxyScreen::setCurrentWorkspace(Workspace *workspace)
296+{
297+ auto p = qobject_cast<ProxyWorkspace*>(workspace);
298+ if (p) {
299+ m_original->setCurrentWorkspace(p->proxyObject());
300+ }
301+}
302+
303+bool ProxyScreen::isSyncing() const
304+{
305+ return m_screens->isSyncing();
306+}
307+
308+ScreenConfig::ScreenConfig(qtmir::ScreenConfiguration *config)
309+ : m_config(config)
310+{
311+}
312+
313+ScreenConfig::~ScreenConfig()
314+{
315+ delete m_config;
316+}
plugins/WindowManager/Screen.hnew file+153
@@ -0,0 +1,153 @@
1+#ifndef SCREEN_H
2+#define SCREEN_H
3+
4+#include <qtmir/screen.h>
5+#include <QScopedPointer>
6+#include <QPointer>
7+
8+#include "WorkspaceModel.h"
9+
10+class ProxyScreen;
11+class ProxyScreens;
12+class ScreenConfig;
13+
14+class Screen: public QObject
15+{
16+ Q_OBJECT
17+
18+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
19+ Q_PROPERTY(bool used READ used NOTIFY usedChanged)
20+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
21+ Q_PROPERTY(qtmir::OutputTypes outputType READ outputType NOTIFY outputTypeChanged)
22+ Q_PROPERTY(float scale READ scale NOTIFY scaleChanged)
23+ Q_PROPERTY(qtmir::FormFactor formFactor READ formFactor NOTIFY formFactorChanged)
24+ Q_PROPERTY(MirPowerMode powerMode READ powerMode NOTIFY powerModeChanged)
25+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged)
26+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
27+ Q_PROPERTY(uint currentModeIndex READ currentModeIndex NOTIFY currentModeIndexChanged)
28+ Q_PROPERTY(QQmlListProperty<qtmir::ScreenMode> availableModes READ availableModes NOTIFY availableModesChanged)
29+ Q_PROPERTY(QSizeF physicalSize READ physicalSize NOTIFY physicalSizeChanged)
30+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeChanged)
31+ Q_PROPERTY(WorkspaceModel* workspaces READ workspaces CONSTANT)
32+ Q_PROPERTY(Workspace* currentWorkspace READ currentWorkspace WRITE setCurrentWorkspace2 NOTIFY currentWorkspaceChanged)
33+public:
34+ bool used() const;
35+ QString name() const;
36+ float scale() const;
37+ QSizeF physicalSize() const;
38+ qtmir::FormFactor formFactor() const;
39+ qtmir::OutputTypes outputType() const;
40+ MirPowerMode powerMode() const;
41+ Qt::ScreenOrientation orientation() const;
42+ QPoint position() const;
43+ QQmlListProperty<qtmir::ScreenMode> availableModes();
44+ uint currentModeIndex() const;
45+ bool isActive() const;
46+ void setActive(bool active);
47+ QScreen* qscreen() const;
48+ QString outputTypeName() const;
49+
50+ Q_INVOKABLE bool isSameAs(Screen*) const;
51+
52+ Q_INVOKABLE ScreenConfig *beginConfiguration() const;
53+ Q_INVOKABLE bool applyConfiguration(ScreenConfig *configuration);
54+
55+ virtual WorkspaceModel* workspaces() const = 0;
56+ virtual Workspace *currentWorkspace() const = 0;
57+ virtual void setCurrentWorkspace(Workspace* workspace) = 0;
58+
59+ void sync(Screen* proxy);
60+
61+ qtmir::Screen* wrapped() const { return m_wrapped; }
62+
63+public Q_SLOTS:
64+ void activate();
65+
66+Q_SIGNALS:
67+ void usedChanged();
68+ void nameChanged();
69+ void outputTypeChanged();
70+ void outputTypeNameChanged();
71+ void scaleChanged();
72+ void formFactorChanged();
73+ void powerModeChanged();
74+ void orientationChanged();
75+ void positionChanged();
76+ void currentModeIndexChanged();
77+ void physicalSizeChanged();
78+ void availableModesChanged();
79+ void activeChanged(bool active);
80+ void currentWorkspaceChanged(Workspace*);
81+
82+protected:
83+ Screen(QObject* parent = 0);
84+
85+ void connectToScreen(qtmir::Screen* screen);
86+ void connectToScreen(Screen* screen);
87+
88+private:
89+ void setCurrentWorkspace2(Workspace* workspace);
90+
91+protected:
92+ QPointer<qtmir::Screen> m_wrapped;
93+};
94+
95+
96+class ConcreteScreen : public Screen
97+{
98+ Q_OBJECT
99+public:
100+ explicit ConcreteScreen(qtmir::Screen*const wrapped);
101+
102+ // From qtmir::Screen
103+ WorkspaceModel* workspaces() const override;
104+ Workspace *currentWorkspace() const override;
105+ void setCurrentWorkspace(Workspace* workspace) override;
106+
107+protected:
108+ void resetCurrentWorkspace();
109+
110+ const QScopedPointer<WorkspaceModel> m_workspaces;
111+ QPointer<Workspace> m_currentWorspace;
112+};
113+
114+class ProxyScreen : public Screen
115+{
116+ Q_OBJECT
117+public:
118+ explicit ProxyScreen(Screen*const screen, ProxyScreens* screens);
119+
120+ // From qtmir::Screen
121+ WorkspaceModel* workspaces() const override;
122+ Workspace *currentWorkspace() const override;
123+ void setCurrentWorkspace(Workspace* workspace) override;
124+
125+ Screen* proxyObject() const { return m_original.data(); }
126+
127+ bool isSyncing() const;
128+
129+private:
130+ const QScopedPointer<WorkspaceModel> m_workspaces;
131+ const QPointer<Screen> m_original;
132+ const ProxyScreens* m_screens;
133+ QPointer<Workspace> m_currentWorspace;
134+};
135+
136+class ScreenConfig: public QObject
137+{
138+ Q_OBJECT
139+ Q_PRIVATE_PROPERTY(m_config, bool valid MEMBER used CONSTANT)
140+ Q_PRIVATE_PROPERTY(m_config, bool used MEMBER used)
141+ Q_PRIVATE_PROPERTY(m_config, float scale MEMBER scale)
142+ Q_PRIVATE_PROPERTY(m_config, qtmir::FormFactor formFactor MEMBER formFactor)
143+ Q_PRIVATE_PROPERTY(m_config, uint currentModeIndex MEMBER currentModeIndex)
144+ Q_PRIVATE_PROPERTY(m_config, QPoint position MEMBER topLeft)
145+
146+public:
147+ ScreenConfig(qtmir::ScreenConfiguration*);
148+ ~ScreenConfig();
149+
150+ qtmir::ScreenConfiguration* m_config;
151+};
152+
153+#endif // SCREEN_H
plugins/WindowManager/ScreenAttached.cppnew file+164
@@ -0,0 +1,164 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ScreenAttached.h"
18+#include "ScreenWindow.h"
19+#include "Screens.h"
20+
21+#include <QQuickItem>
22+#include <QScreen>
23+
24+namespace
25+{
26+QQuickItem* itemForOwner(QObject* obj) {
27+ QObject* parent = obj;
28+ while(parent) {
29+ auto item = qobject_cast<QQuickItem*>(parent);
30+ if (item) return item;
31+ parent = parent->parent();
32+ }
33+ return nullptr;
34+}
35+} // namesapce
36+
37+ScreenAttached::ScreenAttached(QObject *owner)
38+ : Screen(owner)
39+ , m_window(nullptr)
40+{
41+ if (auto item = itemForOwner(owner)) {
42+ connect(item, &QQuickItem::windowChanged, this, &ScreenAttached::windowChanged);
43+ windowChanged(item->window());
44+ } else if (auto window = qobject_cast<QQuickWindow*>(owner)) {
45+ windowChanged(window);
46+ }
47+}
48+
49+WorkspaceModel *ScreenAttached::workspaces() const
50+{
51+ if (!m_screen) return nullptr;
52+ return m_screen->workspaces();
53+}
54+
55+Workspace *ScreenAttached::currentWorkspace() const
56+{
57+ if (!m_screen) return nullptr;
58+ return m_screen->currentWorkspace();
59+}
60+
61+void ScreenAttached::setCurrentWorkspace(Workspace *workspace)
62+{
63+ if (!m_screen) return;
64+ return m_screen->setCurrentWorkspace(workspace);
65+}
66+
67+void ScreenAttached::windowChanged(QQuickWindow *window)
68+{
69+ if (m_window) {
70+ disconnect(m_window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
71+ }
72+
73+ m_window = window;
74+ auto screenWindow = qobject_cast<ScreenWindow*>(window);
75+
76+ if (screenWindow) {
77+ screenChanged2(screenWindow->screenWrapper());
78+ connect(screenWindow, &ScreenWindow::screenWrapperChanged, this, &ScreenAttached::screenChanged2);
79+ } else {
80+ screenChanged(window ? window->screen() : NULL);
81+ if (window) {
82+ connect(window, &QWindow::screenChanged, this, &ScreenAttached::screenChanged);
83+ }
84+ }
85+}
86+
87+void ScreenAttached::screenChanged(QScreen *qscreen)
88+{
89+ // Find a screen that matches.
90+ // Should only get here in mocks if we don't have a ScreenWindow
91+ Screen* screen{nullptr};
92+ Q_FOREACH(auto s, ConcreteScreens::self()->list()) {
93+ if (s->qscreen() == qscreen) {
94+ screen = s;
95+ }
96+ }
97+ screenChanged2(screen);
98+}
99+
100+void ScreenAttached::screenChanged2(Screen* screen)
101+{
102+ if (screen == m_screen) return;
103+
104+ Screen* oldScreen = m_screen;
105+ m_screen = screen;
106+
107+ if (oldScreen)
108+ oldScreen->disconnect(this);
109+
110+ if (!screen)
111+ return; //Don't bother emitting signals, because the new values are garbage anyways
112+
113+ if (!oldScreen || screen->isActive() != oldScreen->isActive())
114+ Q_EMIT activeChanged(screen->isActive());
115+ if (!oldScreen || screen->used() != oldScreen->used())
116+ Q_EMIT usedChanged();
117+ if (!oldScreen || screen->name() != oldScreen->name())
118+ Q_EMIT nameChanged();
119+ if (!oldScreen || screen->outputType() != oldScreen->outputType())
120+ Q_EMIT outputTypeChanged();
121+ if (!oldScreen || screen->scale() != oldScreen->scale())
122+ Q_EMIT scaleChanged();
123+ if (!oldScreen || screen->formFactor() != oldScreen->formFactor())
124+ Q_EMIT formFactorChanged();
125+ if (!oldScreen || screen->powerMode() != oldScreen->powerMode())
126+ Q_EMIT powerModeChanged();
127+ if (!oldScreen || screen->orientation() != oldScreen->orientation())
128+ Q_EMIT orientationChanged();
129+ if (!oldScreen || screen->position() != oldScreen->position())
130+ Q_EMIT positionChanged();
131+ if (!oldScreen || screen->currentModeIndex() != oldScreen->currentModeIndex())
132+ Q_EMIT currentModeIndexChanged();
133+ if (!oldScreen || screen->physicalSize() != oldScreen->physicalSize())
134+ Q_EMIT physicalSizeChanged();
135+ if (!oldScreen || screen->currentWorkspace() != oldScreen->currentWorkspace())
136+ Q_EMIT currentWorkspaceChanged(currentWorkspace());
137+
138+ if (oldScreen) {
139+ QVector<qtmir::ScreenMode*> oldModes;
140+ auto oldModesQmlList = oldScreen->availableModes();
141+ for (int i = 0; i < oldModesQmlList.count(&oldModesQmlList); i++) {
142+ oldModes << oldModesQmlList.at(&oldModesQmlList, i);
143+ }
144+
145+ QVector<qtmir::ScreenMode*> newModes;
146+ auto newModesQmlList = screen->availableModes();
147+ for (int i = 0; i < newModesQmlList.count(&newModesQmlList); i++) {
148+ newModes << newModesQmlList.at(&newModesQmlList, i);
149+ }
150+
151+ if (newModes != newModes) {
152+ Q_EMIT availableModesChanged();
153+ }
154+ } else {
155+ Q_EMIT availableModesChanged();
156+ }
157+
158+ connectToScreen(screen);
159+}
160+
161+ScreenAttached *WMScreen::qmlAttachedProperties(QObject *owner)
162+{
163+ return new ScreenAttached(owner);
164+}
plugins/WindowManager/ScreenAttached.hnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef SCREENATTACHED_H
18+#define SCREENATTACHED_H
19+
20+#include "Screen.h"
21+
22+#include <QQmlEngine>
23+#include <QtQml>
24+
25+class QQuickWindow;
26+
27+class ScreenAttached : public Screen
28+{
29+ Q_OBJECT
30+public:
31+ ScreenAttached(QObject* owner);
32+
33+ WorkspaceModel* workspaces() const override;
34+ Workspace *currentWorkspace() const override;
35+ void setCurrentWorkspace(Workspace* workspace) override;
36+
37+private Q_SLOTS:
38+ void windowChanged(QQuickWindow*);
39+ void screenChanged(QScreen*);
40+ void screenChanged2(Screen* screen);
41+
42+private:
43+ QPointer<Screen> m_screen;
44+ QQuickWindow* m_window;
45+};
46+
47+class WMScreen : public QObject
48+{
49+ Q_OBJECT
50+public:
51+ static ScreenAttached *qmlAttachedProperties(QObject *owner);
52+};
53+
54+QML_DECLARE_TYPEINFO(WMScreen, QML_HAS_ATTACHED_PROPERTIES)
55+
56+#endif // SCREENATTACHED_H
plugins/WindowManager/ScreenWindow.cppnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ScreenWindow.h"
18+#include "Screen.h"
19+
20+// Qt
21+#include <QGuiApplication>
22+#include <QDebug>
23+
24+ScreenWindow::ScreenWindow(QQuickWindow *parent)
25+ : QQuickWindow(parent)
26+{
27+}
28+
29+ScreenWindow::~ScreenWindow()
30+{
31+}
32+
33+ConcreteScreen *ScreenWindow::screenWrapper() const
34+{
35+ return m_screen.data();
36+}
37+
38+void ScreenWindow::setScreenWrapper(ConcreteScreen *screen)
39+{
40+ if (m_screen != screen) {
41+ m_screen = screen;
42+ Q_EMIT screenWrapperChanged(screen);
43+ }
44+ QQuickWindow::setScreen(screen->qscreen());
45+}
plugins/WindowManager/ScreenWindow.hnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_SCREENWINDOW_H
18+#define UNITY_SCREENWINDOW_H
19+
20+#include <QQuickWindow>
21+#include <QPointer>
22+
23+#include "Screen.h"
24+
25+class ScreenAdapter;
26+
27+/*
28+ * ScreenWindow - wrapper of QQuickWindow to enable QML to specify destination screen.
29+**/
30+class ScreenWindow : public QQuickWindow
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(ConcreteScreen *screen READ screenWrapper WRITE setScreenWrapper NOTIFY screenWrapperChanged)
34+ Q_PROPERTY(int winId READ winId CONSTANT)
35+public:
36+ explicit ScreenWindow(QQuickWindow *parent = 0);
37+ ~ScreenWindow();
38+
39+ ConcreteScreen *screenWrapper() const;
40+ void setScreenWrapper(ConcreteScreen *screen);
41+
42+Q_SIGNALS:
43+ void screenWrapperChanged(ConcreteScreen* screen);
44+
45+private:
46+ QPointer<ConcreteScreen> m_screen;
47+};
48+
49+#endif // UNITY_SCREENWINDOW_H
plugins/WindowManager/Screens.cppnew file+245
@@ -0,0 +1,245 @@
1+/*
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Screens.h"
18+#include "ScreensConfiguration.h"
19+#include "Screen.h"
20+#include "WorkspaceManager.h"
21+
22+// qtmirserver
23+#include <qtmir/screens.h>
24+#include <QGuiApplication>
25+#include <QQmlEngine>
26+
27+// Qt
28+#include <QScreen>
29+#include <QWindow>
30+
31+ConcreteScreens* ConcreteScreens::m_self{nullptr};
32+
33+Screens::Screens(const QSharedPointer<qtmir::Screens>& model)
34+ : m_wrapped(model)
35+{
36+}
37+
38+Screens::~Screens()
39+{
40+ qDeleteAll(m_screens);
41+ m_screens.clear();
42+}
43+
44+QHash<int, QByteArray> Screens::roleNames() const
45+{
46+ QHash<int, QByteArray> roles;
47+ roles[ScreenRole] = "screen";
48+ return roles;
49+}
50+
51+QVariant Screens::data(const QModelIndex &index, int role) const
52+{
53+ if (!index.isValid() || index.row() >= m_screens.size()) {
54+ return QVariant();
55+ }
56+
57+ switch(role) {
58+ case ScreenRole:
59+ return QVariant::fromValue(m_screens.at(index.row()));
60+ } // switch
61+
62+ return QVariant();
63+}
64+
65+int Screens::rowCount(const QModelIndex &) const
66+{
67+ return count();
68+}
69+
70+int Screens::indexOf(Screen *screen) const
71+{
72+ return m_screens.indexOf(screen);
73+}
74+
75+Screen *Screens::get(int index) const
76+{
77+ return m_screens.at(index);
78+}
79+
80+int Screens::count() const
81+{
82+ return m_screens.size();
83+}
84+
85+QVariant Screens::activeScreen() const
86+{
87+ for (int i = 0; i < m_screens.count(); i++) {
88+ if (m_screens[i]->isActive()) return i;
89+ }
90+ return QVariant();
91+}
92+
93+void Screens::activateScreen(const QVariant& vindex)
94+{
95+ bool ok = false;
96+ int index = vindex.toInt(&ok);
97+ if (!ok || index < 0 || m_screens.count() <= index) return;
98+
99+ auto screen = m_screens.at(index);
100+ screen->setActive(true);
101+}
102+
103+
104+ConcreteScreens::ConcreteScreens(const QSharedPointer<qtmir::Screens> &model, ScreensConfiguration* config)
105+ : Screens(model)
106+ , m_config(config)
107+{
108+ m_self = this;
109+ connect(m_wrapped.data(), &qtmir::Screens::screenAdded, this, &ConcreteScreens::onScreenAdded);
110+ connect(m_wrapped.data(), &qtmir::Screens::screenRemoved, this, &ConcreteScreens::onScreenRemoved);
111+ connect(m_wrapped.data(), &qtmir::Screens::activeScreenChanged, this, &ConcreteScreens::activeScreenChanged);
112+
113+ Q_FOREACH(qtmir::Screen* screen, m_wrapped->screens()) {
114+ auto screenWrapper(new ConcreteScreen(screen));
115+ m_config->load(screenWrapper);
116+
117+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
118+ m_screens.push_back(screenWrapper);
119+ }
120+}
121+
122+ConcreteScreens::~ConcreteScreens()
123+{
124+ Q_FOREACH(Screen* screen, m_screens) {
125+ m_config->save(screen);
126+ }
127+ delete m_config;
128+}
129+
130+ConcreteScreens *ConcreteScreens::self()
131+{
132+ return ConcreteScreens::m_self;
133+}
134+
135+ProxyScreens *ConcreteScreens::createProxy()
136+{
137+ return new ProxyScreens(this);
138+}
139+
140+void ConcreteScreens::sync(ProxyScreens *proxy)
141+{
142+ if (!proxy) return;
143+ proxy->setSyncing(true);
144+
145+ const auto& proxyList = proxy->list();
146+ for (int i = 0; i < m_screens.count() && i < proxyList.count(); ++i) {
147+ m_screens[i]->sync(proxyList[i]);
148+ }
149+
150+ proxy->setSyncing(false);
151+}
152+
153+void ConcreteScreens::onScreenAdded(qtmir::Screen *added)
154+{
155+ Q_FOREACH(auto screenWrapper, m_screens) {
156+ if (screenWrapper->wrapped() == added) return;
157+ }
158+
159+ beginInsertRows(QModelIndex(), count(), count());
160+ auto screenWrapper(new ConcreteScreen(added));
161+ m_config->load(screenWrapper);
162+
163+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
164+ m_screens.push_back(screenWrapper);
165+ endInsertRows();
166+ Q_EMIT screenAdded(screenWrapper);
167+ Q_EMIT countChanged();
168+}
169+
170+void ConcreteScreens::onScreenRemoved(qtmir::Screen *removed)
171+{
172+ int index = 0;
173+ QMutableVectorIterator<Screen*> iter(m_screens);
174+ while(iter.hasNext()) {
175+ auto screenWrapper = iter.next();
176+ if (screenWrapper->wrapped() == removed) {
177+ m_config->save(screenWrapper);
178+
179+ beginRemoveRows(QModelIndex(), index, index);
180+ iter.remove();
181+ endRemoveRows();
182+
183+ Q_EMIT screenRemoved(screenWrapper);
184+ Q_EMIT countChanged();
185+
186+ screenWrapper->deleteLater();
187+ break;
188+ }
189+ index++;
190+ }
191+}
192+
193+
194+ProxyScreens::ProxyScreens(Screens * const screens)
195+ : Screens(screens->m_wrapped)
196+ , m_original(screens)
197+ , m_syncing(false)
198+{
199+ connect(screens, &Screens::screenAdded, this, [this](Screen *added) {
200+ Q_FOREACH(auto screen, m_screens) {
201+ auto proxy = static_cast<ProxyScreen*>(screen);
202+ if (proxy->proxyObject() == added) return;
203+ }
204+
205+ beginInsertRows(QModelIndex(), count(), count());
206+ auto screenWrapper(new ProxyScreen(added, this));
207+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
208+ m_screens.push_back(screenWrapper);
209+ endInsertRows();
210+ Q_EMIT screenAdded(screenWrapper);
211+ Q_EMIT countChanged();
212+ });
213+
214+ connect(screens, &Screens::screenRemoved, this, [this](Screen *removed) {
215+ int index = 0;
216+ QMutableVectorIterator<Screen*> iter(m_screens);
217+ while(iter.hasNext()) {
218+ auto proxy = static_cast<ProxyScreen*>(iter.next());
219+ if (proxy->proxyObject() == removed) {
220+
221+ beginRemoveRows(QModelIndex(), index, index);
222+ iter.remove();
223+ endRemoveRows();
224+
225+ Q_EMIT screenRemoved(proxy);
226+ Q_EMIT countChanged();
227+
228+ delete proxy;
229+ break;
230+ }
231+ index++;
232+ }
233+ });
234+
235+ Q_FOREACH(Screen* screen, screens->list()) {
236+ auto screenWrapper(new ProxyScreen(screen, this));
237+ QQmlEngine::setObjectOwnership(screenWrapper, QQmlEngine::CppOwnership);
238+ m_screens.push_back(screenWrapper);
239+ }
240+}
241+
242+void ProxyScreens::setSyncing(bool syncing)
243+{
244+ m_syncing = syncing;
245+}
plugins/WindowManager/Screens.hnew file+114
@@ -0,0 +1,114 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_SCREENS_H
18+#define UNITY_SCREENS_H
19+
20+#include <QAbstractListModel>
21+#include <QSharedPointer>
22+#include <QPointer>
23+
24+namespace qtmir
25+{
26+class Screen;
27+class Screens;
28+}
29+
30+class Screen;
31+class ProxyScreens;
32+class ScreensConfiguration;
33+
34+class Screens : public QAbstractListModel
35+{
36+ Q_OBJECT
37+ Q_PROPERTY(int count READ count NOTIFY countChanged)
38+ Q_PROPERTY(QVariant activeScreen READ activeScreen WRITE activateScreen NOTIFY activeScreenChanged)
39+
40+public:
41+ enum ItemRoles {
42+ ScreenRole = Qt::UserRole + 1
43+ };
44+
45+ virtual ~Screens();
46+
47+ /* QAbstractItemModel */
48+ QHash<int, QByteArray> roleNames() const override;
49+ QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
50+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
51+
52+ Q_INVOKABLE int indexOf(Screen*) const;
53+ Q_INVOKABLE Screen* get(int index) const;
54+
55+ int count() const;
56+ QVariant activeScreen() const;
57+
58+ const QVector<Screen*>& list() const { return m_screens; }
59+
60+public Q_SLOTS:
61+ void activateScreen(const QVariant& index);
62+
63+Q_SIGNALS:
64+ void countChanged();
65+ void activeScreenChanged();
66+
67+ void screenAdded(Screen* screen);
68+ void screenRemoved(Screen* screen);
69+
70+protected:
71+ Screens(const QSharedPointer<qtmir::Screens>& model);
72+
73+ QVector<Screen*> m_screens;
74+ const QSharedPointer<qtmir::Screens> m_wrapped;
75+
76+ friend class ProxyScreens;
77+};
78+
79+class ConcreteScreens : public Screens
80+{
81+ Q_OBJECT
82+public:
83+ explicit ConcreteScreens(const QSharedPointer<qtmir::Screens>& model, ScreensConfiguration* config);
84+ ~ConcreteScreens();
85+
86+ Q_INVOKABLE ProxyScreens *createProxy();
87+ Q_INVOKABLE void sync(ProxyScreens *proxy);
88+
89+ static ConcreteScreens *self();
90+
91+protected Q_SLOTS:
92+ void onScreenAdded(qtmir::Screen *screen);
93+ void onScreenRemoved(qtmir::Screen *screen);
94+
95+private:
96+ ScreensConfiguration* m_config;
97+
98+ static ConcreteScreens* m_self;
99+};
100+
101+class ProxyScreens : public Screens
102+{
103+public:
104+ explicit ProxyScreens(Screens*const screens);
105+
106+ void setSyncing(bool syncing);
107+ bool isSyncing() const { return m_syncing; }
108+
109+private:
110+ const QPointer<Screens> m_original;
111+ bool m_syncing;
112+};
113+
114+#endif // SCREENS_H
plugins/WindowManager/ScreensConfiguration.cppnew file+94
@@ -0,0 +1,94 @@
1+/*
2+ * Copyright (C) 2017 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 "ScreensConfiguration.h"
18+#include "Screen.h"
19+#include "Workspace.h"
20+#include "WorkspaceManager.h"
21+
22+#include <QJsonObject>
23+#include <QJsonDocument>
24+#include <QJsonArray>
25+#include <QFile>
26+#include <QStandardPaths>
27+
28+namespace
29+{
30+QJsonArray jsonScreens;
31+}
32+
33+ScreensConfiguration::ScreensConfiguration()
34+{
35+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
36+ QFile f(dbPath + "workspaces");
37+
38+ if (f.open(QIODevice::ReadOnly)) {
39+ QByteArray saveData = f.readAll();
40+ QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
41+ QJsonObject json(loadDoc.object());
42+ jsonScreens = json["screens"].toArray();
43+ }
44+}
45+
46+ScreensConfiguration::~ScreensConfiguration()
47+{
48+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
49+ QFile f(dbPath + "workspaces");
50+ if (f.open(QIODevice::WriteOnly)) {
51+ QJsonObject json;
52+ json["screens"] = jsonScreens;
53+ QJsonDocument saveDoc(json);
54+ f.write(saveDoc.toJson());
55+ }
56+}
57+
58+void ScreensConfiguration::load(Screen *screen)
59+{
60+ int workspaces = 2;
61+ for (auto iter = jsonScreens.begin(); iter != jsonScreens.end(); ++iter) {
62+ QJsonObject jsonScreen = (*iter).toObject();
63+ if (jsonScreen["name"] == screen->name()) {
64+ QJsonValue jsonWorkspaces = jsonScreen["workspaces"];
65+ workspaces = qMax(jsonWorkspaces.toInt(workspaces), 1);
66+ break;
67+ }
68+ }
69+
70+ for (int i = 0; i < workspaces; i++) {
71+ WorkspaceManager::instance()->createWorkspace()->assign(screen->workspaces());
72+ }
73+}
74+
75+void ScreensConfiguration::save(Screen *screen)
76+{
77+ QJsonObject newJsonScreen;
78+ newJsonScreen["name"] = screen->name();
79+ newJsonScreen["workspaces"] = qMax(screen->workspaces()->rowCount(), 1);
80+
81+ auto iter = jsonScreens.begin();
82+ for (; iter != jsonScreens.end(); ++iter) {
83+ QJsonObject jsonScreen = (*iter).toObject();
84+ if (jsonScreen["name"] == screen->name()) {
85+ break;
86+ }
87+ }
88+
89+ if (iter == jsonScreens.end()) {
90+ jsonScreens.push_back(newJsonScreen);
91+ } else {
92+ *iter = newJsonScreen;
93+ }
94+}
src/ShellView.h → plugins/WindowManager/ScreensConfiguration.hrenamed+10−12
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_SHELL_VIEW_H
17+#ifndef SCREENSCONFIGURATION_H
18−#define UNITY_SHELL_VIEW_H
18+#define SCREENSCONFIGURATION_H
19
19
20−#include <QQuickView>
20+class Screen;
21
21
22−class ShellView : public QQuickView
22+class ScreensConfiguration
23{
23{
24− Q_OBJECT
25−
26public:
24public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
25+ explicit ScreensConfiguration();
26+ ~ScreensConfiguration();
28
27
29−private Q_SLOTS:
28+ void load(Screen* screen);
30− void onWidthChanged(int);
29+ void save(Screen* screen);
31− void onHeightChanged(int);
32};
30};
33
31
34−#endif // UNITY_SHELL_VIEW_H
32+#endif // SCREENSCONFIGURATION_H
plugins/WindowManager/TopLevelWindowModel.cpp+207−75
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include "TopLevelWindowModel.h"
17#include "TopLevelWindowModel.h"
18+#include "WindowManagerObjects.h"
18
19
19// unity-api
20// unity-api
20#include <unity/shell/application/ApplicationInfoInterface.h>
21#include <unity/shell/application/ApplicationInfoInterface.h>
@@ -24,11 +25,11 @@
24#include <unity/shell/application/SurfaceManagerInterface.h>
25#include <unity/shell/application/SurfaceManagerInterface.h>
25
26
26// Qt
27// Qt
27−#include <QGuiApplication>
28#include <QDebug>
28#include <QDebug>
29
29
30// local
30// local
31#include "Window.h"
31#include "Window.h"
32+#include "Workspace.h"
32
33
33Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
34
35
@@ -37,8 +38,20 @@ Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
37
38
38namespace unityapi = unity::shell::application;
39namespace unityapi = unity::shell::application;
39
40
40−TopLevelWindowModel::TopLevelWindowModel()
41+TopLevelWindowModel::TopLevelWindowModel(Workspace* workspace)
41− : m_nullWindow(createWindow(nullptr))
42+ : m_nullWindow(createWindow(nullptr)),
43+ m_workspace(workspace)
44+{
45+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::applicationManagerChanged,
46+ this, &TopLevelWindowModel::setApplicationManager);
47+ connect(WindowManagerObjects::instance(), &WindowManagerObjects::surfaceManagerChanged,
48+ this, &TopLevelWindowModel::setSurfaceManager);
49+
50+ setApplicationManager(WindowManagerObjects::instance()->applicationManager());
51+ setSurfaceManager(WindowManagerObjects::instance()->surfaceManager());
52+}
53+
54+TopLevelWindowModel::~TopLevelWindowModel()
42{
55{
43}
56}
44
57
@@ -56,7 +69,6 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
56 beginResetModel();
69 beginResetModel();
57
70
58 if (m_applicationManager) {
71 if (m_applicationManager) {
59− m_windowModel.clear();
60 disconnect(m_applicationManager, 0, this, 0);
72 disconnect(m_applicationManager, 0, this, 0);
61 }
73 }
62
74
@@ -65,26 +77,26 @@ void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInte
65 if (m_applicationManager) {
77 if (m_applicationManager) {
66 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
78 connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
67 this, [this](const QModelIndex &/*parent*/, int first, int last) {
79 this, [this](const QModelIndex &/*parent*/, int first, int last) {
80+ if (!m_workspace || !m_workspace->isActive())
81+ return;
82+
68− for (int i = first; i <= last; ++i) {
83+ for (int i = first; i <= last; ++i) {
69− auto application = m_applicationManager->get(i);
84+ auto application = m_applicationManager->get(i);
70− addApplication(application);
85+ addApplication(application);
71− }
86+ }
72− });
87+ });
73
88
74 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
89 connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
75 this, [this](const QModelIndex &/*parent*/, int first, int last) {
90 this, [this](const QModelIndex &/*parent*/, int first, int last) {
76− for (int i = first; i <= last; ++i) {
91+ for (int i = first; i <= last; ++i) {
77− auto application = m_applicationManager->get(i);
92+ auto application = m_applicationManager->get(i);
78− removeApplication(application);
93+ removeApplication(application);
79− }
80− });
94+ }
81−
95+ });
82− for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
83− auto application = m_applicationManager->get(i);
84− addApplication(application);
85− }
86 }
96 }
87
97
98+ refreshWindows();
99+
88 endResetModel();
100 endResetModel();
89 m_modelState = IdleState;
101 m_modelState = IdleState;
90}
102}
@@ -97,6 +109,11 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
97
109
98 DEBUG_MSG << "(" << surfaceManager << ")";
110 DEBUG_MSG << "(" << surfaceManager << ")";
99
111
112+ Q_ASSERT(m_modelState == IdleState);
113+ m_modelState = ResettingState;
114+
115+ beginResetModel();
116+
100 if (m_surfaceManager) {
117 if (m_surfaceManager) {
101 disconnect(m_surfaceManager, 0, this, 0);
118 disconnect(m_surfaceManager, 0, this, 0);
102 }
119 }
@@ -104,13 +121,16 @@ void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *s
104 m_surfaceManager = surfaceManager;
121 m_surfaceManager = surfaceManager;
105
122
106 if (m_surfaceManager) {
123 if (m_surfaceManager) {
107− connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
124+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesAddedToWorkspace, this, &TopLevelWindowModel::onSurfacesAddedToWorkspace);
108 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
125 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
109 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
126 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
110 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
127 connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
111 }
128 }
112
129
113− Q_EMIT surfaceManagerChanged(m_surfaceManager);
130+ refreshWindows();
131+
132+ endResetModel();
133+ m_modelState = IdleState;
114}
134}
115
135
116void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
136void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
@@ -150,6 +170,7 @@ void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface,
150 Q_ASSERT(surface != nullptr);
170 Q_ASSERT(surface != nullptr);
151
171
152 connectSurface(surface);
172 connectSurface(surface);
173+ m_allSurfaces.insert(surface);
153
174
154 bool filledPlaceholder = false;
175 bool filledPlaceholder = false;
155 for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
176 for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
@@ -226,15 +247,14 @@ void TopLevelWindowModel::connectWindow(Window *window)
226
247
227 connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
248 connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
228 if (window->surface()) {
249 if (window->surface()) {
229− // Condense changes to the focused window
230− // eg: Do focusedWindow=A to focusedWindow=B instead of
231− // focusedWindow=A to focusedWindow=null to focusedWindow=B
232 if (focused) {
250 if (focused) {
233− Q_ASSERT(m_newlyFocusedWindow == nullptr);
251+ setFocusedWindow(window);
234− m_focusedWindowChanged = true;
252+ m_focusedWindowCleared = false;
235− m_newlyFocusedWindow = window;
236 } else if (m_focusedWindow == window) {
253 } else if (m_focusedWindow == window) {
237− m_focusedWindowChanged = true;
254+ // Condense changes to the focused window
255+ // eg: Do focusedWindow=A to focusedWindow=B instead of
256+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
257+ m_focusedWindowCleared = true;
238 } else {
258 } else {
239 // don't clear the focused window if you were not there in the first place
259 // don't clear the focused window if you were not there in the first place
240 // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
260 // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
@@ -339,6 +359,7 @@ void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surf
339 auto window = m_windowModel[i].window;
359 auto window = m_windowModel[i].window;
340 window->setSurface(nullptr);
360 window->setSurface(nullptr);
341 window->setFocused(false);
361 window->setFocused(false);
362+ m_allSurfaces.remove(surface);
342 INFO_MSG << " Removed surface from entry. After: " << toString();
363 INFO_MSG << " Removed surface from entry. After: " << toString();
343 }
364 }
344}
365}
@@ -354,48 +375,107 @@ Window *TopLevelWindowModel::createWindow(unityapi::MirSurfaceInterface *surface
354 return qmlWindow;
375 return qmlWindow;
355}
376}
356
377
357−void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
378+void TopLevelWindowModel::onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
379+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces)
358{
380{
359− DEBUG_MSG << "(" << surface << ")";
381+ if (!m_workspace || !m_applicationManager) return;
382+ if (workspace != m_workspace->workspace()) {
383+ removeSurfaces(surfaces);
384+ return;
385+ }
360
386
387+ Q_FOREACH(auto surface, surfaces) {
388+ if (m_allSurfaces.contains(surface)) continue;
389+
361− if (surface->parentSurface()) {
390+ if (surface->parentSurface()) {
362− // Wrap it in a Window so that we keep focusedWindow() up to date.
391+ // Wrap it in a Window so that we keep focusedWindow() up to date.
363− Window *window = createWindow(surface);
392+ Window *window = createWindow(surface);
364− connect(surface, &QObject::destroyed, window, [=](){
393+ connect(surface, &QObject::destroyed, window, [=](){
365− window->setSurface(nullptr);
394+ window->setSurface(nullptr);
366− window->deleteLater();
395+ window->deleteLater();
367− });
396+ });
368− } else {
369− if (surface->type() == Mir::InputMethodType) {
370− connectSurface(surface);
371− setInputMethodWindow(createWindow(surface));
372 } else {
397 } else {
398+ if (surface->type() == Mir::InputMethodType) {
399+ connectSurface(surface);
400+ setInputMethodWindow(createWindow(surface));
401+ } else {
373− auto *application = m_applicationManager->findApplicationWithSurface(surface);
402+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
374− if (application) {
403+ if (application) {
375− if (surface->state() == Mir::HiddenState) {
404+ if (surface->state() == Mir::HiddenState) {
376− // Ignore it until it's finally shown
405+ // Ignore it until it's finally shown
377− connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
406+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [=](Mir::State newState) {
378− Q_ASSERT(newState != Mir::HiddenState);
407+ Q_ASSERT(newState != Mir::HiddenState);
379− disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
408+ disconnect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, 0);
409+ prependSurface(surface, application);
410+ });
411+ } else {
380 prependSurface(surface, application);
412 prependSurface(surface, application);
381− });
413+ }
382 } else {
414 } else {
383− prependSurface(surface, application);
415+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
416+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
417+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
418+ Window *promptWindow = createWindow(surface);
419+ connect(surface, &QObject::destroyed, promptWindow, [=](){
420+ promptWindow->setSurface(nullptr);
421+ promptWindow->deleteLater();
422+ });
384 }
423 }
385− } else {
386− // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
387− // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
388− // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
389− Window *promptWindow = createWindow(surface);
390− connect(surface, &QObject::destroyed, promptWindow, [=](){
391− promptWindow->setSurface(nullptr);
392− promptWindow->deleteLater();
393− });
394 }
424 }
395 }
425 }
396 }
426 }
397}
427}
398
428
429+void TopLevelWindowModel::removeSurfaces(const QVector<unity::shell::application::MirSurfaceInterface *> surfaces)
430+{
431+ int start = -1;
432+ int end = -1;
433+ for (auto iter = surfaces.constBegin(); iter != surfaces.constEnd();) {
434+ auto surface = *iter;
435+ iter++;
436+
437+ // Do removals in adjacent blocks.
438+ start = end = indexOf(surface);
439+ if (start == -1) {
440+ // could be a child surface
441+ m_allSurfaces.remove(surface);
442+ continue;
443+ }
444+ while(iter != surfaces.constEnd()) {
445+ int index = indexOf(*iter);
446+ if (index != end+1) {
447+ break;
448+ }
449+ end++;
450+ iter++;
451+ }
452+
453+ if (m_modelState == IdleState) {
454+ beginRemoveRows(QModelIndex(), start, end);
455+ m_modelState = RemovingState;
456+ } else {
457+ Q_ASSERT(m_modelState == ResettingState);
458+ // No point in signaling anything if we're resetting the whole model
459+ }
460+
461+ for (int index = start; index <= end; index++) {
462+ auto window = m_windowModel[start].window;
463+ window->setSurface(nullptr);
464+ window->setFocused(false);
465+
466+ m_windowModel.removeAt(start);
467+ m_allSurfaces.remove(surface);
468+ }
469+
470+ if (m_modelState == RemovingState) {
471+ endRemoveRows();
472+ Q_EMIT countChanged();
473+ Q_EMIT listChanged();
474+ m_modelState = IdleState;
475+ }
476+ }
477+}
478+
399void TopLevelWindowModel::deleteAt(int index)
479void TopLevelWindowModel::deleteAt(int index)
400{
480{
401 auto window = m_windowModel[index].window;
481 auto window = m_windowModel[index].window;
@@ -418,12 +498,14 @@ void TopLevelWindowModel::removeAt(int index)
418 }
498 }
419
499
420 auto window = m_windowModel[index].window;
500 auto window = m_windowModel[index].window;
501+ auto surface = window->surface();
421
502
422 if (!window->surface()) {
503 if (!window->surface()) {
423 window->setFocused(false);
504 window->setFocused(false);
424 }
505 }
425
506
426 m_windowModel.removeAt(index);
507 m_windowModel.removeAt(index);
508+ m_allSurfaces.remove(surface);
427
509
428 if (m_modelState == RemovingState) {
510 if (m_modelState == RemovingState) {
429 endRemoveRows();
511 endRemoveRows();
@@ -434,6 +516,7 @@ void TopLevelWindowModel::removeAt(int index)
434
516
435 if (m_focusedWindow == window) {
517 if (m_focusedWindow == window) {
436 setFocusedWindow(nullptr);
518 setFocusedWindow(nullptr);
519+ m_focusedWindowCleared = false;
437 }
520 }
438
521
439 INFO_MSG << " after " << toString();
522 INFO_MSG << " after " << toString();
@@ -452,6 +535,15 @@ void TopLevelWindowModel::setInputMethodWindow(Window *window)
452void TopLevelWindowModel::removeInputMethodWindow()
535void TopLevelWindowModel::removeInputMethodWindow()
453{
536{
454 if (m_inputMethodWindow) {
537 if (m_inputMethodWindow) {
538+ auto surface = m_inputMethodWindow->surface();
539+ if (surface) {
540+ m_allSurfaces.remove(surface);
541+ }
542+ if (m_focusedWindow == m_inputMethodWindow) {
543+ setFocusedWindow(nullptr);
544+ m_focusedWindowCleared = false;
545+ }
546+
455 delete m_inputMethodWindow;
547 delete m_inputMethodWindow;
456 m_inputMethodWindow = nullptr;
548 m_inputMethodWindow = nullptr;
457 Q_EMIT inputMethodSurfaceChanged(nullptr);
549 Q_EMIT inputMethodSurfaceChanged(nullptr);
@@ -463,7 +555,7 @@ void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInt
463 DEBUG_MSG << "(" << surfaces << ")";
555 DEBUG_MSG << "(" << surfaces << ")";
464 const int raiseCount = surfaces.size();
556 const int raiseCount = surfaces.size();
465 for (int i = 0; i < raiseCount; i++) {
557 for (int i = 0; i < raiseCount; i++) {
466− int fromIndex = findIndexOf(surfaces[i]);
558+ int fromIndex = indexOf(surfaces[i]);
467 if (fromIndex != -1) {
559 if (fromIndex != -1) {
468 move(fromIndex, 0);
560 move(fromIndex, 0);
469 }
561 }
@@ -490,16 +582,6 @@ QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
490 }
582 }
491}
583}
492
584
493−int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
494−{
495− for (int i=0; i<m_windowModel.count(); i++) {
496− if (m_windowModel[i].window->surface() == surface) {
497− return i;
498− }
499− }
500− return -1;
501−}
502−
503int TopLevelWindowModel::generateId()
585int TopLevelWindowModel::generateId()
504{
586{
505 int id = m_nextId;
587 int id = m_nextId;
@@ -701,12 +783,11 @@ void TopLevelWindowModel::onModificationsStarted()
701
783
702void TopLevelWindowModel::onModificationsEnded()
784void TopLevelWindowModel::onModificationsEnded()
703{
785{
704− if (m_focusedWindowChanged) {
786+ if (m_focusedWindowCleared) {
705− setFocusedWindow(m_newlyFocusedWindow);
787+ setFocusedWindow(nullptr);
706 }
788 }
707 // reset
789 // reset
708− m_focusedWindowChanged = false;
790+ m_focusedWindowCleared = false;
709− m_newlyFocusedWindow = nullptr;
710}
791}
711
792
712void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
793void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
@@ -725,4 +806,55 @@ void TopLevelWindowModel::activateNullWindow()
725{
806{
726 if (!m_nullWindow->focused())
807 if (!m_nullWindow->focused())
727 m_nullWindow->activate();
808 m_nullWindow->activate();
728−}
809+}
810+
811+void TopLevelWindowModel::refreshWindows()
812+{
813+ DEBUG_MSG << "()";
814+ clear();
815+
816+ if (!m_workspace || !m_applicationManager || !m_surfaceManager) return;
817+
818+ m_surfaceManager->forEachSurfaceInWorkspace(m_workspace->workspace(), [this](unity::shell::application::MirSurfaceInterface* surface) {
819+ if (surface->parentSurface()) {
820+ // Wrap it in a Window so that we keep focusedWindow() up to date.
821+ Window *window = createWindow(surface);
822+ connect(surface, &QObject::destroyed, window, [=](){
823+ window->setSurface(nullptr);
824+ window->deleteLater();
825+ });
826+ } else {
827+ if (surface->type() == Mir::InputMethodType) {
828+ setInputMethodWindow(createWindow(surface));
829+ } else {
830+ auto *application = m_applicationManager->findApplicationWithSurface(surface);
831+ if (application) {
832+ prependSurface(surface, application);
833+ } else {
834+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
835+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
836+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
837+ Window *promptWindow = createWindow(surface);
838+ connect(surface, &QObject::destroyed, promptWindow, [=](){
839+ promptWindow->setSurface(nullptr);
840+ promptWindow->deleteLater();
841+ });
842+ }
843+ }
844+ }
845+ });
846+}
847+
848+void TopLevelWindowModel::clear()
849+{
850+ DEBUG_MSG << "()";
851+
852+ while(m_windowModel.count() > 0) {
853+ ModelEntry entry = m_windowModel.takeAt(0);
854+ disconnect(entry.window, 0, this, 0);
855+ delete entry.window;
856+ }
857+ m_allSurfaces.clear();
858+ setFocusedWindow(nullptr);
859+ m_focusedWindowCleared = false;
860+}
plugins/WindowManager/TopLevelWindowModel.h+19−24
@@ -20,11 +20,16 @@
20#include <QAbstractListModel>
20#include <QAbstractListModel>
21#include <QLoggingCategory>
21#include <QLoggingCategory>
22
22
23+#include <memory>
24+
23#include "WindowManagerGlobal.h"
25#include "WindowManagerGlobal.h"
24
26
25Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
27Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
26
28
27class Window;
29class Window;
30+class Workspace;
31+
32+namespace miral { class Workspace; }
28
33
29namespace unity {
34namespace unity {
30 namespace shell {
35 namespace shell {
@@ -72,16 +77,6 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
72 */
77 */
73 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
78 Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
74
79
75− Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
76− READ surfaceManager
77− WRITE setSurfaceManager
78− NOTIFY surfaceManagerChanged)
79−
80− Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
81− READ applicationManager
82− WRITE setApplicationManager
83− NOTIFY applicationManagerChanged)
84−
85 /**
80 /**
86 The id to be used on the next entry created
81 The id to be used on the next entry created
87 Useful for tests
82 Useful for tests
@@ -100,7 +95,8 @@ public:
100 ApplicationRole = Qt::UserRole + 1,
95 ApplicationRole = Qt::UserRole + 1,
101 };
96 };
102
97
103− TopLevelWindowModel();
98+ TopLevelWindowModel(Workspace* workspace);
99+ ~TopLevelWindowModel();
104
100
105 // From QAbstractItemModel
101 // From QAbstractItemModel
106 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
102 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -111,17 +107,11 @@ public:
111 return roleNames;
107 return roleNames;
112 }
108 }
113
109
114− // Own API
110+ // Own API;
115
111
116 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
112 unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
117 Window* focusedWindow() const;
113 Window* focusedWindow() const;
118
114
119− unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
120− void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
121−
122− unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
123− void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
124−
125 int nextId() const { return m_nextId; }
115 int nextId() const { return m_nextId; }
126
116
127public:
117public:
@@ -168,13 +158,13 @@ public:
168 * @brief Raises and focuses a window with no surface
158 * @brief Raises and focuses a window with no surface
169 */
159 */
170 Q_INVOKABLE void activateNullWindow();
160 Q_INVOKABLE void activateNullWindow();
161+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
162+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
171
163
172Q_SIGNALS:
164Q_SIGNALS:
173 void countChanged();
165 void countChanged();
174 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
166 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
175 void focusedWindowChanged(Window *focusedWindow);
167 void focusedWindowChanged(Window *focusedWindow);
176− void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
177− void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
178
168
179 /**
169 /**
180 * @brief Emitted when the list changes
170 * @brief Emitted when the list changes
@@ -186,7 +176,8 @@ Q_SIGNALS:
186 void nextIdChanged();
176 void nextIdChanged();
187
177
188private Q_SLOTS:
178private Q_SLOTS:
179+ void onSurfacesAddedToWorkspace(const std::shared_ptr<miral::Workspace>& workspace,
189− void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
180+ const QVector<unity::shell::application::MirSurfaceInterface*> surfaces);
190 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
181 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
191
182
192 void onModificationsStarted();
183 void onModificationsStarted();
@@ -203,9 +194,9 @@ private:
203 void setInputMethodWindow(Window *window);
194 void setInputMethodWindow(Window *window);
204 void setFocusedWindow(Window *window);
195 void setFocusedWindow(Window *window);
205 void removeInputMethodWindow();
196 void removeInputMethodWindow();
206− int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
207 void deleteAt(int index);
197 void deleteAt(int index);
208 void removeAt(int index);
198 void removeAt(int index);
199+ void removeSurfaces(const QVector<unity::shell::application::MirSurfaceInterface *> surfaces);
209
200
210 void addApplication(unity::shell::application::ApplicationInfoInterface *application);
201 void addApplication(unity::shell::application::ApplicationInfoInterface *application);
211 void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
202 void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
@@ -228,6 +219,8 @@ private:
228 void activateEmptyWindow(Window *window);
219 void activateEmptyWindow(Window *window);
229
220
230 void activateTopMostWindowWithoutId(int forbiddenId);
221 void activateTopMostWindowWithoutId(int forbiddenId);
222+ void refreshWindows();
223+ void clear();
231
224
232 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
225 Window *createWindow(unity::shell::application::MirSurfaceInterface *surface);
233
226
@@ -245,6 +238,9 @@ private:
245 Window* m_inputMethodWindow{nullptr};
238 Window* m_inputMethodWindow{nullptr};
246 Window* m_focusedWindow{nullptr};
239 Window* m_focusedWindow{nullptr};
247 Window* m_nullWindow;
240 Window* m_nullWindow;
241+ Workspace* m_workspace{nullptr};
242+ // track all the surfaces we've been told about.
243+ QSet<unity::shell::application::MirSurfaceInterface*> m_allSurfaces;
248
244
249 int m_nextId{1};
245 int m_nextId{1};
250 // Just something big enough that we don't risk running out of unused id numbers.
246 // Just something big enough that we don't risk running out of unused id numbers.
@@ -265,8 +261,7 @@ private:
265 ModelState m_modelState{IdleState};
261 ModelState m_modelState{IdleState};
266
262
267 // Valid between modificationsStarted and modificationsEnded
263 // Valid between modificationsStarted and modificationsEnded
268− bool m_focusedWindowChanged{false};
264+ bool m_focusedWindowCleared{false};
269− Window *m_newlyFocusedWindow{nullptr};
270};
265};
271
266
272#endif // TOPLEVELWINDOWMODEL_H
267#endif // TOPLEVELWINDOWMODEL_H
plugins/WindowManager/WindowManagerObjects.cppnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2017 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 "WindowManagerObjects.h"
18+
19+WindowManagerObjects::WindowManagerObjects(QObject *parent)
20+ : QObject(parent)
21+ , m_surfaceManager(nullptr)
22+ , m_applicationManager(nullptr)
23+{
24+}
25+
26+WindowManagerObjects *WindowManagerObjects::instance()
27+{
28+ static WindowManagerObjects* objects(new WindowManagerObjects());
29+ return objects;
30+}
31+
32+
33+void WindowManagerObjects::setSurfaceManager(unity::shell::application::SurfaceManagerInterface *surfaceManager)
34+{
35+ if (m_surfaceManager == surfaceManager) return;
36+
37+ m_surfaceManager = surfaceManager;
38+ Q_EMIT surfaceManagerChanged(surfaceManager);
39+}
40+
41+void WindowManagerObjects::setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager)
42+{
43+ if (m_applicationManager == applicationManager) return;
44+
45+ m_applicationManager = applicationManager;
46+ Q_EMIT applicationManagerChanged(applicationManager);
47+}
plugins/WindowManager/WindowManagerObjects.hnew file+66
@@ -0,0 +1,66 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WINDOWMANAGEROBJECTS_H
18+#define WINDOWMANAGEROBJECTS_H
19+
20+#include <QObject>
21+
22+#include "WindowManagerGlobal.h"
23+
24+namespace unity {
25+ namespace shell {
26+ namespace application {
27+ class SurfaceManagerInterface;
28+ class ApplicationManagerInterface;
29+ }
30+ }
31+}
32+
33+class WINDOWMANAGERQML_EXPORT WindowManagerObjects : public QObject
34+{
35+ Q_OBJECT
36+
37+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
38+ READ surfaceManager
39+ WRITE setSurfaceManager
40+ NOTIFY surfaceManagerChanged)
41+
42+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
43+ READ applicationManager
44+ WRITE setApplicationManager
45+ NOTIFY applicationManagerChanged)
46+public:
47+ explicit WindowManagerObjects(QObject *parent = 0);
48+
49+ static WindowManagerObjects *instance();
50+
51+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
52+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
53+
54+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
55+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
56+
57+Q_SIGNALS:
58+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
59+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
60+
61+private:
62+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
63+ unity::shell::application::ApplicationManagerInterface* m_applicationManager;
64+};
65+
66+#endif // WINDOWMANAGEROBJECTS_H
plugins/WindowManager/WindowManagerPlugin.cpp+57−2
@@ -17,19 +17,74 @@
17#include "WindowManagerPlugin.h"
17#include "WindowManagerPlugin.h"
18
18
19#include "AvailableDesktopArea.h"
19#include "AvailableDesktopArea.h"
20+#include "Screen.h"
21+#include "ScreenAttached.h"
22+#include "Screens.h"
23+#include "ScreensConfiguration.h"
24+#include "ScreenWindow.h"
20#include "TopLevelWindowModel.h"
25#include "TopLevelWindowModel.h"
21#include "Window.h"
26#include "Window.h"
27+#include "WindowManagerObjects.h"
22#include "WindowMargins.h"
28#include "WindowMargins.h"
29+#include "WorkspaceManager.h"
30+#include "Workspace.h"
31+#include "WorkspaceModel.h"
23
32
24#include <QtQml>
33#include <QtQml>
34+#include <qtmir/qtmir.h>
35+
36+namespace {
37+
38+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
39+
40+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
41+{
42+ Q_UNUSED(engine)
43+ Q_UNUSED(scriptEngine)
44+ return WorkspaceManager::instance();
45+}
46+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
47+ Q_UNUSED(engine);
48+ Q_UNUSED(scriptEngine);
49+ return ConcreteScreens::self();
50+}
51+QObject* objectsSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
52+ Q_UNUSED(engine);
53+ Q_UNUSED(scriptEngine);
54+ return WindowManagerObjects::instance();
55+}
56+
57+} // namspace
25
58
26void WindowManagerPlugin::registerTypes(const char *uri)
59void WindowManagerPlugin::registerTypes(const char *uri)
27{
60{
28 qmlRegisterType<AvailableDesktopArea>(uri, 1, 0, "AvailableDesktopArea");
61 qmlRegisterType<AvailableDesktopArea>(uri, 1, 0, "AvailableDesktopArea");
29− qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
30 qmlRegisterType<WindowMargins>(uri, 1, 0, "WindowMargins");
62 qmlRegisterType<WindowMargins>(uri, 1, 0, "WindowMargins");
63+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
64+ qmlRegisterSingletonType<ConcreteScreens>(uri, 1, 0, "Screens", screensSingleton);
65+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", notInstantiatable);
66+ qmlRegisterSingletonType<WindowManagerObjects>(uri, 1, 0, "WindowManagerObjects", objectsSingleton);
31
67
32− qRegisterMetaType<Window*>("Window*");
68+ qRegisterMetaType<ConcreteScreen*>("ConcreteScreen*");
69+ qRegisterMetaType<ProxyScreens*>("ProxyScreens*");
70+ qRegisterMetaType<Workspace*>("Workspace*");
71+ qRegisterMetaType<TopLevelWindowModel*>("TopLevelWindowModel*");
72+ qRegisterMetaType<ScreenConfig*>("ScreenConfig*");
73+ qRegisterMetaType<WorkspaceModel*>("WorkspaceModel*");
33
74
75+ qRegisterMetaType<Window*>("Window*");
34 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
76 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
77+
78+ qmlRegisterType<ScreenWindow>(uri, 1, 0, "ScreenWindow");
79+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
80+
81+ qmlRegisterUncreatableType<WMScreen>(uri, 1, 0, "WMScreen", notInstantiatable);
82+}
83+
84+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
85+{
86+ QQmlExtensionPlugin::initializeEngine(engine, uri);
87+
88+ // Create Screens
89+ new ConcreteScreens(qtmir::get_screen_model(), new ScreensConfiguration());
35}
90}
plugins/WindowManager/WindowManagerPlugin.h+1
@@ -27,6 +27,7 @@ class WindowManagerPlugin : public QQmlExtensionPlugin
27
27
28public:
28public:
29 void registerTypes(const char *uri) override;
29 void registerTypes(const char *uri) override;
30+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
30};
31};
31
32
32#endif // WINDOWMANAGER_PLUGIN_H
33#endif // WINDOWMANAGER_PLUGIN_H
plugins/WindowManager/Workspace.cppnew file+178
@@ -0,0 +1,178 @@
1+/*
2+ * Copyright (C) 2017 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 "Workspace.h"
18+#include "WorkspaceModel.h"
19+#include "WorkspaceManager.h"
20+#include "TopLevelWindowModel.h"
21+#include "Screen.h"
22+
23+#include "wmpolicyinterface.h"
24+
25+int nextWorkspace = 0;
26+
27+Workspace::Workspace(QObject *parent)
28+ : QObject(parent)
29+ , m_workspace(WMPolicyInterface::instance()->createWorkspace())
30+ , m_model(nullptr)
31+{
32+ setObjectName((QString("Wks%1").arg(nextWorkspace++)));
33+}
34+
35+Workspace::Workspace(const Workspace &other)
36+ : QObject(nullptr)
37+ , m_workspace(other.m_workspace)
38+ , m_model(nullptr)
39+{
40+ setObjectName(other.objectName());
41+
42+ connect(&other, &Workspace::activeChanged, this, &Workspace::activeChanged);
43+}
44+
45+Workspace::~Workspace()
46+{
47+ if (m_model) {
48+ m_model->remove(this);
49+ }
50+}
51+
52+void Workspace::assign(WorkspaceModel *model, const QVariant& vIndex)
53+{
54+ if (m_model == model) return;
55+
56+ if (m_model) {
57+ disconnect(m_model, 0, this, 0);
58+ m_model->remove(this);
59+ }
60+
61+ m_model = model;
62+
63+ if (model) {
64+ int index = m_model->rowCount();
65+ if (vIndex.isValid() && vIndex.canConvert(QVariant::Int)) {
66+ index = vIndex.toInt();
67+ }
68+ m_model->insert(index, this);
69+
70+ connect(m_model, &QObject::destroyed, this, [this]() {
71+ m_model = nullptr;
72+ Q_EMIT unassigned();
73+ });
74+ Q_EMIT assigned();
75+ } else {
76+ Q_EMIT unassigned();
77+ }
78+}
79+
80+void Workspace::unassign()
81+{
82+ assign(nullptr);
83+}
84+
85+bool Workspace::isAssigned() const
86+{
87+ return m_model != nullptr;
88+}
89+
90+bool Workspace::isSameAs(Workspace *wks) const
91+{
92+ if (!wks) return false;
93+ if (wks == this) return true;
94+ return wks->workspace() == workspace();
95+}
96+
97+
98+ConcreteWorkspace::ConcreteWorkspace(QObject *parent)
99+ : Workspace(parent)
100+ , m_active(false)
101+ , m_windowModel(new TopLevelWindowModel(this))
102+{
103+ connect(WorkspaceManager::instance(), &WorkspaceManager::activeWorkspaceChanged, this, [this](Workspace* activeWorkspace) {
104+ bool newActive = activeWorkspace == this;
105+ if (newActive != m_active) {
106+ m_active = newActive;
107+ Q_EMIT activeChanged(m_active);
108+
109+ if (m_active) {
110+ WMPolicyInterface::instance()->setActiveWorkspace(m_workspace);
111+ }
112+ }
113+ });
114+}
115+
116+ConcreteWorkspace::~ConcreteWorkspace()
117+{
118+ WorkspaceManager::instance()->destroyWorkspace(this);
119+ WMPolicyInterface::instance()->releaseWorkspace(m_workspace);
120+}
121+
122+TopLevelWindowModel *ConcreteWorkspace::windowModel() const
123+{
124+ return m_windowModel.data();
125+}
126+
127+void ConcreteWorkspace::activate()
128+{
129+ WorkspaceManager::instance()->setActiveWorkspace(this);
130+}
131+
132+void ConcreteWorkspace::setCurrentOn(Screen *screen)
133+{
134+ if (screen) {
135+ screen->setCurrentWorkspace(this);
136+ }
137+}
138+
139+
140+ProxyWorkspace::ProxyWorkspace(Workspace * const workspace)
141+ : Workspace(*workspace)
142+ , m_original(workspace)
143+{
144+}
145+
146+void ProxyWorkspace::assign(WorkspaceModel *model, const QVariant &index)
147+{
148+ Workspace::assign(model, index);
149+}
150+
151+void ProxyWorkspace::unassign()
152+{
153+ Workspace::unassign();
154+}
155+
156+bool ProxyWorkspace::isActive() const
157+{
158+ return m_original ? m_original->isActive() : false;
159+}
160+
161+TopLevelWindowModel *ProxyWorkspace::windowModel() const
162+{
163+ return m_original ? m_original->windowModel() : nullptr;
164+}
165+
166+void ProxyWorkspace::activate()
167+{
168+ if (m_original) {
169+ m_original->activate();
170+ }
171+}
172+
173+void ProxyWorkspace::setCurrentOn(Screen *screen)
174+{
175+ if (screen && m_original) {
176+ screen->setCurrentWorkspace(m_original);
177+ }
178+}
plugins/WindowManager/Workspace.hnew file+122
@@ -0,0 +1,122 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WINDOWMANAGER_WORKSPACE_H
18+#define WINDOWMANAGER_WORKSPACE_H
19+
20+#include <QObject>
21+#include <QVariant>
22+#include <QPointer>
23+#include <QSharedPointer>
24+
25+#include <memory>
26+#include <functional>
27+
28+#include "WindowManagerGlobal.h"
29+
30+class WorkspaceModel;
31+class TopLevelWindowModel;
32+class Screen;
33+
34+namespace miral { class Workspace; }
35+
36+namespace unity {
37+ namespace shell {
38+ namespace application {
39+ class MirSurfaceInterface;
40+ }
41+ }
42+}
43+
44+class WINDOWMANAGERQML_EXPORT Workspace : public QObject
45+{
46+ Q_OBJECT
47+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
48+ Q_PROPERTY(TopLevelWindowModel* windowModel READ windowModel CONSTANT)
49+public:
50+ virtual ~Workspace();
51+
52+ virtual void assign(WorkspaceModel* model, const QVariant& index = QVariant());
53+ virtual void unassign();
54+
55+ virtual bool isActive() const = 0;
56+ virtual TopLevelWindowModel *windowModel() const = 0;
57+ virtual void setCurrentOn(Screen*) = 0;
58+
59+ std::shared_ptr<miral::Workspace> workspace() const { return m_workspace; }
60+ bool isAssigned() const;
61+ Q_INVOKABLE bool isSameAs(Workspace*) const;
62+
63+public Q_SLOTS:
64+ virtual void activate() = 0;
65+
66+Q_SIGNALS:
67+ void assigned();
68+ void unassigned();
69+
70+ void activeChanged(bool);
71+
72+protected:
73+ Workspace(QObject *parent = nullptr);
74+ Workspace(Workspace const& other);
75+
76+ std::shared_ptr<miral::Workspace> m_workspace;
77+ WorkspaceModel* m_model;
78+};
79+
80+class WINDOWMANAGERQML_EXPORT ConcreteWorkspace : public Workspace
81+{
82+public:
83+ ~ConcreteWorkspace();
84+
85+ bool isActive() const override { return m_active; }
86+ TopLevelWindowModel *windowModel() const override;
87+ void activate() override;
88+ void setCurrentOn(Screen*) override;
89+
90+private:
91+ explicit ConcreteWorkspace(QObject *parent = nullptr);
92+
93+ bool m_active;
94+ const QScopedPointer<TopLevelWindowModel> m_windowModel;
95+
96+ friend class WorkspaceManager;
97+};
98+
99+class ProxyWorkspace : public Workspace
100+{
101+ Q_OBJECT
102+public:
103+ explicit ProxyWorkspace(Workspace*const workspace);
104+ ~ProxyWorkspace() = default;
105+
106+ Q_INVOKABLE void assign(WorkspaceModel* model, const QVariant& index = QVariant()) override;
107+
108+ bool isActive() const override;
109+ TopLevelWindowModel *windowModel() const override;
110+ void activate() override;
111+ void setCurrentOn(Screen*) override;
112+
113+ Workspace* proxyObject() const { return m_original.data(); }
114+
115+public Q_SLOTS:
116+ void unassign() override;
117+
118+private:
119+ const QPointer<Workspace> m_original;
120+};
121+
122+#endif // WINDOWMANAGER_WORKSPACE_H
plugins/WindowManager/WorkspaceManager.cppnew file+106
@@ -0,0 +1,106 @@
1+/*
2+ * Copyright (C) 2017 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 "WorkspaceManager.h"
18+#include "Workspace.h"
19+#include "TopLevelWindowModel.h"
20+#include "WindowManagerObjects.h"
21+#include <unity/shell/application/SurfaceManagerInterface.h>
22+
23+// Qt
24+#include <QGuiApplication>
25+#include <QScreen>
26+#include <QQmlEngine>
27+
28+WorkspaceManager *WorkspaceManager::instance()
29+{
30+ static WorkspaceManager* workspaceManager(new WorkspaceManager());
31+ return workspaceManager;
32+}
33+
34+WorkspaceManager::WorkspaceManager()
35+ : m_activeWorkspace(nullptr)
36+{
37+}
38+
39+Workspace *WorkspaceManager::createWorkspace()
40+{
41+ auto workspace = new ConcreteWorkspace(this);
42+ QQmlEngine::setObjectOwnership(workspace, QQmlEngine::CppOwnership);
43+ m_allWorkspaces.insert(workspace);
44+
45+ if (m_allWorkspaces.count() == 0 && m_activeWorkspace) {
46+ setActiveWorkspace(nullptr);
47+ } else if (m_allWorkspaces.count() == 1) {
48+ setActiveWorkspace(workspace);
49+ }
50+
51+ return workspace;
52+}
53+
54+void WorkspaceManager::destroyWorkspace(Workspace *workspace)
55+{
56+ if (!workspace) return;
57+
58+ if (workspace->isAssigned()) {
59+ workspace->unassign();
60+ }
61+ m_allWorkspaces.remove(workspace);
62+
63+ if (m_activeWorkspace == workspace) {
64+ setActiveWorkspace(m_allWorkspaces.count() ? *m_allWorkspaces.begin() : nullptr);
65+ }
66+ if (m_activeWorkspace) {
67+ moveWorkspaceContentToWorkspace(m_activeWorkspace, workspace);
68+ }
69+
70+ disconnect(workspace, 0, this, 0);
71+}
72+
73+void WorkspaceManager::moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface *surface, Workspace *workspace)
74+{
75+ auto surfaceManager = WindowManagerObjects::instance()->surfaceManager();
76+ if (surfaceManager) {
77+ surfaceManager->moveSurfaceToWorkspace(surface, workspace->workspace());
78+ }
79+}
80+
81+void WorkspaceManager::moveWorkspaceContentToWorkspace(Workspace *to, Workspace *from)
82+{
83+ auto surfaceManager = WindowManagerObjects::instance()->surfaceManager();
84+ if (surfaceManager) {
85+ surfaceManager->moveWorkspaceContentToWorkspace(to->workspace(), from->workspace());
86+ }
87+}
88+
89+Workspace *WorkspaceManager::activeWorkspace() const
90+{
91+ return m_activeWorkspace;
92+}
93+
94+void WorkspaceManager::setActiveWorkspace(Workspace *workspace)
95+{
96+ if (workspace != m_activeWorkspace) {
97+ m_activeWorkspace = workspace;
98+ Q_EMIT activeWorkspaceChanged(workspace);
99+ }
100+}
101+
102+void WorkspaceManager::setActiveWorkspace2(Workspace *workspace)
103+{
104+ if (!workspace) return;
105+ workspace->activate();
106+}
plugins/WindowManager/WorkspaceManager.hnew file+69
@@ -0,0 +1,69 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WORKSPACEMANAGER_H
18+#define WORKSPACEMANAGER_H
19+
20+#include <QQmlListProperty>
21+
22+#include "WindowManagerGlobal.h"
23+#include "WorkspaceModel.h"
24+
25+class Workspace;
26+
27+namespace unity {
28+ namespace shell {
29+ namespace application {
30+ class MirSurfaceInterface;
31+ class SurfaceManagerInterface;
32+ }
33+ }
34+}
35+
36+class WINDOWMANAGERQML_EXPORT WorkspaceManager : public QObject
37+{
38+ Q_OBJECT
39+ Q_PROPERTY(Workspace* activeWorkspace READ activeWorkspace WRITE setActiveWorkspace2 NOTIFY activeWorkspaceChanged)
40+
41+public:
42+ WorkspaceManager();
43+ static WorkspaceManager* instance();
44+
45+ Workspace* activeWorkspace() const;
46+ void setActiveWorkspace(Workspace* workspace);
47+
48+ Workspace* createWorkspace();
49+ void destroyWorkspace(Workspace* workspace);
50+
51+ void destroyFloatingWorkspaces();
52+
53+ Q_INVOKABLE void moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
54+ Workspace* workspace);
55+
56+ Q_INVOKABLE void moveWorkspaceContentToWorkspace(Workspace* to, Workspace* from);
57+
58+Q_SIGNALS:
59+ void activeWorkspaceChanged(Workspace*);
60+
61+private:
62+ void setActiveWorkspace2(Workspace* workspace);
63+
64+ QSet<Workspace*> m_allWorkspaces;
65+ Workspace* m_activeWorkspace;
66+ unity::shell::application::SurfaceManagerInterface* m_surfaceManager;
67+};
68+
69+#endif // WORKSPACEMANAGER_H
plugins/WindowManager/WorkspaceModel.cppnew file+254
@@ -0,0 +1,254 @@
1+/*
2+ * Copyright (C) 2017 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 "WorkspaceModel.h"
18+#include "WorkspaceManager.h"
19+#include "Workspace.h"
20+#include "Screen.h"
21+
22+#include <QQmlEngine>
23+
24+Q_LOGGING_CATEGORY(WORKSPACES, "Workspaces", QtInfoMsg)
25+
26+#define DEBUG_MSG qCDebug(WORKSPACES).nospace().noquote() << __func__
27+#define INFO_MSG qCInfo(WORKSPACES).nospace().noquote() << __func__
28+
29+WorkspaceModel::WorkspaceModel(QObject *parent)
30+ : QAbstractListModel(parent)
31+{
32+}
33+
34+WorkspaceModel::~WorkspaceModel()
35+{
36+ qDeleteAll(m_workspaces.toList()); // make a copy so the list doesnt edit itself during delete.
37+ m_workspaces.clear();
38+}
39+
40+void WorkspaceModel::append(Workspace *workspace)
41+{
42+ insert(m_workspaces.count(), workspace);
43+}
44+
45+void WorkspaceModel::insert(int index, Workspace *workspace)
46+{
47+ beginInsertRows(QModelIndex(), index, index);
48+
49+ m_workspaces.insert(index, workspace);
50+
51+ endInsertRows();
52+
53+ Q_EMIT workspaceInserted(index, workspace);
54+ Q_EMIT countChanged();
55+}
56+
57+void WorkspaceModel::remove(Workspace *workspace)
58+{
59+ int index = m_workspaces.indexOf(workspace);
60+ if (index < 0) return;
61+
62+ beginRemoveRows(QModelIndex(), index, index);
63+
64+ m_workspaces.removeAt(index);
65+ insertUnassigned(workspace);
66+
67+ endRemoveRows();
68+
69+ Q_EMIT workspaceRemoved(workspace);
70+ Q_EMIT countChanged();
71+}
72+
73+void WorkspaceModel::move(int from, int to)
74+{
75+ if (from == to) return;
76+ DEBUG_MSG << " from=" << from << " to=" << to;
77+
78+ if (from >= 0 && from < m_workspaces.size() && to >= 0 && to < m_workspaces.size()) {
79+ QModelIndex parent;
80+
81+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
82+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
83+ const auto &window = m_windowModel.takeAt(from);
84+ m_workspaces.insert(to, window);
85+#else
86+ m_workspaces.move(from, to);
87+#endif
88+ endMoveRows();
89+
90+ Q_EMIT workspaceMoved(from, to);
91+ }
92+}
93+
94+int WorkspaceModel::indexOf(Workspace *workspace) const
95+{
96+ return m_workspaces.indexOf(workspace);
97+}
98+
99+Workspace *WorkspaceModel::get(int index) const
100+{
101+ if (index < 0 || index >= rowCount()) return nullptr;
102+ return m_workspaces.at(index);
103+}
104+
105+int WorkspaceModel::rowCount(const QModelIndex &) const
106+{
107+ return m_workspaces.count();
108+}
109+
110+QVariant WorkspaceModel::data(const QModelIndex &index, int role) const
111+{
112+ if (index.row() < 0 || index.row() >= m_workspaces.size())
113+ return QVariant();
114+
115+ if (role == WorkspaceRole) {
116+ Workspace *workspace = m_workspaces.at(index.row());
117+ return QVariant::fromValue(workspace);
118+ } else {
119+ return QVariant();
120+ }
121+}
122+
123+void WorkspaceModel::sync(WorkspaceModel *proxy)
124+{
125+ if (!proxy) return;
126+ const auto& proxyList = proxy->list();
127+
128+ // check for removals
129+ int removedIndexWhichWasActive = -1;
130+ QVector<Workspace*> dpCpy(this->list());
131+ Q_FOREACH(auto workspace, dpCpy) {
132+
133+ bool found = false;
134+ Q_FOREACH(auto p, proxyList) {
135+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(p);
136+ if (workspaceProxy->proxyObject() == workspace) {
137+ found = true;
138+ break;
139+ }
140+ }
141+ if (!found) {
142+ if (workspace->isActive()) {
143+ removedIndexWhichWasActive = indexOf(workspace);
144+ }
145+ workspace->unassign();
146+ }
147+ }
148+
149+ // existing
150+ QSet<Workspace*> newWorkspaces;
151+ for (int i = 0; i < proxyList.count(); i++) {
152+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(proxyList[i]);
153+ auto workspace = workspaceProxy->proxyObject();
154+
155+ int oldIndex = this->indexOf(workspace);
156+
157+ if (oldIndex < 0) {
158+ workspace->assign(this, QVariant(i));
159+ } else if (oldIndex != i) {
160+ this->move(oldIndex, i);
161+ }
162+ newWorkspaces.insert(workspace);
163+ }
164+
165+ // Make sure we have at least one workspace in the model.
166+ if (rowCount() == 0) {
167+ Workspace* workspace = WorkspaceManager::instance()->createWorkspace();
168+ workspace->assign(this);
169+ (new ProxyWorkspace(workspace))->assign(proxy);
170+ }
171+
172+ if (removedIndexWhichWasActive != -1) {
173+ int newActiveIndex = qMin(removedIndexWhichWasActive, this->rowCount()-1);
174+ Workspace* newActiveWorkspace = newActiveIndex >= 0 ? this->get(newActiveIndex) : nullptr;
175+
176+ WorkspaceManager::instance()->setActiveWorkspace(newActiveWorkspace);
177+ }
178+
179+ proxy->finishSync();
180+ finishSync();
181+}
182+
183+void WorkspaceModel::finishSync()
184+{
185+ QSet<Workspace*> dpCpy(m_unassignedWorkspaces);
186+ Q_FOREACH(auto workspace, dpCpy) {
187+ delete workspace;
188+ }
189+ m_unassignedWorkspaces.clear();
190+}
191+
192+void WorkspaceModel::insertUnassigned(Workspace *workspace)
193+{
194+ m_unassignedWorkspaces.insert(workspace);
195+ connect(workspace, &Workspace::assigned, this, [=]() {
196+ m_unassignedWorkspaces.remove(workspace);
197+ disconnect(workspace, &Workspace::assigned, this, 0);
198+ });
199+ connect(workspace, &QObject::destroyed, this, [=]() {
200+ m_unassignedWorkspaces.remove(workspace);
201+ });
202+}
203+
204+
205+ProxyWorkspaceModel::ProxyWorkspaceModel(WorkspaceModel * const model, ProxyScreen* screen)
206+ : m_original(model)
207+ , m_screen(screen)
208+{
209+ Q_FOREACH(auto workspace, model->list()) {
210+ auto proxy = new ProxyWorkspace(workspace);
211+ QQmlEngine::setObjectOwnership(proxy, QQmlEngine::CppOwnership);
212+ proxy->assign(this);
213+ }
214+ connect(m_original, &WorkspaceModel::workspaceInserted, this, [this](int index, Workspace* inserted) {
215+ if (isSyncing()) return;
216+
217+ (new ProxyWorkspace(inserted))->assign(this, index);
218+ });
219+ connect(m_original, &WorkspaceModel::workspaceRemoved, this, [this](Workspace* removed) {
220+ if (isSyncing()) return;
221+
222+ for (int i = 0; i < rowCount(); i++) {
223+ auto workspaceProxy = qobject_cast<ProxyWorkspace*>(get(i));
224+ auto w = workspaceProxy->proxyObject();
225+ if (w == removed) {
226+ remove(workspaceProxy);
227+ break;
228+ }
229+ }
230+ });
231+ connect(m_original, &WorkspaceModel::workspaceMoved, this, [this](int from, int to) {
232+ if (isSyncing()) return;
233+
234+ move(from, to);
235+ });
236+}
237+
238+void ProxyWorkspaceModel::move(int from, int to)
239+{
240+ WorkspaceModel::move(from, to);
241+}
242+
243+bool ProxyWorkspaceModel::isSyncing() const
244+{
245+ return m_screen->isSyncing();
246+}
247+
248+void ProxyWorkspaceModel::addWorkspace()
249+{
250+ auto newWorkspace = WorkspaceManager::instance()->createWorkspace();
251+ m_original->insertUnassigned(newWorkspace);
252+
253+ (new ProxyWorkspace(newWorkspace))->assign(this);
254+}
plugins/WindowManager/WorkspaceModel.hnew file+102
@@ -0,0 +1,102 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WORKSPACEMODEL_H
18+#define WORKSPACEMODEL_H
19+
20+#include <QAbstractListModel>
21+#include <QLoggingCategory>
22+#include <QPointer>
23+
24+Q_DECLARE_LOGGING_CATEGORY(WORKSPACES)
25+
26+class Workspace;
27+class ProxyWorkspaceModel;
28+class ProxyScreen;
29+
30+class WorkspaceModel : public QAbstractListModel
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
34+public:
35+ /**
36+ * @brief The Roles supported by the model
37+ *
38+ * WorkspaceRole - A workspace.
39+ */
40+ enum Roles {
41+ WorkspaceRole = Qt::UserRole
42+ };
43+
44+ explicit WorkspaceModel(QObject *parent = 0);
45+ ~WorkspaceModel();
46+
47+ void append(Workspace *workspace);
48+ void insert(int index, Workspace *workspace);
49+ void remove(Workspace* workspace);
50+ virtual void move(int from, int to);
51+
52+ Q_INVOKABLE int indexOf(Workspace *workspace) const;
53+ Q_INVOKABLE Workspace* get(int index) const;
54+
55+ // From QAbstractItemModel
56+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
57+ QVariant data(const QModelIndex& index, int role) const override;
58+ QHash<int, QByteArray> roleNames() const override {
59+ QHash<int, QByteArray> roleNames { {WorkspaceRole, "workspace"} };
60+ return roleNames;
61+ }
62+
63+ const QVector<Workspace*>& list() const { return m_workspaces; }
64+
65+ void sync(WorkspaceModel* proxy);
66+ void finishSync();
67+
68+Q_SIGNALS:
69+ void countChanged();
70+
71+ void workspaceInserted(int index, Workspace *workspace);
72+ void workspaceRemoved(Workspace *workspace);
73+ void workspaceMoved(int from, int to);
74+
75+protected:
76+ void insertUnassigned(Workspace* workspace);
77+
78+ QVector<Workspace*> m_workspaces;
79+ QSet<Workspace*> m_unassignedWorkspaces;
80+
81+ friend class ProxyWorkspaceModel;
82+};
83+
84+class ProxyWorkspaceModel : public WorkspaceModel
85+{
86+ Q_OBJECT
87+public:
88+ explicit ProxyWorkspaceModel(WorkspaceModel*const model, ProxyScreen* screen);
89+
90+ Q_INVOKABLE void move(int from, int to) override;
91+
92+ bool isSyncing() const;
93+
94+public Q_SLOTS:
95+ void addWorkspace();
96+
97+protected:
98+ const QPointer<WorkspaceModel> m_original;
99+ const ProxyScreen* m_screen;
100+};
101+
102+#endif // WORKSPACEMODEL_H
qml/ApplicationMenus/ApplicationMenuItemFactory.qml+4−4
@@ -45,7 +45,7 @@ Object {
45
45
46 Action {
46 Action {
47 id: action
47 id: action
48− text: menuData.label.replace("_", "&")
48+ text: menuData && menuData.label.replace("_", "&") || ""
49 }
49 }
50
50
51 ListItemLayout {
51 ListItemLayout {
@@ -59,8 +59,8 @@ Object {
59 }
59 }
60
60
61 Label {
61 Label {
62− text: menuData.shortcut
62+ text: menuData && menuData.shortcut || ""
63− visible: menuData.shortcut && QuickUtils.keyboardAttached
63+ visible: menuData && menuData.shortcut && QuickUtils.keyboardAttached
64 SlotsLayout.position: SlotsLayout.Trailing
64 SlotsLayout.position: SlotsLayout.Trailing
65 color: enabled ? theme.palette.normal.backgroundSecondaryText :
65 color: enabled ? theme.palette.normal.backgroundSecondaryText :
66 theme.palette.disabled.backgroundSecondaryText
66 theme.palette.disabled.backgroundSecondaryText
@@ -86,7 +86,7 @@ Object {
86
86
87 Action {
87 Action {
88 id: action
88 id: action
89− text: menuData.label.replace("_", "&")
89+ text: menuData && menuData.label.replace("_", "&") || ""
90 }
90 }
91
91
92 ListItemLayout {
92 ListItemLayout {
qml/ApplicationMenus/MenuBar.qml+5−1
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
19import Utils 0.1
19import Utils 0.1
20import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
21import GlobalShortcut 1.0
21import GlobalShortcut 1.0
22+import "../Components/PanelState"
22
23
23Item {
24Item {
24 id: root
25 id: root
@@ -29,6 +30,7 @@ Item {
29 property bool enableKeyFilter: false
30 property bool enableKeyFilter: false
30 property real overflowWidth: width
31 property real overflowWidth: width
31 property bool windowMoving: false
32 property bool windowMoving: false
33+ property PanelState panelState
32
34
33 // read from outside
35 // read from outside
34 readonly property bool valid: rowRepeater.count > 0
36 readonly property bool valid: rowRepeater.count > 0
@@ -106,7 +108,9 @@ Item {
106
108
107 Component {
109 Component {
108 id: menuComponent
110 id: menuComponent
109− MenuPopup { }
111+ MenuPopup {
112+ panelState: root.panelState
113+ }
110 }
114 }
111
115
112 Repeater {
116 Repeater {
qml/ApplicationMenus/MenuPopup.qml+3−1
@@ -64,6 +64,7 @@ UbuntuShape {
64 }
64 }
65
65
66 property alias unityMenuModel: repeater.model
66 property alias unityMenuModel: repeater.model
67+ property PanelState panelState
67
68
68 function show() {
69 function show() {
69 visible = true;
70 visible = true;
@@ -103,7 +104,7 @@ UbuntuShape {
103 property real __minimumWidth: units.gu(20)
104 property real __minimumWidth: units.gu(20)
104 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
105 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
105 property real __minimumHeight: units.gu(2)
106 property real __minimumHeight: units.gu(2)
106− property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
107+ property real __maximumHeight: ApplicationMenusLimits.screenHeight - panelState.panelHeight
107
108
108 signal dismissAll()
109 signal dismissAll()
109
110
@@ -492,6 +493,7 @@ UbuntuShape {
492
493
493 onLoaded: {
494 onLoaded: {
494 item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
495 item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
496+ item.panelState = Qt.binding(function() { return root.panelState; });
495 item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
497 item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
496 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
498 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
497 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
499 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
qml/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−file(GLOB QML_JS_FILES *.qml *.js)
1+file(GLOB QML_JS_FILES *.qml *.js qmldir)
2
2
3install(FILES ${QML_JS_FILES}
3install(FILES ${QML_JS_FILES}
4 DESTINATION ${SHELL_APP_DIR}
4 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−pragma Singleton
18import QtQuick 2.4
17import QtQuick 2.4
19
18
20QtObject {
19QtObject {
qml/Components/PanelState/qmldir+1−1
@@ -1 +1 @@
1−singleton PanelState 1.0 PanelState.qml
1+PanelState 1.0 PanelState.qml
qml/Components/VirtualTouchPad.qml+14−20
@@ -18,22 +18,20 @@ import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Qt.labs.settings 1.0
20import Qt.labs.settings 1.0
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import UInput 0.1
22import UInput 0.1
23import "../Components"
23import "../Components"
24
24
25Item {
25Item {
26 id: root
26 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
31
27
32 Component.onCompleted: {
28 Component.onCompleted: {
29+ UInput.createMouse();
33 if (!settings.touchpadTutorialHasRun) {
30 if (!settings.touchpadTutorialHasRun) {
34 root.runTutorial()
31 root.runTutorial()
35 }
32 }
36 }
33 }
34+ Component.onDestruction: UInput.removeMouse()
37
35
38 function runTutorial() {
36 function runTutorial() {
39 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
37 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
85 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
83 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
86 && clickTimer.running) {
84 && clickTimer.running) {
87 clickTimer.stop();
85 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
89 isDoubleClick = true;
87 isDoubleClick = true;
90 }
88 }
91 isClick = true;
89 isClick = true;
@@ -104,7 +102,7 @@ Item {
104
102
105 onReleased: {
103 onReleased: {
106 if (isDoubleClick || isDrag) {
104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108 isDoubleClick = false;
106 isDoubleClick = false;
109 }
107 }
110 if (isClick) {
108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120 interval: 200
118 interval: 200
121 property int button: UInput.ButtonLeft
119 property int button: UInput.ButtonLeft
122 onTriggered: {
120 onTriggered: {
123− uinput.pressMouse(button);
121+ UInput.pressMouse(button);
124− uinput.releaseMouse(button);
122+ UInput.releaseMouse(button);
125 }
123 }
126 function scheduleClick(button) {
124 function scheduleClick(button) {
127 clickTimer.button = button;
125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138 isDrag = true;
136 isDrag = true;
139 }
137 }
140
138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142 }
140 }
143
141
144 function scroll(touchPoints) {
142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166 dh /= 2;
164 dh /= 2;
167 dv /= 2;
165 dv /= 2;
168
166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170 }
168 }
171
169
172 touchPoints: [
170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189 objectName: "leftButton"
187 objectName: "leftButton"
190 Layout.fillWidth: true
188 Layout.fillWidth: true
191 Layout.fillHeight: true
189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194 property bool highlight: false
192 property bool highlight: false
195 UbuntuShape {
193 UbuntuShape {
196 anchors.fill: parent
194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204 objectName: "rightButton"
202 objectName: "rightButton"
205 Layout.fillWidth: true
203 Layout.fillWidth: true
206 Layout.fillHeight: true
204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209 property bool highlight: false
207 property bool highlight: false
210 UbuntuShape {
208 UbuntuShape {
211 anchors.fill: parent
209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239 }
237 }
240 }
238 }
241
239
242− Screens {
243− id: screens
244− }
245−
246 InputMethod {
240 InputMethod {
247 id: inputMethod
241 id: inputMethod
248 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
242 // 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
243+ enabled: Screens.count > 1 && settings.oskEnabled && !tutorial.running
250 objectName: "inputMethod"
244 objectName: "inputMethod"
251 anchors.fill: parent
245 anchors.fill: parent
252 }
246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
28 property var screen: Screen
28 property var screen: Screen
29 property var orientationLock: OrientationLock
29 property var orientationLock: OrientationLock
30
30
31+ property alias deviceConfiguration: _deviceConfiguration
31 DeviceConfiguration {
32 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
33 name: applicationArguments.deviceName
34 name: applicationArguments.deviceName
34 }
35 }
35
36
qml/ErrorApplication.qmlnew file+67
@@ -0,0 +1,67 @@
1+/*
2+* Copyright (C) 2017 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.Window 2.2
19+import Ubuntu.Components 1.3
20+import WindowManager 1.0
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ ScreenWindow {
27+ id: window
28+ objectName: "screen"+index
29+ screen: model.screen
30+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
31+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
32+
33+ Binding {
34+ when: applicationArguments.hasGeometry
35+ target: window
36+ property: "width"
37+ value: applicationArguments.windowGeometry.width
38+ }
39+ Binding {
40+ when: applicationArguments.hasGeometry
41+ target: window
42+ property: "height"
43+ value: applicationArguments.windowGeometry.height
44+ }
45+
46+ Loader {
47+ width: window.width
48+ height: window.height
49+
50+ Rectangle {
51+ color: "white"
52+ Column {
53+ spacing: units.gu(1)
54+
55+ Label {
56+ text: "Unity encountered an unrecoverable error while loading:"
57+ fontSize: "large"
58+ }
59+
60+ Label {
61+ text: errorString
62+ }
63+ }
64+ }
65+ }
66+ }
67+}
qml/Greeter/Greeter.qml+21−3
@@ -24,6 +24,7 @@ import Unity.Launcher 0.1
24import Unity.Session 0.1
24import Unity.Session 0.1
25
25
26import "." 0.1
26import "." 0.1
27+import ".." 0.1
27import "../Components"
28import "../Components"
28
29
29Showable {
30Showable {
@@ -225,7 +226,7 @@ Showable {
225 if (forcedUnlock && shown) {
226 if (forcedUnlock && shown) {
226 hideView();
227 hideView();
227 if (hideNow) {
228 if (hideNow) {
228− root.hideNow(); // skip hide animation
229+ ShellNotifier.greeter.hide(true); // skip hide animation
229 }
230 }
230 }
231 }
231 }
232 }
@@ -373,7 +374,7 @@ Showable {
373 onEmergencyCall: root.emergencyCall()
374 onEmergencyCall: root.emergencyCall()
374 onRequiredChanged: {
375 onRequiredChanged: {
375 if (!loader.item.required) {
376 if (!loader.item.required) {
376− root.hide();
377+ ShellNotifier.greeter.hide(false);
377 }
378 }
378 }
379 }
379 }
380 }
@@ -487,12 +488,29 @@ Showable {
487 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
488 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
488 }
489 }
489
490
491+ Connections {
492+ target: ShellNotifier.greeter
493+ onHide: {
494+ if (now) {
495+ root.hideNow(); // skip hide animation
496+ } else {
497+ root.hide();
498+ }
499+ }
500+ }
501+
502+ Binding {
503+ target: ShellNotifier.greeter
504+ property: "shown"
505+ value: root.shown
506+ }
507+
490 Connections {
508 Connections {
491 target: DBusUnitySessionService
509 target: DBusUnitySessionService
492 onLockRequested: root.forceShow()
510 onLockRequested: root.forceShow()
493 onUnlocked: {
511 onUnlocked: {
494 root.forcedUnlock = true;
512 root.forcedUnlock = true;
495− root.hideNow();
513+ ShellNotifier.greeter.hide(true);
496 }
514 }
497 }
515 }
498
516
qml/Greeter/SecondaryGreeter.qmlnew 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+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+import "../Components"
21+import ".." 0.1
22+
23+Showable {
24+ id: root
25+
26+ readonly property bool active: required || hasLockedApp
27+
28+ readonly property bool hasLockedApp: lockedApp !== ""
29+ readonly property bool locked: false
30+ readonly property bool waiting: false
31+ readonly property bool fullyShown: shown
32+
33+ property string lockedApp: ""
34+
35+ function forceShow() { show(); }
36+ property var notifyAppFocusRequested: (function(appId) { return; })
37+ property var notifyUserRequestedApp: (function(appId) { return; })
38+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
39+
40+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
41+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
42+
43+ shown: ShellNotifier.greeter.shown
44+ Component.onCompleted: opacity = shown ? 1 : 0
45+ visible: opacity != 0
46+
47+ Rectangle {
48+ anchors.fill: parent
49+ color: UbuntuColors.purple
50+ }
51+
52+ MouseArea {
53+ anchors.fill: parent
54+ acceptedButtons: Qt.AllButtons
55+ onWheel: wheel.accepted = true
56+ }
57+
58+ Connections {
59+ target: ShellNotifier.greeter
60+ onHide: {
61+ if (now) {
62+ root.hideNow(); // skip hide animation
63+ } else {
64+ root.hide();
65+ }
66+ }
67+ onShownChanged: {
68+ if (ShellNotifier.greeter.shown) {
69+ root.show();
70+ } else {
71+ root.hide();
72+ }
73+ }
74+ }
75+}
qml/OrientedShell.qml+5−8
@@ -18,7 +18,7 @@ import QtQuick 2.4
18import QtQuick.Window 2.2
18import QtQuick.Window 2.2
19import Unity.InputInfo 0.1
19import Unity.InputInfo 0.1
20import Unity.Session 0.1
20import Unity.Session 0.1
21−import Unity.Screens 0.1
21+import WindowManager 1.0
22import Utils 0.1
22import Utils 0.1
23import GSettings 1.0
23import GSettings 1.0
24import "Components"
24import "Components"
@@ -32,15 +32,16 @@ Item {
32 implicitWidth: units.gu(40)
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
33 implicitHeight: units.gu(71)
34
34
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+
35 onWidthChanged: calculateUsageMode();
38 onWidthChanged: calculateUsageMode();
36
39
37 DeviceConfiguration {
40 DeviceConfiguration {
38− id: deviceConfiguration
41+ id: _deviceConfiguration
39 name: applicationArguments.deviceName
42 name: applicationArguments.deviceName
40 }
43 }
41
44
42− property alias orientations: d.orientations
43−
44 Item {
45 Item {
45 id: d
46 id: d
46
47
@@ -157,10 +158,6 @@ Item {
157 return false;
158 return false;
158 }
159 }
159
160
160− Screens {
161− id: screens
162− }
163−
164 property int orientation
161 property int orientation
165 onPhysicalOrientationChanged: {
162 onPhysicalOrientationChanged: {
166 if (!orientationLocked) {
163 if (!orientationLocked) {
qml/Panel/Panel.qml+18−16
@@ -48,6 +48,7 @@ Item {
48 property bool hasKeyboard: false
48 property bool hasKeyboard: false
49
49
50 property string mode: "staged"
50 property string mode: "staged"
51+ property PanelState panelState
51
52
52 MouseArea {
53 MouseArea {
53 id: backMouseEater
54 id: backMouseEater
@@ -64,14 +65,14 @@ Item {
64 }
65 }
65
66
66 Binding {
67 Binding {
67− target: PanelState
68+ target: panelState
68 property: "panelHeight"
69 property: "panelHeight"
69 value: minimizedPanelHeight
70 value: minimizedPanelHeight
70 }
71 }
71
72
72 RegisteredApplicationMenuModel {
73 RegisteredApplicationMenuModel {
73 id: registeredMenuModel
74 id: registeredMenuModel
74− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
75 }
76 }
76
77
77 QtObject {
78 QtObject {
@@ -82,11 +83,11 @@ Item {
82 !indicators.shown &&
83 !indicators.shown &&
83 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
84 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
84
85
85− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
86− PanelState.decorationsAlwaysVisible
87+ panelState.decorationsAlwaysVisible
87
88
88− property bool showPointerMenu: revealControls && enablePointerMenu &&
89+ property bool showPointerMenu: revealControls &&
89− (PanelState.decorationsVisible || mode == "windowed")
90+ (panelState.decorationsVisible || mode == "windowed")
90
91
91 property bool enablePointerMenu: applicationMenus.available &&
92 property bool enablePointerMenu: applicationMenus.available &&
92 applicationMenus.model
93 applicationMenus.model
@@ -138,7 +139,7 @@ Item {
138 fill: panelAreaBackground
139 fill: panelAreaBackground
139 bottomMargin: -units.gu(1)
140 bottomMargin: -units.gu(1)
140 }
141 }
141− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
142 source: "graphics/rectangular_dropshadow.sci"
143 source: "graphics/rectangular_dropshadow.sci"
143 }
144 }
144
145
@@ -195,12 +196,12 @@ Item {
195 visible: opacity != 0
196 visible: opacity != 0
196 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
197 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
197
198
198− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
199+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
199 windowIsMaximized: true
200 windowIsMaximized: true
200− onCloseClicked: PanelState.closeClicked()
201+ onCloseClicked: panelState.closeClicked()
201− onMinimizeClicked: PanelState.minimizeClicked()
202+ onMinimizeClicked: panelState.minimizeClicked()
202− onMaximizeClicked: PanelState.restoreClicked()
203+ onMaximizeClicked: panelState.restoreClicked()
203− closeButtonShown: PanelState.closeButtonShown
204+ closeButtonShown: panelState.closeButtonShown
204 }
205 }
205
206
206 Loader {
207 Loader {
@@ -220,11 +221,12 @@ Item {
220 sourceComponent: MenuBar {
221 sourceComponent: MenuBar {
221 id: bar
222 id: bar
222 objectName: "menuBar"
223 objectName: "menuBar"
223− anchors.left: parent ? parent.left : undefined
224+ anchors.left: menuBarLoader ? menuBarLoader.left : undefined
224 anchors.margins: units.gu(1)
225 anchors.margins: units.gu(1)
225 height: menuBarLoader.height
226 height: menuBarLoader.height
226− enableKeyFilter: valid && PanelState.decorationsVisible
227+ enableKeyFilter: valid && panelState.decorationsVisible
227 unityMenuModel: __applicationMenus.model
228 unityMenuModel: __applicationMenus.model
229+ panelState: root.panelState
228
230
229 Connections {
231 Connections {
230 target: __applicationMenus
232 target: __applicationMenus
@@ -236,7 +238,7 @@ Item {
236 onShownChanged: bar.dismiss();
238 onShownChanged: bar.dismiss();
237 }
239 }
238
240
239− onDoubleClicked: PanelState.restoreClicked()
241+ onDoubleClicked: panelState.restoreClicked()
240 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
242 onPressed: mouse.accepted = false // let the parent mouse area handle this, so it can both unsnap window and show menu
241 }
243 }
242 }
244 }
@@ -343,7 +345,7 @@ Item {
343 opacity: __applicationMenus.visible && !__applicationMenus.expanded ? 1 : 0
345 opacity: __applicationMenus.visible && !__applicationMenus.expanded ? 1 : 0
344 visible: opacity != 0
346 visible: opacity != 0
345 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
347 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
346− text: PanelState.title
348+ text: panelState.title
347 }
349 }
348
350
349 PanelMenu {
351 PanelMenu {
qml/Shell.qml+41−25
@@ -1,4 +1,4 @@
1−/*
1+/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,7 @@ import "Notifications"
38import "Stage"
38import "Stage"
39import "Tutorial"
39import "Tutorial"
40import "Wizard"
40import "Wizard"
41+import "Components/PanelState"
41import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Notifications 1.0 as NotificationBackend
42import Unity.Session 0.1
43import Unity.Session 0.1
43import Unity.Indicators 0.1 as Indicators
44import Unity.Indicators 0.1 as Indicators
@@ -95,6 +96,11 @@ StyledItem {
95
96
96 readonly property var mainApp: stage.mainApp
97 readonly property var mainApp: stage.mainApp
97
98
99+ readonly property var topLevelSurfaceList: {
100+ if (!WMScreen.currentWorkspace) return null;
101+ return stage.temporarySelectedWorkspace ? stage.temporarySelectedWorkspace.windowModel : WMScreen.currentWorkspace.windowModel
102+ }
103+
98 onMainAppChanged: {
104 onMainAppChanged: {
99 if (mainApp) {
105 if (mainApp) {
100 _onMainAppChanged(mainApp.appId);
106 _onMainAppChanged(mainApp.appId);
@@ -183,11 +189,11 @@ StyledItem {
183 }
189 }
184
190
185 function startApp(appId) {
191 function startApp(appId) {
186− if (ApplicationManager.findApplication(appId)) {
192+ if (!ApplicationManager.findApplication(appId)) {
187− ApplicationManager.requestFocusApplication(appId);
188− } else {
189 ApplicationManager.startApplication(appId);
193 ApplicationManager.startApplication(appId);
190 }
194 }
195+ ApplicationManager.requestFocusApplication(appId);
196+ stage.closeSpread();
191 }
197 }
192
198
193 function startLockedApp(app) {
199 function startLockedApp(app) {
@@ -261,23 +267,17 @@ StyledItem {
261 schema.id: "com.canonical.Unity8"
267 schema.id: "com.canonical.Unity8"
262 }
268 }
263
269
270+ PanelState {
271+ id: panelState
272+ objectName: "panelState"
273+ }
274+
264 Item {
275 Item {
265 id: stages
276 id: stages
266 objectName: "stages"
277 objectName: "stages"
267 width: parent.width
278 width: parent.width
268 height: parent.height
279 height: parent.height
269
280
270− SurfaceManager {
271− id: surfaceMan
272− objectName: "surfaceManager"
273− }
274− TopLevelWindowModel {
275− id: topLevelSurfaceList
276− objectName: "topLevelSurfaceList"
277− applicationManager: ApplicationManager // it's a singleton
278− surfaceManager: surfaceMan
279− }
280−
281 Stage {
281 Stage {
282 id: stage
282 id: stage
283 objectName: "stage"
283 objectName: "stage"
@@ -288,7 +288,7 @@ StyledItem {
288 background: wallpaperResolver.background
288 background: wallpaperResolver.background
289
289
290 applicationManager: ApplicationManager
290 applicationManager: ApplicationManager
291− topLevelSurfaceList: topLevelSurfaceList
291+ topLevelSurfaceList: shell.topLevelSurfaceList
292 inputMethodRect: inputMethod.visibleRect
292 inputMethodRect: inputMethod.visibleRect
293 rightEdgePushProgress: rightEdgeBarrier.progress
293 rightEdgePushProgress: rightEdgeBarrier.progress
294 availableDesktopArea: availableDesktopAreaItem
294 availableDesktopArea: availableDesktopAreaItem
@@ -316,6 +316,7 @@ StyledItem {
316 altTabPressed: physicalKeysMapper.altTabPressed
316 altTabPressed: physicalKeysMapper.altTabPressed
317 oskEnabled: shell.oskEnabled
317 oskEnabled: shell.oskEnabled
318 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
318 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
319+ panelState: panelState
319
320
320 onSpreadShownChanged: {
321 onSpreadShownChanged: {
321 panel.indicators.hide();
322 panel.indicators.hide();
@@ -356,7 +357,7 @@ StyledItem {
356 InputMethod {
357 InputMethod {
357 id: inputMethod
358 id: inputMethod
358 objectName: "inputMethod"
359 objectName: "inputMethod"
359− surface: topLevelSurfaceList.inputMethodSurface
360+ surface: shell.topLevelSurfaceList.inputMethodSurface
360 anchors {
361 anchors {
361 fill: parent
362 fill: parent
362 topMargin: panel.panelHeight
363 topMargin: panel.panelHeight
@@ -370,8 +371,13 @@ StyledItem {
370 objectName: "greeterLoader"
371 objectName: "greeterLoader"
371 anchors.fill: parent
372 anchors.fill: parent
372 anchors.topMargin: panel.panelHeight
373 anchors.topMargin: panel.panelHeight
373− sourceComponent: shell.mode != "shell" ? integratedGreeter :
374+ sourceComponent: {
375+ if (shell.mode != "shell") {
376+ if (screenWindow.primary) return integratedGreeter;
377+ return secondaryGreeter;
378+ }
374− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
379+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
380+ }
375 onLoaded: {
381 onLoaded: {
376 item.objectName = "greeter"
382 item.objectName = "greeter"
377 }
383 }
@@ -441,6 +447,13 @@ StyledItem {
441 }
447 }
442 }
448 }
443
449
450+ Component {
451+ id: secondaryGreeter
452+ SecondaryGreeter {
453+ hides: [launcher, panel.indicators]
454+ }
455+ }
456+
444 Timer {
457 Timer {
445 // See powerConnection for why this is useful
458 // See powerConnection for why this is useful
446 id: showGreeterDelayed
459 id: showGreeterDelayed
@@ -551,13 +564,14 @@ StyledItem {
551 && !stage.spreadShown
564 && !stage.spreadShown
552 }
565 }
553
566
554− readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
567+ readonly property bool focusedSurfaceIsFullscreen: shell.topLevelSurfaceList.focusedWindow
555− ? topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
568+ ? shell.topLevelSurfaceList.focusedWindow.state == Mir.FullscreenState
556 : false
569 : false
557 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
570 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
558 || greeter.hasLockedApp
571 || greeter.hasLockedApp
559 greeterShown: greeter && greeter.shown
572 greeterShown: greeter && greeter.shown
560 hasKeyboard: shell.hasKeyboard
573 hasKeyboard: shell.hasKeyboard
574+ panelState: panelState
561 }
575 }
562
576
563 Launcher {
577 Launcher {
@@ -806,11 +820,11 @@ StyledItem {
806 Cursor {
820 Cursor {
807 id: cursor
821 id: cursor
808 objectName: "cursor"
822 objectName: "cursor"
809− visible: shell.hasMouse
823+
810 z: itemGrabber.z + 1
824 z: itemGrabber.z + 1
811 topBoundaryOffset: panel.panelHeight
825 topBoundaryOffset: panel.panelHeight
812−
826+ enabled: shell.hasMouse && screenWindow.active
813− confiningItem: stage.itemConfiningMouseCursor
827+ visible: enabled
814
828
815 property bool mouseNeverMoved: true
829 property bool mouseNeverMoved: true
816 Binding {
830 Binding {
@@ -822,6 +836,8 @@ StyledItem {
822 when: cursor.mouseNeverMoved && cursor.visible
836 when: cursor.mouseNeverMoved && cursor.visible
823 }
837 }
824
838
839+ confiningItem: stage.itemConfiningMouseCursor
840+
825 height: units.gu(3)
841 height: units.gu(3)
826
842
827 readonly property var previewRectangle: stage.previewRectangle.target &&
843 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -885,7 +901,7 @@ StyledItem {
885
901
886 // non-visual objects
902 // non-visual objects
887 KeymapSwitcher {
903 KeymapSwitcher {
888− focusedSurface: topLevelSurfaceList.focusedWindow ? topLevelSurfaceList.focusedWindow.surface : null
904+ focusedSurface: shell.topLevelSurfaceList.focusedWindow ? shell.topLevelSurfaceList.focusedWindow.surface : null
889 }
905 }
890 BrightnessControl {}
906 BrightnessControl {}
891
907
qml/ShellApplication.qmlnew file+60
@@ -0,0 +1,60 @@
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 QtQuick.Window 2.2
19+import WindowManager 1.0
20+import Unity.Application 0.1
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ ShellScreen {
27+ id: window
28+ objectName: "screen"+index
29+ screen: model.screen
30+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
31+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
32+
33+ Binding {
34+ when: applicationArguments.hasGeometry
35+ target: window
36+ property: "width"
37+ value: applicationArguments.windowGeometry.width
38+ }
39+ Binding {
40+ when: applicationArguments.hasGeometry
41+ target: window
42+ property: "height"
43+ value: applicationArguments.windowGeometry.height
44+ }
45+
46+ Component.onCompleted: screen.active = primary
47+ primary: index == 0
48+ }
49+
50+ property var windowManagerSurfaceManagerBinding: Binding {
51+ target: WindowManagerObjects
52+ property: "surfaceManager"
53+ value: SurfaceManager
54+ }
55+ property var windowManagerApplicationManagerBinding: Binding {
56+ target: WindowManagerObjects
57+ property: "applicationManager"
58+ value: ApplicationManager
59+ }
60+}
qml/ShellNotifier.qmlnew file+30
@@ -0,0 +1,30 @@
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+ Shared signals & properties on multi-window desktop
19+ */
20+
21+pragma Singleton
22+import QtQuick 2.4
23+
24+QtObject {
25+ property var greeter: QtObject {
26+ signal hide(bool now)
27+
28+ property bool shown: true
29+ }
30+}
qml/ShellScreen.qmlnew file+64
@@ -0,0 +1,64 @@
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 WindowManager 1.0
20+import Cursor 1.1
21+import "Components"
22+
23+ScreenWindow {
24+ id: screenWindow
25+
26+ color: "black"
27+ title: "Unity8 Shell"
28+ property bool primary: false
29+
30+ DeviceConfiguration {
31+ id: deviceConfiguration
32+ name: applicationArguments.deviceName
33+ }
34+
35+ Loader {
36+ id: loader
37+ width: screenWindow.width
38+ height: screenWindow.height
39+
40+ sourceComponent: {
41+ if (Screens.count > 1 && primary && deviceConfiguration.category !== "desktop") {
42+ return disabledScreenComponent;
43+ }
44+ return shellComponent;
45+ }
46+ }
47+
48+ Component {
49+ id: shellComponent
50+ OrientedShell {
51+ implicitWidth: screenWindow.width
52+ implicitHeight: screenWindow.height
53+
54+ deviceConfiguration {
55+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
56+ }
57+ }
58+ }
59+
60+ Component {
61+ id: disabledScreenComponent
62+ DisabledScreenNotice {}
63+ }
64+}
qml/Stage/ApplicationWindow.qml+10−207
@@ -52,13 +52,8 @@ FocusScope {
52 // other instructions.
52 // other instructions.
53 if (surface) {
53 if (surface) {
54 surfaceContainer.surface = surface;
54 surfaceContainer.surface = surface;
55− d.liveSurface = surface.live;
56− d.hadSurface = false;
57 surfaceInitTimer.start();
55 surfaceInitTimer.start();
58 } else {
56 } else {
59− if (d.surfaceInitialized) {
60− d.hadSurface = true;
61− }
62 d.surfaceInitialized = false;
57 d.surfaceInitialized = false;
63 surfaceContainer.surface = null;
58 surfaceContainer.surface = null;
64 }
59 }
@@ -67,21 +62,6 @@ FocusScope {
67 QtObject {
62 QtObject {
68 id: d
63 id: d
69
64
70− property bool liveSurface: false;
71− property var con: Connections {
72− target: root.surface
73− onLiveChanged: d.liveSurface = root.surface.live
74− }
75− // using liveSurface instead of root.surface.live because with the latter
76− // this expression is not reevaluated when root.surface changes
77− readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
78− && applicationState !== ApplicationInfoInterface.Running
79− onNeedToTakeScreenshotChanged: {
80− if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
81− screenshotImage.take();
82− }
83− }
84−
85 // helpers so that we don't have to check for the existence of an application everywhere
65 // helpers so that we don't have to check for the existence of an application everywhere
86 // (in order to avoid breaking qml binding due to a javascript exception)
66 // (in order to avoid breaking qml binding due to a javascript exception)
87 readonly property string name: root.application ? root.application.name : ""
67 readonly property string name: root.application ? root.application.name : ""
@@ -131,34 +111,10 @@ FocusScope {
131 id: surfaceInitTimer
111 id: surfaceInitTimer
132 interval: 100
112 interval: 100
133 onTriggered: {
113 onTriggered: {
134− if (root.surface && root.surface.live) {d.surfaceInitialized = true;}
114+ if (root.surface && root.surface.live) {
135− }
115+ d.surfaceInitialized = true;
136− }
116+ d.hadSurface = true;
137−
138− Timer {
139− id: surfaceIsOldTimer
140− interval: 1000
141− onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
142− }
143−
144− Image {
145− id: screenshotImage
146− objectName: "screenshotImage"
147− anchors.fill: parent
148− fillMode: Image.PreserveAspectCrop
149− horizontalAlignment: Image.AlignLeft
150− verticalAlignment: Image.AlignTop
151− antialiasing: !root.interactive
152− z: 1
153−
154− function take() {
155− // Save memory by using a half-resolution (thus quarter size) screenshot.
156− // Do not make this a binding, we can only take the screenshot once!
157− surfaceContainer.grabToImage(
158− function(result) {
159− screenshotImage.source = result.url;
160− },
117+ }
161− Qt.size(root.width / 2, root.height / 2));
162 }
118 }
163 }
119 }
164
120
@@ -167,7 +123,7 @@ FocusScope {
167 visible: active
123 visible: active
168 active: false
124 active: false
169 anchors.fill: parent
125 anchors.fill: parent
170− z: screenshotImage.z + 1
126+ z: 1
171 sourceComponent: Component {
127 sourceComponent: Component {
172 Splash {
128 Splash {
173 id: splash
129 id: splash
@@ -238,167 +194,14 @@ FocusScope {
238 id: stateGroup
194 id: stateGroup
239 objectName: "applicationWindowStateGroup"
195 objectName: "applicationWindowStateGroup"
240 states: [
196 states: [
241− State {
197+ State{
242− name: "void"
243− when:
244− d.hadSurface && (!root.surface || !d.surfaceInitialized)
245− &&
246− screenshotImage.status !== Image.Ready
247− },
248− State {
249− name: "splashScreen"
250− when:
251− !d.hadSurface && (!root.surface || !d.surfaceInitialized)
252− &&
253− screenshotImage.status !== Image.Ready
254− },
255− State {
256 name: "surface"
198 name: "surface"
257− when:
258− (root.surface && d.surfaceInitialized)
199+ when: (root.surface && d.surfaceInitialized) || d.hadSurface
259− &&
260− (d.liveSurface ||
261− (d.applicationState !== ApplicationInfoInterface.Running
262− && screenshotImage.status !== Image.Ready))
263− PropertyChanges {
264− target: root
265− implicitWidth: surfaceContainer.implicitWidth
266− implicitHeight: surfaceContainer.implicitHeight
267− }
268− },
269− State {
270− name: "screenshot"
271− when:
272− screenshotImage.status === Image.Ready
273− &&
274− (d.applicationState !== ApplicationInfoInterface.Running
275− || !root.surface || !d.surfaceInitialized)
276 },
200 },
277 State {
201 State {
278− // This is a dead end. From here we expect the surface to be removed from the model
279− // shortly after we stop referencing to it in our SurfaceContainer.
280− name: "closed"
202+ name: "splash"
281− when:
282− // The surface died while the application is running. It must have been closed
283− // by the shell or the application decided to destroy it by itself
284− root.surface && d.surfaceInitialized && !d.liveSurface
203+ when: !root.surface && !d.surfaceInitialized && !d.hadSurface
285− && d.applicationState === ApplicationInfoInterface.Running
286− }
287− ]
288−
289− transitions: [
290− Transition {
291− from: ""; to: "splashScreen"
292− PropertyAction { target: splashLoader; property: "active"; value: true }
293− PropertyAction { target: surfaceContainer
294− property: "visible"; value: false }
295− },
296− Transition {
297− from: "splashScreen"; to: "surface"
298− SequentialAnimation {
299− PropertyAction { target: surfaceContainer
300− property: "opacity"; value: 0.0 }
301− PropertyAction { target: surfaceContainer
302− property: "visible"; value: true }
303− UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
304− from: 0.0; to: 1.0
305− duration: UbuntuAnimation.BriskDuration }
306− ScriptAction { script: {
307− splashLoader.active = false;
308− surfaceIsOldTimer.start();
309− } }
310− }
311− },
312− Transition {
313− from: "surface"; to: "splashScreen"
314− SequentialAnimation {
315− ScriptAction { script: {
316− surfaceIsOldTimer.stop();
317− d.surfaceOldEnoughToBeResized = false;
318− splashLoader.active = true;
319− surfaceContainer.visible = true;
320− } }
321− UbuntuNumberAnimation { target: splashLoader; property: "opacity";
322− from: 0.0; to: 1.0
323− duration: UbuntuAnimation.BriskDuration }
324− PropertyAction { target: surfaceContainer
325− property: "visible"; value: false }
326− }
327− },
328− Transition {
329− from: "surface"; to: "screenshot"
330− SequentialAnimation {
331− ScriptAction { script: {
332− surfaceIsOldTimer.stop();
333− d.surfaceOldEnoughToBeResized = false;
334− screenshotImage.visible = true;
335− } }
336− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
337− from: 0.0; to: 1.0
338− duration: UbuntuAnimation.BriskDuration }
339− ScriptAction { script: {
340− surfaceContainer.visible = false;
341− surfaceContainer.surface = null;
342− d.hadSurface = true;
343− } }
344− }
345− },
346− Transition {
347− from: "screenshot"; to: "surface"
348− SequentialAnimation {
349− PropertyAction { target: surfaceContainer
350− property: "visible"; value: true }
351− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
352− from: 1.0; to: 0.0
353− duration: UbuntuAnimation.BriskDuration }
354− ScriptAction { script: {
355− screenshotImage.visible = false;
356− screenshotImage.source = "";
357− surfaceIsOldTimer.start();
358− } }
359− }
360− },
361− Transition {
362− from: "splashScreen"; to: "screenshot"
363− SequentialAnimation {
364− PropertyAction { target: screenshotImage
365− property: "visible"; value: true }
366− UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
367− from: 0.0; to: 1.0
368− duration: UbuntuAnimation.BriskDuration }
369− PropertyAction { target: splashLoader; property: "active"; value: false }
204+ PropertyChanges { target: splashLoader; active: true }
370− }
371− },
372− Transition {
373− from: "surface"; to: "void"
374− ScriptAction { script: {
375− surfaceIsOldTimer.stop();
376− d.surfaceOldEnoughToBeResized = false;
377− surfaceContainer.visible = false;
378− } }
379− },
380− Transition {
381− from: "void"; to: "surface"
382− SequentialAnimation {
383− PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
384− PropertyAction { target: surfaceContainer; property: "visible"; value: true }
385− UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
386− from: 0.0; to: 1.0
387− duration: UbuntuAnimation.BriskDuration }
388− ScriptAction { script: {
389− surfaceIsOldTimer.start();
390− } }
391− }
392− },
393− Transition {
394− to: "closed"
395− SequentialAnimation {
396− ScriptAction { script: {
397− surfaceContainer.visible = false;
398− surfaceContainer.surface = null;
399− d.hadSurface = true;
400− } }
401− }
402 }
205 }
403 ]
206 ]
404 }
207 }
qml/Stage/DecoratedWindow.qml+13−11
@@ -39,6 +39,7 @@ FocusScope {
39 property alias interactive: applicationWindow.interactive
39 property alias interactive: applicationWindow.interactive
40 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
40 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
41 property alias windowControlButtonsVisible: decoration.windowControlButtonsVisible
41 property alias windowControlButtonsVisible: decoration.windowControlButtonsVisible
42+ property PanelState panelState
42
43
43 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
44 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
44 property bool hasDecoration: true
45 property bool hasDecoration: true
@@ -126,15 +127,15 @@ FocusScope {
126 name: "preview"; when: root.scaleToPreviewProgress > 0
127 name: "preview"; when: root.scaleToPreviewProgress > 0
127 PropertyChanges {
128 PropertyChanges {
128 target: root
129 target: root
129− implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
130+ implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
130− implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
131+ implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
131 }
132 }
132 PropertyChanges {
133 PropertyChanges {
133 target: applicationWindow;
134 target: applicationWindow;
134− requestedWidth: applicationWindow.oldRequestedWidth
135+// requestedWidth: applicationWindow.oldRequestedWidth
135− requestedHeight: applicationWindow.oldRequestedHeight
136+// requestedHeight: applicationWindow.oldRequestedHeight
136− width: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
137+ width: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
137− height: MathUtils.linearAnimation(0, 1, applicationWindow.oldRequestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
138+ height: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
138 itemScale: root.implicitWidth / width
139 itemScale: root.implicitWidth / width
139 }
140 }
140 }
141 }
@@ -173,10 +174,10 @@ FocusScope {
173 height: implicitHeight
174 height: implicitHeight
174 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
175 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
175 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
176 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
176− property int oldRequestedWidth: requestedWidth
177+// property int oldRequestedWidth: requestedWidth
177− property int oldRequestedHeight: requestedHeight
178+// property int oldRequestedHeight: requestedHeight
178− onRequestedWidthChanged: oldRequestedWidth = requestedWidth
179+// onRequestedWidthChanged: oldRequestedWidth = requestedWidth
179− onRequestedHeightChanged: oldRequestedHeight = requestedHeight
180+// onRequestedHeightChanged: oldRequestedHeight = requestedHeight
180 focus: true
181 focus: true
181
182
182 property real itemScale: 1
183 property real itemScale: 1
@@ -219,6 +220,7 @@ FocusScope {
219
220
220 title: applicationWindow.title
221 title: applicationWindow.title
221 windowMoving: moveHandler.moving && !altDragHandler.dragging
222 windowMoving: moveHandler.moving && !altDragHandler.dragging
223+ panelState: root.panelState
222
224
223 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
225 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
224 Behavior on opacity { UbuntuNumberAnimation { } }
226 Behavior on opacity { UbuntuNumberAnimation { } }
@@ -242,7 +244,7 @@ FocusScope {
242 enableMenus: {
244 enableMenus: {
243 return active &&
245 return active &&
244 surface &&
246 surface &&
245− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
247+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
246 }
248 }
247 menu: sharedAppModel.model
249 menu: sharedAppModel.model
248
250
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
35 property int leftMargin
35 property int leftMargin
36 property real appContainerWidth
36 property real appContainerWidth
37 property real appContainerHeight
37 property real appContainerHeight
38+ property PanelState panelState
38
39
39 readonly property real hintThreshold: 0.1
40 readonly property real hintThreshold: 0.1
40
41
@@ -178,7 +179,7 @@ Rectangle {
178 ParallelAnimation {
179 ParallelAnimation {
179 id: fakeMaximizeAnimation
180 id: fakeMaximizeAnimation
180 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
182 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184 }
185 }
@@ -186,39 +187,39 @@ Rectangle {
186 ParallelAnimation {
187 ParallelAnimation {
187 id: fakeMaximizeLeftAnimation
188 id: fakeMaximizeLeftAnimation
188 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
190 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
192 }
193 }
193
194
194 ParallelAnimation {
195 ParallelAnimation {
195 id: fakeMaximizeRightAnimation
196 id: fakeMaximizeRightAnimation
196 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
198 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - panelState.panelHeight }
200 }
201 }
201
202
202 ParallelAnimation {
203 ParallelAnimation {
203 id: fakeMaximizeTopLeftAnimation
204 id: fakeMaximizeTopLeftAnimation
204 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
206 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
208 }
209 }
209
210
210 ParallelAnimation {
211 ParallelAnimation {
211 id: fakeMaximizeTopRightAnimation
212 id: fakeMaximizeTopRightAnimation
212 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: panelState.panelHeight }
214 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215− UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - panelState.panelHeight)/2 }
216 }
217 }
217
218
218 ParallelAnimation {
219 ParallelAnimation {
219 id: fakeMaximizeBottomLeftAnimation
220 id: fakeMaximizeBottomLeftAnimation
220 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
222 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224 }
225 }
@@ -226,7 +227,7 @@ Rectangle {
226 ParallelAnimation {
227 ParallelAnimation {
227 id: fakeMaximizeBottomRightAnimation
228 id: fakeMaximizeBottomRightAnimation
228 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229 UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229− UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + panelState.panelHeight)/2 }
230 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232 }
233 }
qml/Stage/Spread/ScreensAndWorkspaces.qmlnew file+261
@@ -0,0 +1,261 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Ubuntu.Components.Popups 1.3
4+import WindowManager 1.0
5+import Unity.Application 0.1
6+import ".."
7+
8+Item {
9+ id: root
10+
11+ property string background
12+
13+ property var screensProxy: Screens.createProxy();
14+
15+ property QtObject activeWorkspace: null
16+
17+ signal closeSpread();
18+
19+ Row {
20+ id: row
21+ anchors.bottom: parent.bottom
22+ anchors.horizontalCenter: parent.horizontalCenter
23+ Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: UbuntuAnimation.SlowDuration } }
24+ spacing: units.gu(1)
25+
26+ property var selectedIndex: undefined
27+
28+ Repeater {
29+ model: screensProxy
30+
31+ delegate: Item {
32+ height: root.height - units.gu(6)
33+ width: workspaces.width
34+
35+ Item {
36+ id: header
37+ anchors { left: parent.left; top: parent.top; right: parent.right }
38+ height: units.gu(7)
39+ z: 1
40+
41+ property bool isCurrent: {
42+ // another screen is selected.
43+ if (row.selectedIndex != undefined && row.selectedIndex != index) return false;
44+
45+ // this screen is active.
46+ if (WMScreen.active && WMScreen.isSameAs(model.screen) && WMScreen.currentWorkspace.isSameAs(activeWorkspace)) return true;
47+ if (model.screen.workspaces.indexOf(activeWorkspace) >= 0) return true;
48+
49+ // not active.
50+ return false;
51+ }
52+
53+ property bool isSelected: screenMA.containsMouse
54+ onIsSelectedChanged: {
55+ if (isSelected) {
56+ row.selectedIndex = Qt.binding(function() { return index; });
57+ } else if (row.selectedIndex === index) {
58+ row.selectedIndex = undefined;
59+ }
60+ }
61+
62+ UbuntuShape {
63+ anchors.fill: parent
64+ backgroundColor: "white"
65+ opacity: header.isCurrent || header.isSelected ? 1.0 : 0.5
66+ }
67+
68+ DropArea {
69+ anchors.fill: parent
70+ keys: ["workspace"]
71+
72+ onEntered: {
73+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
74+ drag.source.inDropArea = true;
75+ }
76+
77+ onExited: {
78+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
79+ drag.source.inDropArea = false;
80+ }
81+
82+ onDropped: {
83+ drag.source.inDropArea = false;
84+ }
85+ }
86+
87+ Column {
88+ anchors.fill: parent
89+ anchors.margins: units.gu(1)
90+
91+ Label {
92+ text: model.screen.name
93+ color: header.isCurrent || header.isSelected ? "black" : "white"
94+ }
95+
96+ Label {
97+ text: model.screen.outputTypeName
98+ color: header.isCurrent || header.isSelected ? "black" : "white"
99+ fontSize: "x-small"
100+ }
101+
102+ Label {
103+ text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
104+ color: header.isCurrent || header.isSelected ? "black" : "white"
105+ fontSize: "x-small"
106+ }
107+ }
108+
109+ Icon {
110+ anchors {
111+ top: parent.top
112+ right: parent.right
113+ margins: units.gu(1)
114+ }
115+ width: units.gu(3)
116+ height: width
117+ source: "image://theme/select"
118+ color: header.isCurrent || header.isSelected ? "black" : "white"
119+ visible: model.screen.active
120+ }
121+
122+ MouseArea {
123+ id: screenMA
124+ hoverEnabled: true
125+ anchors.fill: parent
126+
127+ onClicked: {
128+ var obj = screensMenuComponent.createObject(header)
129+ obj.open(mouseX, mouseY)
130+ }
131+ }
132+
133+ Component {
134+ id: screensMenuComponent
135+ UbuntuShape {
136+ id: screensMenu
137+ width: units.gu(20)
138+ height: contentColumn.childrenRect.height
139+ backgroundColor: "white"
140+
141+ function open(mouseX, mouseY) {
142+ x = Math.max(0, Math.min(mouseX - width / 2, parent.width - width))
143+ y = mouseY + units.gu(1)
144+ }
145+
146+ InverseMouseArea {
147+ anchors.fill: parent
148+ onClicked: {
149+ screensMenu.destroy()
150+ }
151+ }
152+
153+ Column {
154+ id: contentColumn
155+ width: parent.width
156+ ListItem {
157+ height: layout.height
158+ highlightColor: "transparent"
159+ ListItemLayout {
160+ id: layout
161+ title.text: qsTr("Add workspace")
162+ title.color: "black"
163+ }
164+ onClicked: {
165+ screen.workspaces.addWorkspace();
166+ Screens.sync(root.screensProxy);
167+ screensMenu.destroy();
168+ }
169+ }
170+ }
171+ }
172+ }
173+ }
174+
175+ Workspaces {
176+ id: workspaces
177+ height: parent.height - header.height - units.gu(2)
178+ width: {
179+ var width = 0;
180+ if (screensProxy.count == 1) {
181+ width = Math.min(implicitWidth, root.width - units.gu(8));
182+ } else {
183+ width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
184+ }
185+ return Math.max(workspaces.minimumWidth, width);
186+ }
187+
188+ Behavior on width { UbuntuNumberAnimation {} }
189+ anchors.bottom: parent.bottom
190+ anchors.bottomMargin: units.gu(1)
191+ anchors.horizontalCenter: parent.horizontalCenter
192+ screen: model.screen
193+ background: root.background
194+
195+ workspaceModel: model.screen.workspaces
196+ activeWorkspace: root.activeWorkspace
197+ readOnly: false
198+
199+ onCommitScreenSetup: Screens.sync(root.screensProxy)
200+ onCloseSpread: root.closeSpread();
201+
202+ onClicked: {
203+ root.activeWorkspace = workspace;
204+ }
205+ }
206+ }
207+ }
208+ }
209+
210+ Rectangle {
211+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
212+ width: units.gu(5)
213+ color: "#33000000"
214+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
215+ MouseArea {
216+ id: leftScrollArea
217+ anchors.fill: parent
218+ hoverEnabled: true
219+ onPressed: mouse.accepted = false;
220+ }
221+ DropArea {
222+ id: leftFakeDropArea
223+ anchors.fill: parent
224+ keys: ["application", "workspace"]
225+ }
226+ }
227+ Rectangle {
228+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
229+ width: units.gu(5)
230+ color: "#33000000"
231+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
232+ MouseArea {
233+ id: rightScrollArea
234+ anchors.fill: parent
235+ hoverEnabled: true
236+ onPressed: mouse.accepted = false;
237+ }
238+ DropArea {
239+ id: rightFakeDropArea
240+ anchors.fill: parent
241+ keys: ["application", "workspace"]
242+ }
243+ }
244+ Timer {
245+ repeat: true
246+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
247+ interval: UbuntuAnimation.SlowDuration
248+ triggeredOnStart: true
249+ onTriggered: {
250+ var newOffset = row.anchors.horizontalCenterOffset;
251+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
252+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
253+ newOffset += units.gu(20)
254+ } else {
255+ newOffset -= units.gu(20)
256+ }
257+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
258+ row.anchors.horizontalCenterOffset = newOffset;
259+ }
260+ }
261+}
qml/Stage/Spread/Spread.qml+7−8
@@ -28,10 +28,10 @@ Item {
28 property var spreadFlickable
28 property var spreadFlickable
29
29
30 // some config options
30 // some config options
31− property real contentMargin: 0.16 * root.height
31+ property real contentMargin: 0.1 * root.height
32− property real contentTopMargin: contentMargin
32+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
33− property real contentBottomMargin: 0.35 * contentMargin
33+ property real contentBottomMargin: contentMargin
34− property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
34+ property real windowTitleTopMargin: contentMargin - windowTitle.height
35 property int stackItemCount: 3
35 property int stackItemCount: 3
36 property real leftRotationAngle: 22
36 property real leftRotationAngle: 22
37 property real rightRotationAngle: 32
37 property real rightRotationAngle: 32
@@ -52,7 +52,7 @@ Item {
52
52
53 readonly property real spreadWidth: rightStackXPos - leftStackXPos
53 readonly property real spreadWidth: rightStackXPos - leftStackXPos
54 readonly property real spreadHeight: root.height
54 readonly property real spreadHeight: root.height
55− readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
55+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
56 readonly property real spreadItemWidth: stackHeight
56 readonly property real spreadItemWidth: stackHeight
57
57
58 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
58 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
@@ -92,10 +92,9 @@ Item {
92
92
93 readonly property real visibleItemCount: (spreadWidth / spreadItemWidth) / (1 - itemOverlap)
93 readonly property real visibleItemCount: (spreadWidth / spreadItemWidth) / (1 - itemOverlap)
94
94
95− readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
95+ readonly property real spreadTotalWidth: totalItemCount * spreadWidth / visibleItemCount
96−
97− readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth ,0) / (2 * spreadWidth)
98
96
97+ readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + (leftStackXPos - leftMargin) * 2, 0) / (2 * spreadWidth)
99
98
100 readonly property var curve: BezierCurve {
99 readonly property var curve: BezierCurve {
101 controlPoint2: {'x': 0.19, 'y': 0.00}
100 controlPoint2: {'x': 0.19, 'y': 0.00}
qml/Stage/Spread/SpreadDelegateInputArea.qml+36−17
@@ -28,6 +28,9 @@ Item {
28
28
29 readonly property alias distance: d.distance
29 readonly property alias distance: d.distance
30
30
31+ property var stage: null
32+ property var dragDelegate: null
33+
31 signal clicked()
34 signal clicked()
32 signal close()
35 signal close()
33
36
@@ -84,33 +87,27 @@ Item {
84 }
87 }
85 }
88 }
86
89
87− // Event eater
88− MouseArea {
89− anchors.fill: parent
90− onClicked: root.clicked()
91− onWheel: wheel.accepted = true
92− }
93−
94 MultiPointTouchArea {
90 MultiPointTouchArea {
95 anchors.fill: parent
91 anchors.fill: parent
96− mouseEnabled: false
97 maximumTouchPoints: 1
92 maximumTouchPoints: 1
98 property int offset: 0
93 property int offset: 0
99
94
95+ // tp.startY seems to be broken for mouse interaction... lets track it ourselves
96+ property int startY: 0
97+
100 touchPoints: [
98 touchPoints: [
101 TouchPoint {
99 TouchPoint {
102 id: tp
100 id: tp
103 }
101 }
104 ]
102 ]
105
103
106− onCanceled: {
104+ onPressed: {
107− d.moving = false
105+ startY = tp.y
108− animation.animate("center");
109 }
106 }
110
107
111 onTouchUpdated: {
108 onTouchUpdated: {
112− if (!d.moving) {
109+ if (!d.moving || !tp.pressed) {
113− if (Math.abs(tp.startY - tp.y) > d.threshold) {
110+ if (Math.abs(startY - tp.y) > d.threshold) {
114 d.moving = true;
111 d.moving = true;
115 d.dragEvents = []
112 d.dragEvents = []
116 offset = tp.y - tp.startY;
113 offset = tp.y - tp.startY;
@@ -119,17 +116,32 @@ Item {
119 }
116 }
120 }
117 }
121
118
122− if (root.closeable) {
119+
123− d.distance = tp.y - tp.startY - offset
120+ var value = tp.y - tp.startY - offset;
121+ if (value < 0) {
122+ var coords = mapToItem(stage, tp.x, tp.y);
123+ dragDelegate.Drag.hotSpot.x = dragDelegate.width / 2
124+ dragDelegate.Drag.hotSpot.y = units.gu(2)
125+ dragDelegate.x = coords.x - dragDelegate.Drag.hotSpot.x
126+ dragDelegate.y = coords.y - dragDelegate.Drag.hotSpot.y
127+ dragDelegate.Drag.active = true;
128+ dragDelegate.surface = model.window.surface;
129+
124 } else {
130 } else {
125− var value = tp.y - tp.startY - offset;
131+ if (root.closeable) {
132+ d.distance = value
133+ } else {
126− d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
134+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
135+ }
127 }
136 }
128
137
129 d.pushDragEvent(tp);
138 d.pushDragEvent(tp);
130 }
139 }
131
140
132 onReleased: {
141 onReleased: {
142+ var result = dragDelegate.Drag.drop();
143+ dragDelegate.surface = null;
144+
133 if (!d.moving) {
145 if (!d.moving) {
134 root.clicked()
146 root.clicked()
135 }
147 }
@@ -149,6 +161,13 @@ Item {
149 animation.animate("center")
161 animation.animate("center")
150 }
162 }
151 }
163 }
164+
165+ onCanceled: {
166+ dragDelegate.Drag.active = false;
167+ dragDelegate.surface = null;
168+ d.moving = false
169+ animation.animate("center");
170+ }
152 }
171 }
153
172
154 UbuntuNumberAnimation {
173 UbuntuNumberAnimation {
qml/Stage/Spread/WorkspacePreview.qmlnew file+127
@@ -0,0 +1,127 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Application 0.1
4+import WindowManager 1.0
5+import ".."
6+import "../../Components"
7+
8+Item {
9+ id: previewSpace
10+ clip: true
11+
12+ property var workspace
13+
14+ property string background
15+ property int screenHeight
16+
17+ property real previewScale: previewSpace.height / previewSpace.screenHeight
18+
19+ property bool containsDragLeft: false
20+ property bool containsDragRight: false
21+ property bool isActive: false
22+ property bool isSelected: false
23+
24+ Image {
25+ source: previewSpace.background
26+ anchors.fill: parent
27+ sourceSize.width: width
28+ sourceSize.height: height
29+
30+ Repeater {
31+ id: topLevelSurfaceRepeater
32+ model: visible ? workspace.windowModel : null
33+ delegate: Item {
34+ width: surfaceItem.width
35+ height: surfaceItem.height + decorationHeight * previewScale
36+ x: model.window.position.x * previewScale
37+ y: (model.window.position.y - decorationHeight) * previewScale
38+ z: topLevelSurfaceRepeater.count - index
39+ visible: model.window.state !== Mir.MinimizedState && model.window.state !== Mir.HiddenState
40+
41+ property int decorationHeight: units.gu(3)
42+
43+ WindowDecoration {
44+ width: surfaceItem.implicitWidth
45+ height: parent.decorationHeight
46+ transform: Scale {
47+ origin.x: 0
48+ origin.y: 0
49+ xScale: previewScale
50+ yScale: previewScale
51+ }
52+ title: model.window && model.window.surface ? model.window.surface.name : ""
53+ z: 3
54+ }
55+
56+ MirSurfaceItem {
57+ id: surfaceItem
58+ y: parent.decorationHeight * previewScale
59+ width: implicitWidth * previewScale
60+ height: implicitHeight * previewScale
61+ surfaceWidth: -1
62+ surfaceHeight: -1
63+ surface: model.window.surface
64+ }
65+ }
66+ }
67+
68+ }
69+
70+ Rectangle {
71+ anchors.fill: parent
72+ border.color: UbuntuColors.ash
73+ border.width: units.gu(.5)
74+ color: "transparent"
75+ visible: previewSpace.isActive
76+ }
77+
78+ Rectangle {
79+ anchors.fill: parent
80+ border.color: UbuntuColors.blue
81+ border.width: units.gu(.5)
82+ color: "transparent"
83+ visible: previewSpace.isSelected
84+ }
85+
86+ Rectangle {
87+ anchors.fill: parent
88+ anchors.rightMargin: parent.width / 2
89+ color: "#55000000"
90+ visible: previewSpace.containsDragLeft
91+
92+ Column {
93+ anchors.centerIn: parent
94+ spacing: units.gu(1)
95+ Icon {
96+ source: "../graphics/multi-monitor_drop-here.png"
97+ height: units.gu(4)
98+ width: height
99+ anchors.horizontalCenter: parent.horizontalCenter
100+ }
101+ Label {
102+ text: qsTr("Drop here")
103+ }
104+ }
105+ }
106+
107+ Rectangle {
108+ anchors.fill: parent
109+ anchors.leftMargin: parent.width / 2
110+ color: "#55000000"
111+ visible: previewSpace.containsDragRight
112+
113+ Column {
114+ anchors.centerIn: parent
115+ spacing: units.gu(1)
116+ Icon {
117+ source: "../graphics/multi-monitor_leave.png"
118+ height: units.gu(4)
119+ width: height
120+ anchors.horizontalCenter: parent.horizontalCenter
121+ }
122+ Label {
123+ text: qsTr("Drop and go")
124+ }
125+ }
126+ }
127+}
qml/Stage/Spread/Workspaces.qmlnew file+412
@@ -0,0 +1,412 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import WindowManager 1.0
4+import "MathUtils.js" as MathUtils
5+import "../../Components"
6+
7+Item {
8+ id: root
9+ implicitWidth: listView.contentWidth
10+ readonly property int minimumWidth: {
11+ var count = Math.min(3, listView.count);
12+ return listView.itemWidth * count + listView.spacing * (count - 1)
13+ }
14+
15+ property QtObject screen: null
16+ property alias workspaceModel: listView.model
17+ property var background // TODO: should be stored in the workspace data
18+ property int selectedIndex: -1
19+ property bool readOnly: true
20+ property var activeWorkspace: null
21+
22+ signal commitScreenSetup();
23+ signal closeSpread();
24+ signal clicked(var workspace);
25+
26+ DropArea {
27+ anchors.fill: root
28+
29+ keys: ['workspace']
30+
31+ onEntered: {
32+ var index = listView.getDropIndex(drag);
33+ drag.source.workspace.assign(workspaceModel, index)
34+ drag.source.inDropArea = true;
35+ }
36+
37+ onPositionChanged: {
38+ var index = listView.getDropIndex(drag);
39+ if (listView.dropItemIndex == index) return;
40+ listView.model.move(listView.dropItemIndex, index, 1);
41+ listView.dropItemIndex = index;
42+ }
43+
44+ onExited: {
45+ drag.source.workspace.unassign()
46+ listView.dropItemIndex = -1;
47+ listView.hoveredWorkspaceIndex = -1;
48+ drag.source.inDropArea = false;
49+ }
50+
51+ onDropped: {
52+ drop.accept(Qt.MoveAction);
53+ listView.dropItemIndex = -1;
54+ drag.source.inDropArea = false;
55+ }
56+ }
57+ DropArea {
58+ anchors.fill: parent
59+ keys: ["application"]
60+
61+ onPositionChanged: {
62+ listView.progressiveScroll(drag.x)
63+ listView.updateDropProperties(drag)
64+ }
65+ onExited: {
66+ listView.hoveredWorkspaceIndex = -1
67+ }
68+ onDropped: {
69+ var surface = drag.source.surface;
70+ drag.source.surface = null;
71+ var workspace = listView.model.get(listView.hoveredWorkspaceIndex);
72+ WorkspaceManager.moveSurfaceToWorkspace(surface, workspace);
73+ drop.accept(Qt.MoveAction)
74+ if (listView.hoveredHalf == "right") {
75+ root.closeSpread();
76+ workspace.activate();
77+ }
78+ surface.activate();
79+ listView.hoveredWorkspaceIndex = -1
80+ }
81+ }
82+
83+ onSelectedIndexChanged: {
84+ listView.positionViewAtIndex(selectedIndex, ListView.Center);
85+ }
86+
87+ Item {
88+ // We need to clip the listview as it has left/right margins and it would
89+ // overlap with items next to it and eat mouse input. However, we can't
90+ // just clip at the actual bounds as the delegates have the close button
91+ // on hover which reaches a bit outside, so lets some margins for the clipping
92+ anchors.fill: parent
93+ anchors.margins: -units.gu(2)
94+ clip: true
95+
96+
97+ ListView {
98+ id: listView
99+ anchors {
100+ fill: parent
101+ topMargin: -parent.anchors.margins
102+ bottomMargin: -parent.anchors.margins
103+ leftMargin: -itemWidth - parent.anchors.margins
104+ rightMargin: -itemWidth - parent.anchors.margins
105+ }
106+ boundsBehavior: Flickable.StopAtBounds
107+
108+ Behavior on contentX {
109+ SmoothedAnimation { duration: 200 }
110+ }
111+
112+ property var clickedWorkspace: null
113+
114+ orientation: ListView.Horizontal
115+ spacing: units.gu(1)
116+ leftMargin: itemWidth
117+ rightMargin: itemWidth
118+
119+ property int screenWidth: screen.availableModes[screen.currentModeIndex].size.width
120+ property int screenHeight: screen.availableModes[screen.currentModeIndex].size.height
121+ property int itemWidth: height * screenWidth / screenHeight
122+ property int foldingAreaWidth: itemWidth / 2
123+ property int maxAngle: 40
124+
125+ property real realContentX: contentX - originX + leftMargin
126+ property int dropItemIndex: -1
127+ property int hoveredWorkspaceIndex: -1
128+ property string hoveredHalf: "" // left or right
129+
130+ function getDropIndex(drag) {
131+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
132+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
133+ if (index < 0) index = 0;
134+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
135+ if (index > upperLimit) index = upperLimit;
136+ return index;
137+ }
138+
139+ function updateDropProperties(drag) {
140+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
141+ var index = Math.floor(drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing);
142+ if (index < 0) {
143+ listView.hoveredWorkspaceIndex = -1;
144+ listView.hoveredHalf = "";
145+ return;
146+ }
147+
148+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
149+ if (index > upperLimit) index = upperLimit;
150+ listView.hoveredWorkspaceIndex = index;
151+ var pixelsInTile = (drag.x + listView.realContentX) % (listView.itemWidth + listView.spacing);
152+ listView.hoveredHalf = (pixelsInTile / listView.itemWidth) < .5 ? "left" : "right";
153+ }
154+
155+ function progressiveScroll(mouseX) {
156+ var progress = Math.max(0, Math.min(1, (mouseX - listView.itemWidth) / (width - listView.leftMargin * 2 - listView.itemWidth * 2)))
157+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
158+ }
159+
160+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
161+
162+ delegate: Item {
163+ id: workspaceDelegate
164+ objectName: "delegate" + index
165+ height: parent.height
166+ width: listView.itemWidth
167+ Behavior on width { UbuntuNumberAnimation {} }
168+ visible: listView.dropItemIndex !== index
169+
170+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
171+ property int distanceFromLeft: itemX //- listView.leftMargin
172+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
173+
174+ property int itemAngle: {
175+ if (index == 0) {
176+ if (distanceFromLeft < 0) {
177+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
178+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
179+ }
180+ return 0
181+ }
182+ if (index == listView.count - 1) {
183+ if (distanceFromRight < 0) {
184+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
185+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
186+ }
187+ return 0
188+ }
189+
190+ if (distanceFromLeft < listView.foldingAreaWidth) {
191+ // itemX : 10gu = p : 100
192+ var progress = distanceFromLeft / listView.foldingAreaWidth
193+ return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
194+ }
195+ if (distanceFromRight < listView.foldingAreaWidth) {
196+ var progress = distanceFromRight / listView.foldingAreaWidth
197+ return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
198+ }
199+ return 0
200+ }
201+
202+ property int itemOffset: {
203+ if (index == 0) {
204+ if (distanceFromLeft < 0) {
205+ return -distanceFromLeft
206+ }
207+ return 0
208+ }
209+ if (index == listView.count - 1) {
210+ if (distanceFromRight < 0) {
211+ return distanceFromRight
212+ }
213+ return 0
214+ }
215+
216+ if (itemX < -listView.foldingAreaWidth) {
217+ return -itemX
218+ }
219+ if (distanceFromLeft < listView.foldingAreaWidth) {
220+ return (listView.foldingAreaWidth - distanceFromLeft) / 2
221+ }
222+
223+ if (distanceFromRight < -listView.foldingAreaWidth) {
224+ return distanceFromRight
225+ }
226+
227+ if (distanceFromRight < listView.foldingAreaWidth) {
228+ return -(listView.foldingAreaWidth - distanceFromRight) / 2
229+ }
230+
231+ return 0
232+ }
233+
234+ z: itemOffset < 0 ? itemOffset : -itemOffset
235+ transform: [
236+ Rotation {
237+ angle: itemAngle
238+ axis { x: 0; y: 1; z: 0 }
239+ origin { x: itemAngle < 0 ? listView.itemWidth : 0; y: height / 2 }
240+ },
241+ Translate {
242+ x: itemOffset
243+ }
244+ ]
245+
246+ WorkspacePreview {
247+ id: workspacePreview
248+ height: listView.height
249+ width: listView.itemWidth
250+ background: root.background
251+ screenHeight: listView.screenHeight
252+ containsDragLeft: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "left"
253+ containsDragRight: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "right"
254+ isActive: workspace.isSameAs(root.activeWorkspace)
255+ isSelected: index === root.selectedIndex
256+ workspace: model.workspace
257+ }
258+ MouseArea {
259+ anchors.fill: parent
260+ onClicked: {
261+ root.clicked(model.workspace)
262+ }
263+ onDoubleClicked: {
264+ model.workspace.activate();
265+ root.closeSpread();
266+ }
267+ }
268+
269+ MouseArea {
270+ id: closeMouseArea
271+ objectName: "closeMouseArea"
272+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 }
273+ hoverEnabled: true
274+ height: units.gu(4)
275+ width: height
276+ visible: !root.readOnly && listView.count > 1
277+
278+ onClicked: {
279+ model.workspace.unassign();
280+ root.commitScreenSetup();
281+ }
282+ Image {
283+ id: closeImage
284+ source: "../graphics/window-close.svg"
285+ anchors.fill: closeMouseArea
286+ anchors.margins: units.gu(1)
287+ sourceSize.width: width
288+ sourceSize.height: height
289+ readonly property var mousePos: hoverMouseArea.mapToItem(workspaceDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
290+ readonly property bool shown: (hoverMouseArea.containsMouse || parent.containsMouse)
291+ && mousePos.y < workspaceDelegate.width / 4
292+ && mousePos.y > -units.gu(2)
293+ && mousePos.x > -units.gu(2)
294+ && mousePos.x < workspaceDelegate.height / 4
295+ opacity: shown ? 1 : 0
296+ visible: opacity > 0
297+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
298+
299+ }
300+ }
301+ }
302+
303+ MouseArea {
304+ id: hoverMouseArea
305+ anchors.fill: parent
306+ hoverEnabled: true
307+ propagateComposedEvents: true
308+ anchors.leftMargin: listView.leftMargin
309+ anchors.rightMargin: listView.rightMargin
310+ enabled: !root.readOnly
311+
312+ property int draggedIndex: -1
313+
314+ property int startX: 0
315+ property int startY: 0
316+
317+ onMouseXChanged: {
318+ if (!pressed || dragging) {
319+ listView.progressiveScroll(mouseX)
320+ }
321+ }
322+ onMouseYChanged: {
323+ if (Math.abs(mouseY - startY) > units.gu(3)) {
324+ drag.axis = Drag.XAndYAxis;
325+ }
326+ }
327+
328+ onReleased: {
329+ var result = fakeDragItem.Drag.drop();
330+ // if (result == Qt.IgnoreAction) {
331+ // WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
332+ // }
333+ root.commitScreenSetup();
334+ drag.target = null;
335+ }
336+
337+ property bool dragging: drag.active
338+ onDraggingChanged: {
339+ if (drag.active) {
340+ var ws = listView.model.get(draggedIndex);
341+ if (ws) ws.unassign();
342+ }
343+ }
344+
345+ onPressed: {
346+ startX = mouseX;
347+ startY = mouseY;
348+ if (listView.model.count < 2) return;
349+
350+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
351+ draggedIndex = listView.indexAt(coords.x, coords.y)
352+ var clickedItem = listView.itemAt(coords.x, coords.y)
353+
354+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
355+ fakeDragItem.x = itemCoords.x
356+ fakeDragItem.y = itemCoords.y
357+ fakeDragItem.workspace = listView.model.get(draggedIndex)
358+
359+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
360+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
361+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
362+
363+ drag.axis = Drag.YAxis;
364+ drag.target = fakeDragItem;
365+ }
366+
367+ WorkspacePreview {
368+ id: fakeDragItem
369+ height: listView.height
370+ width: listView.itemWidth
371+ background: root.background
372+ screenHeight: screen.availableModes[screen.currentModeIndex].size.height
373+ visible: Drag.active
374+
375+ Drag.active: hoverMouseArea.drag.active
376+ Drag.keys: ['workspace']
377+
378+ property bool inDropArea: false
379+
380+ Rectangle {
381+ anchors.fill: parent
382+ color: "#33000000"
383+ opacity: parent.inDropArea ? 0 : 1
384+ Behavior on opacity { UbuntuNumberAnimation { } }
385+ Rectangle {
386+ anchors.centerIn: parent
387+ width: units.gu(6)
388+ height: units.gu(6)
389+ radius: width / 2
390+ color: "#aa000000"
391+ }
392+
393+ Icon {
394+ height: units.gu(3)
395+ width: height
396+ anchors.centerIn: parent
397+ name: "edit-delete"
398+ color: "white"
399+ }
400+ }
401+
402+ states: [
403+ State {
404+ when: fakeDragItem.Drag.active
405+ ParentChange { target: fakeDragItem; parent: shell }
406+ }
407+ ]
408+ }
409+ }
410+ }
411+ }
412+}
qml/Stage/Stage.qml+223−112
@@ -48,6 +48,9 @@ FocusScope {
48 property rect inputMethodRect
48 property rect inputMethodRect
49 property real rightEdgePushProgress: 0
49 property real rightEdgePushProgress: 0
50 property Item availableDesktopArea
50 property Item availableDesktopArea
51+ property PanelState panelState
52+
53+ readonly property var temporarySelectedWorkspace: state == "spread" ? screensAndWorkspaces.activeWorkspace : null
51
54
52 // Configuration
55 // Configuration
53 property string mode: "staged"
56 property string mode: "staged"
@@ -154,6 +157,7 @@ FocusScope {
154 function updateFocusedAppOrientationAnimated() { /* TODO */}
157 function updateFocusedAppOrientationAnimated() { /* TODO */}
155
158
156 function closeSpread() {
159 function closeSpread() {
160+ spreadItem.highlightedIndex = -1;
157 priv.goneToSpread = false;
161 priv.goneToSpread = false;
158 }
162 }
159
163
@@ -260,6 +264,43 @@ FocusScope {
260 }
264 }
261 }
265 }
262
266
267+ GlobalShortcut {
268+ id: showWorkspaceSwitcherShortcutLeft
269+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Left
270+ active: !workspaceSwitcher.active
271+ onTriggered: {
272+ root.focus = true;
273+ workspaceSwitcher.showLeft()
274+ }
275+ }
276+ GlobalShortcut {
277+ id: showWorkspaceSwitcherShortcutRight
278+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Right
279+ active: !workspaceSwitcher.active
280+ onTriggered: {
281+ root.focus = true;
282+ workspaceSwitcher.showRight()
283+ }
284+ }
285+ GlobalShortcut {
286+ id: showWorkspaceSwitcherShortcutUp
287+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Up
288+ active: !workspaceSwitcher.active
289+ onTriggered: {
290+ root.focus = true;
291+ workspaceSwitcher.showUp()
292+ }
293+ }
294+ GlobalShortcut {
295+ id: showWorkspaceSwitcherShortcutDown
296+ shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Down
297+ active: !workspaceSwitcher.active
298+ onTriggered: {
299+ root.focus = true;
300+ workspaceSwitcher.showDown()
301+ }
302+ }
303+
263 QtObject {
304 QtObject {
264 id: priv
305 id: priv
265 objectName: "DesktopStagePrivate"
306 objectName: "DesktopStagePrivate"
@@ -395,23 +436,23 @@ FocusScope {
395 readonly property real windowDecorationHeight: units.gu(3)
436 readonly property real windowDecorationHeight: units.gu(3)
396 }
437 }
397
438
398− Component.onCompleted: priv.updateMainAndSideStageIndexes();
439+ Component.onCompleted: priv.updateMainAndSideStageIndexes()
399
440
400 Connections {
441 Connections {
401− target: PanelState
442+ target: panelState
402 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
443 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
403 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
444 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
404 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
445 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
405 }
446 }
406
447
407 Binding {
448 Binding {
408− target: PanelState
449+ target: panelState
409 property: "decorationsVisible"
450 property: "decorationsVisible"
410 value: mode == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !root.spreadShown
451 value: mode == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !root.spreadShown
411 }
452 }
412
453
413 Binding {
454 Binding {
414− target: PanelState
455+ target: panelState
415 property: "title"
456 property: "title"
416 value: {
457 value: {
417 if (priv.focusedAppDelegate !== null) {
458 if (priv.focusedAppDelegate !== null) {
@@ -426,7 +467,7 @@ FocusScope {
426 }
467 }
427
468
428 Binding {
469 Binding {
429− target: PanelState
470+ target: panelState
430 property: "focusedPersistentSurfaceId"
471 property: "focusedPersistentSurfaceId"
431 value: {
472 value: {
432 if (priv.focusedAppDelegate !== null) {
473 if (priv.focusedAppDelegate !== null) {
@@ -440,21 +481,21 @@ FocusScope {
440 }
481 }
441
482
442 Binding {
483 Binding {
443− target: PanelState
484+ target: panelState
444 property: "dropShadow"
485 property: "dropShadow"
445 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
486 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
446 }
487 }
447
488
448 Binding {
489 Binding {
449− target: PanelState
490+ target: panelState
450 property: "closeButtonShown"
491 property: "closeButtonShown"
451 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
492 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
452 }
493 }
453
494
454 Component.onDestruction: {
495 Component.onDestruction: {
455− PanelState.title = "";
496+ panelState.title = "";
456− PanelState.decorationsVisible = false;
497+ panelState.decorationsVisible = false;
457− PanelState.dropShadow = false;
498+ panelState.dropShadow = false;
458 }
499 }
459
500
460 Instantiator {
501 Instantiator {
@@ -500,6 +541,7 @@ FocusScope {
500 PropertyChanges { target: noAppsRunningHint; visible: (root.topLevelSurfaceList.count < 1) }
541 PropertyChanges { target: noAppsRunningHint; visible: (root.topLevelSurfaceList.count < 1) }
501 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
542 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
502 PropertyChanges { target: wallpaper; visible: false }
543 PropertyChanges { target: wallpaper; visible: false }
544+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
503 },
545 },
504 State {
546 State {
505 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
547 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
@@ -554,11 +596,16 @@ FocusScope {
554 Transition {
596 Transition {
555 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
597 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
556 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
598 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
599+ PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
557 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
600 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
601+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
558 },
602 },
559 Transition {
603 Transition {
560 to: "spread"
604 to: "spread"
605+ PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
561 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
606 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
607+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
608+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
562 },
609 },
563 Transition {
610 Transition {
564 from: "spread"
611 from: "spread"
@@ -616,10 +663,20 @@ FocusScope {
616 visible: false
663 visible: false
617 }
664 }
618
665
666+ ScreensAndWorkspaces {
667+ id: screensAndWorkspaces
668+ anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.leftMargin }
669+ height: Math.max(units.gu(30), parent.height * .3)
670+ background: root.background
671+ opacity: 0
672+ visible: opacity > 0
673+ onCloseSpread: priv.goneToSpread = false;
674+ }
675+
619 Spread {
676 Spread {
620 id: spreadItem
677 id: spreadItem
621 objectName: "spreadItem"
678 objectName: "spreadItem"
622− anchors.fill: appContainer
679+ anchors { left: parent.left; bottom: parent.bottom; right: parent.right; top: screensAndWorkspaces.bottom }
623 leftMargin: root.availableDesktopArea.x
680 leftMargin: root.availableDesktopArea.x
624 model: root.topLevelSurfaceList
681 model: root.topLevelSurfaceList
625 spreadFlickable: floatingFlickable
682 spreadFlickable: floatingFlickable
@@ -632,6 +689,89 @@ FocusScope {
632 onCloseCurrentApp: {
689 onCloseCurrentApp: {
633 appRepeater.itemAt(highlightedIndex).close();
690 appRepeater.itemAt(highlightedIndex).close();
634 }
691 }
692+
693+ FloatingFlickable {
694+ id: floatingFlickable
695+ objectName: "spreadFlickable"
696+ anchors.fill: parent
697+ enabled: false
698+ contentWidth: spreadItem.spreadTotalWidth
699+
700+ function snap(toIndex) {
701+ var delegate = appRepeater.itemAt(toIndex)
702+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
703+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
704+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
705+ snapAnimation.to = floatingFlickable.contentX - offset;
706+ snapAnimation.start();
707+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
708+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
709+ snapAnimation.to = floatingFlickable.contentX - offset;
710+ snapAnimation.start();
711+ }
712+ }
713+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
714+ }
715+
716+ MouseArea {
717+ id: hoverMouseArea
718+ objectName: "hoverMouseArea"
719+ anchors.fill: parent
720+ propagateComposedEvents: true
721+ hoverEnabled: true
722+ enabled: false
723+ visible: enabled
724+ property bool wasTouchPress: false
725+
726+ property int scrollAreaWidth: width / 3
727+ property bool progressiveScrollingEnabled: false
728+
729+ onMouseXChanged: {
730+ mouse.accepted = false
731+
732+ if (hoverMouseArea.pressed || wasTouchPress) {
733+ return;
734+ }
735+
736+ // Find the hovered item and mark it active
737+ for (var i = appRepeater.count - 1; i >= 0; i--) {
738+ var appDelegate = appRepeater.itemAt(i);
739+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
740+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
741+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
742+ spreadItem.highlightedIndex = i;
743+ break;
744+ }
745+ }
746+
747+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
748+ var margins = floatingFlickable.width * 0.05;
749+
750+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
751+ progressiveScrollingEnabled = true
752+ }
753+
754+ // do we need to scroll?
755+ if (mouseX < scrollAreaWidth + margins) {
756+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
757+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
758+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
759+ }
760+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
761+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
762+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
763+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
764+ }
765+ }
766+ }
767+
768+ onPressed: {
769+ mouse.accepted = false;
770+ wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
771+ }
772+
773+ onExited: wasTouchPress = false;
774+ }
635 }
775 }
636
776
637 Label {
777 Label {
@@ -739,6 +879,24 @@ FocusScope {
739 }
879 }
740 }
880 }
741
881
882+ MirSurfaceItem {
883+ id: fakeDragItem
884+ property real previewScale: .5
885+ height: (screensAndWorkspaces.height - units.gu(8)) / 2
886+ // w : h = iw : ih
887+ width: implicitWidth * height / implicitHeight
888+ surfaceWidth: -1
889+ surfaceHeight: -1
890+ opacity: surface != null ? 1 : 0
891+ Behavior on opacity { UbuntuNumberAnimation {} }
892+ visible: opacity > 0
893+
894+ Drag.active: surface != null
895+ Drag.keys: ["application"]
896+
897+ z: 1000
898+ }
899+
742 Repeater {
900 Repeater {
743 id: appRepeater
901 id: appRepeater
744 model: topLevelSurfaceList
902 model: topLevelSurfaceList
@@ -767,6 +925,9 @@ FocusScope {
767 }
925 }
768 z: normalZ
926 z: normalZ
769
927
928+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
929+ Behavior on opacity { UbuntuNumberAnimation {} }
930+
770 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
931 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
771 // match what the actual surface size is.
932 // match what the actual surface size is.
772 // Don't write to those, they will be set by states
933 // Don't write to those, they will be set by states
@@ -946,7 +1107,6 @@ FocusScope {
946 function claimFocus() {
1107 function claimFocus() {
947 if (root.state == "spread") {
1108 if (root.state == "spread") {
948 spreadItem.highlightedIndex = index
1109 spreadItem.highlightedIndex = index
949− priv.goneToSpread = false;
950 }
1110 }
951 if (root.mode == "stagedWithSideStage") {
1111 if (root.mode == "stagedWithSideStage") {
952 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1112 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
@@ -1280,8 +1440,6 @@ FocusScope {
1280 y: spreadMaths.targetY
1440 y: spreadMaths.targetY
1281 z: index
1441 z: index
1282 height: spreadItem.spreadItemHeight
1442 height: spreadItem.spreadItemHeight
1283− requestedWidth: decoratedWindow.oldRequestedWidth
1284− requestedHeight: decoratedWindow.oldRequestedHeight
1285 visible: spreadMaths.itemVisible
1443 visible: spreadMaths.itemVisible
1286 }
1444 }
1287 PropertyChanges { target: dragArea; enabled: true }
1445 PropertyChanges { target: dragArea; enabled: true }
@@ -1301,8 +1459,6 @@ FocusScope {
1301 y: stagedRightEdgeMaths.animatedY
1459 y: stagedRightEdgeMaths.animatedY
1302 z: stagedRightEdgeMaths.animatedZ
1460 z: stagedRightEdgeMaths.animatedZ
1303 height: stagedRightEdgeMaths.animatedHeight
1461 height: stagedRightEdgeMaths.animatedHeight
1304− requestedWidth: decoratedWindow.oldRequestedWidth
1305− requestedHeight: decoratedWindow.oldRequestedHeight
1306 visible: appDelegate.x < root.width
1462 visible: appDelegate.x < root.width
1307 }
1463 }
1308 PropertyChanges {
1464 PropertyChanges {
@@ -1332,8 +1488,6 @@ FocusScope {
1332 y: windowedRightEdgeMaths.animatedY
1488 y: windowedRightEdgeMaths.animatedY
1333 z: windowedRightEdgeMaths.animatedZ
1489 z: windowedRightEdgeMaths.animatedZ
1334 height: stagedRightEdgeMaths.animatedHeight
1490 height: stagedRightEdgeMaths.animatedHeight
1335− requestedWidth: decoratedWindow.oldRequestedWidth
1336− requestedHeight: decoratedWindow.oldRequestedHeight
1337 }
1491 }
1338 PropertyChanges {
1492 PropertyChanges {
1339 target: decoratedWindow
1493 target: decoratedWindow
@@ -1356,11 +1510,15 @@ FocusScope {
1356 target: appDelegate
1510 target: appDelegate
1357 x: stageMaths.itemX
1511 x: stageMaths.itemX
1358 y: root.availableDesktopArea.y
1512 y: root.availableDesktopArea.y
1359− requestedWidth: appContainer.width
1360− requestedHeight: root.availableDesktopArea.height
1361 visuallyMaximized: true
1513 visuallyMaximized: true
1362 visible: appDelegate.x < root.width
1514 visible: appDelegate.x < root.width
1363 }
1515 }
1516+ PropertyChanges {
1517+ target: appDelegate
1518+ requestedWidth: appContainer.width
1519+ requestedHeight: root.availableDesktopArea.height
1520+ restoreEntryValues: false
1521+ }
1364 PropertyChanges {
1522 PropertyChanges {
1365 target: decoratedWindow
1523 target: decoratedWindow
1366 hasDecoration: false
1524 hasDecoration: false
@@ -1389,11 +1547,15 @@ FocusScope {
1389 x: stageMaths.itemX
1547 x: stageMaths.itemX
1390 y: root.availableDesktopArea.y
1548 y: root.availableDesktopArea.y
1391 z: stageMaths.itemZ
1549 z: stageMaths.itemZ
1392− requestedWidth: stageMaths.itemWidth
1393− requestedHeight: root.availableDesktopArea.height
1394 visuallyMaximized: true
1550 visuallyMaximized: true
1395 visible: appDelegate.x < root.width
1551 visible: appDelegate.x < root.width
1396 }
1552 }
1553+ PropertyChanges {
1554+ target: appDelegate
1555+ requestedWidth: stageMaths.itemWidth
1556+ requestedHeight: root.availableDesktopArea.height
1557+ restoreEntryValues: false
1558+ }
1397 PropertyChanges {
1559 PropertyChanges {
1398 target: decoratedWindow
1560 target: decoratedWindow
1399 hasDecoration: false
1561 hasDecoration: false
@@ -1414,8 +1576,13 @@ FocusScope {
1414 requestedX: root.availableDesktopArea.x;
1576 requestedX: root.availableDesktopArea.x;
1415 requestedY: 0;
1577 requestedY: 0;
1416 visuallyMinimized: false;
1578 visuallyMinimized: false;
1579+ visuallyMaximized: true
1580+ }
1581+ PropertyChanges {
1582+ target: appDelegate
1417 requestedWidth: root.availableDesktopArea.width;
1583 requestedWidth: root.availableDesktopArea.width;
1418 requestedHeight: appContainer.height;
1584 requestedHeight: appContainer.height;
1585+ restoreEntryValues: false
1419 }
1586 }
1420 PropertyChanges { target: touchControls; enabled: true }
1587 PropertyChanges { target: touchControls; enabled: true }
1421 PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
1588 PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
@@ -1426,8 +1593,12 @@ FocusScope {
1426 target: appDelegate;
1593 target: appDelegate;
1427 requestedX: 0
1594 requestedX: 0
1428 requestedY: 0
1595 requestedY: 0
1596+ }
1597+ PropertyChanges {
1598+ target: appDelegate
1429− requestedWidth: appContainer.width;
1599+ requestedWidth: appContainer.width
1430− requestedHeight: appContainer.height;
1600+ requestedHeight: appContainer.height
1601+ restoreEntryValues: false
1431 }
1602 }
1432 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1603 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1433 },
1604 },
@@ -1441,6 +1612,12 @@ FocusScope {
1441 PropertyChanges { target: touchControls; enabled: true }
1612 PropertyChanges { target: touchControls; enabled: true }
1442 PropertyChanges { target: resizeArea; enabled: true }
1613 PropertyChanges { target: resizeArea; enabled: true }
1443 PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
1614 PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
1615+ PropertyChanges {
1616+ target: appDelegate
1617+ requestedWidth: windowedWidth
1618+ requestedHeight: windowedHeight
1619+ restoreEntryValues: false
1620+ }
1444 },
1621 },
1445 State {
1622 State {
1446 name: "restored";
1623 name: "restored";
@@ -1541,6 +1718,7 @@ FocusScope {
1541 }
1718 }
1542 }
1719 }
1543 ]
1720 ]
1721+
1544 transitions: [
1722 transitions: [
1545 Transition {
1723 Transition {
1546 from: "staged,stagedWithSideStage"
1724 from: "staged,stagedWithSideStage"
@@ -1554,6 +1732,7 @@ FocusScope {
1554 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1732 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1555 },
1733 },
1556 Transition {
1734 Transition {
1735+ from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,staged,stagedWithSideStage,windowedRightEdge,stagedRightEdge";
1557 to: "spread"
1736 to: "spread"
1558 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1737 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1559 PropertyAction { target: appDelegate; properties: "z,visible" }
1738 PropertyAction { target: appDelegate; properties: "z,visible" }
@@ -1646,7 +1825,7 @@ FocusScope {
1646 ]
1825 ]
1647
1826
1648 Binding {
1827 Binding {
1649− target: PanelState
1828+ target: panelState
1650 property: "decorationsAlwaysVisible"
1829 property: "decorationsAlwaysVisible"
1651 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1830 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1652 }
1831 }
@@ -1667,6 +1846,7 @@ FocusScope {
1667 borderThickness: units.gu(2)
1846 borderThickness: units.gu(2)
1668 enabled: false
1847 enabled: false
1669 visible: enabled
1848 visible: enabled
1849+ readyToAssesBounds: !appDelegate._constructing
1670
1850
1671 onPressed: {
1851 onPressed: {
1672 appDelegate.activate();
1852 appDelegate.activate();
@@ -1690,18 +1870,13 @@ FocusScope {
1690 width: implicitWidth
1870 width: implicitWidth
1691 height: implicitHeight
1871 height: implicitHeight
1692 highlightSize: windowInfoItem.iconMargin / 2
1872 highlightSize: windowInfoItem.iconMargin / 2
1693− altDragEnabled: root.mode == "windowed"
1694 boundsItem: root.availableDesktopArea
1873 boundsItem: root.availableDesktopArea
1874+ panelState: root.panelState
1875+ altDragEnabled: root.mode == "windowed"
1695
1876
1696 requestedWidth: appDelegate.requestedWidth
1877 requestedWidth: appDelegate.requestedWidth
1697 requestedHeight: appDelegate.requestedHeight
1878 requestedHeight: appDelegate.requestedHeight
1698
1879
1699− property int oldRequestedWidth: -1
1700− property int oldRequestedHeight: -1
1701−
1702− onRequestedWidthChanged: oldRequestedWidth = requestedWidth
1703− onRequestedHeightChanged: oldRequestedHeight = requestedHeight
1704−
1705 onCloseClicked: { appDelegate.close(); }
1880 onCloseClicked: { appDelegate.close(); }
1706 onMaximizeClicked: {
1881 onMaximizeClicked: {
1707 if (appDelegate.canBeMaximized) {
1882 if (appDelegate.canBeMaximized) {
@@ -1782,6 +1957,8 @@ FocusScope {
1782 anchors.fill: decoratedWindow
1957 anchors.fill: decoratedWindow
1783 enabled: false
1958 enabled: false
1784 closeable: true
1959 closeable: true
1960+ stage: root
1961+ dragDelegate: fakeDragItem
1785
1962
1786 onClicked: {
1963 onClicked: {
1787 spreadItem.highlightedIndex = index;
1964 spreadItem.highlightedIndex = index;
@@ -1824,12 +2001,15 @@ FocusScope {
1824 objectName: "closeMouseArea"
2001 objectName: "closeMouseArea"
1825 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
2002 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1826 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
2003 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1827− visible: dragArea.distance == 0
2004+ readonly property bool shown: dragArea.distance == 0
1828 && index == spreadItem.highlightedIndex
2005 && index == spreadItem.highlightedIndex
1829 && mousePos.y < (decoratedWindow.height / 3)
2006 && mousePos.y < (decoratedWindow.height / 3)
1830 && mousePos.y > -units.gu(4)
2007 && mousePos.y > -units.gu(4)
1831 && mousePos.x > -units.gu(4)
2008 && mousePos.x > -units.gu(4)
1832 && mousePos.x < (decoratedWindow.width * 2 / 3)
2009 && mousePos.x < (decoratedWindow.width * 2 / 3)
2010+ opacity: shown ? 1 : 0
2011+ visible: opacity > 0
2012+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
1833 height: units.gu(6)
2013 height: units.gu(6)
1834 width: height
2014 width: height
1835
2015
@@ -1900,89 +2080,20 @@ FocusScope {
1900 leftMargin: root.availableDesktopArea.x
2080 leftMargin: root.availableDesktopArea.x
1901 appContainerWidth: appContainer.width
2081 appContainerWidth: appContainer.width
1902 appContainerHeight: appContainer.height
2082 appContainerHeight: appContainer.height
2083+ panelState: root.panelState
1903 }
2084 }
1904
2085
1905− MouseArea {
2086+ WorkspaceSwitcher {
1906− id: hoverMouseArea
2087+ id: workspaceSwitcher
1907− objectName: "hoverMouseArea"
2088+ anchors.centerIn: parent
1908− anchors.fill: appContainer
2089+ height: units.gu(20)
1909− propagateComposedEvents: true
2090+ width: root.width - units.gu(8)
1910− hoverEnabled: true
2091+ background: root.background
1911− enabled: false
2092+ onActiveChanged: {
1912− visible: enabled
2093+ if (!active) {
1913−
2094+ appContainer.focus = true;
1914− property int scrollAreaWidth: width / 3
1915− property bool progressiveScrollingEnabled: false
1916− property bool wasTouchPress: false
1917−
1918− onMouseXChanged: {
1919− mouse.accepted = false
1920−
1921− if (hoverMouseArea.pressed || wasTouchPress) {
1922− return;
1923− }
1924−
1925− // Find the hovered item and mark it active
1926− for (var i = appRepeater.count - 1; i >= 0; i--) {
1927− var appDelegate = appRepeater.itemAt(i);
1928− var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1929− var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1930− if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1931− spreadItem.highlightedIndex = i;
1932− break;
1933− }
1934− }
1935−
1936− if (floatingFlickable.contentWidth > floatingFlickable.width) {
1937− var margins = floatingFlickable.width * 0.05;
1938−
1939− if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1940− progressiveScrollingEnabled = true
1941− }
1942−
1943− // do we need to scroll?
1944− if (mouseX < scrollAreaWidth + margins) {
1945− var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1946− var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1947− floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1948− }
1949− if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1950− var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1951− var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1952− floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1953− }
1954− }
1955− }
1956−
1957− onPressed: {
1958− mouse.accepted = false;
1959− wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
1960− }
1961−
1962− onExited: wasTouchPress = false;
1963− }
1964−
1965− FloatingFlickable {
1966− id: floatingFlickable
1967− objectName: "spreadFlickable"
1968− anchors.fill: appContainer
1969− enabled: false
1970− contentWidth: spreadItem.spreadTotalWidth
1971−
1972− function snap(toIndex) {
1973− var delegate = appRepeater.itemAt(toIndex)
1974− var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1975− if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1976− var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1977− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1978− snapAnimation.start();
1979− } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1980− var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1981− snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1982− snapAnimation.start();
1983 }
2095 }
1984 }
2096 }
1985− UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1986 }
2097 }
1987
2098
1988 PropertyAnimation {
2099 PropertyAnimation {
qml/Stage/WindowDecoration.qml+2
@@ -35,6 +35,7 @@ MouseArea {
35 property bool enableMenus: true
35 property bool enableMenus: true
36 property bool windowMoving: false
36 property bool windowMoving: false
37 property alias windowControlButtonsVisible: buttons.visible
37 property alias windowControlButtonsVisible: buttons.visible
38+ property PanelState panelState
38
39
39 readonly property real buttonsWidth: buttons.width + row.spacing
40 readonly property real buttonsWidth: buttons.width + row.spacing
40
41
@@ -147,6 +148,7 @@ MouseArea {
147 enableKeyFilter: valid && root.active && root.enableMenus
148 enableKeyFilter: valid && root.active && root.enableMenus
148 unityMenuModel: root.menu
149 unityMenuModel: root.menu
149 windowMoving: root.windowMoving
150 windowMoving: root.windowMoving
151+ panelState: root.panelState
150
152
151 onPressed: root.onPressed(mouse)
153 onPressed: root.onPressed(mouse)
152 onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
154 onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
qml/Stage/WindowResizeArea.qml+27−4
@@ -36,35 +36,58 @@ MouseArea {
36 property int minWidth: 0
36 property int minWidth: 0
37 property int minHeight: 0
37 property int minHeight: 0
38
38
39+ property bool readyToAssesBounds: false
40+ onReadyToAssesBoundsChanged: d.reassesBounds()
41+
39 QtObject {
42 QtObject {
40 id: d
43 id: d
41
44
42 readonly property int maxSafeInt: 2147483647
45 readonly property int maxSafeInt: 2147483647
43 readonly property int maxSizeIncrement: units.gu(40)
46 readonly property int maxSizeIncrement: units.gu(40)
44
47
48+ function reassesBounds() {
49+ if (!readyToAssesBounds) return;
50+
51+ if (target.windowedWidth < minimumWidth) {
52+ target.windowedWidth = minimumWidth;
53+ }
54+ if (target.windowedHeight < minimumHeight) {
55+ target.windowedHeight = minimumHeight;
56+ }
57+ if (target.windowedHeight < minimumHeight) {
58+ target.windowedHeight = minimumHeight;
59+ }
60+ if (target.windowedWidth > maximumWidth) {
61+ target.windowedWidth = maximumWidth;
62+ }
63+ if (target.windowedHeight > maximumHeight) {
64+ target.windowedHeight = maximumHeight;
65+ }
66+ }
67+
45 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
68 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
46 onMinimumWidthChanged: {
69 onMinimumWidthChanged: {
47− if (target.windowedWidth < minimumWidth) {
70+ if (readyToAssesBounds && target.windowedWidth < minimumWidth) {
48 target.windowedWidth = minimumWidth;
71 target.windowedWidth = minimumWidth;
49 }
72 }
50 }
73 }
51 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
74 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
52 onMinimumHeightChanged: {
75 onMinimumHeightChanged: {
53− if (target.windowedHeight < minimumHeight) {
76+ if (readyToAssesBounds && target.windowedHeight < minimumHeight) {
54 target.windowedHeight = minimumHeight;
77 target.windowedHeight = minimumHeight;
55 }
78 }
56 }
79 }
57 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
80 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
58 ? root.target.maximumWidth : maxSafeInt
81 ? root.target.maximumWidth : maxSafeInt
59 onMaximumWidthChanged: {
82 onMaximumWidthChanged: {
60− if (target.windowedWidth > maximumWidth) {
83+ if (readyToAssesBounds && target.windowedWidth > maximumWidth) {
61 target.windowedWidth = maximumWidth;
84 target.windowedWidth = maximumWidth;
62 }
85 }
63 }
86 }
64 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
87 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
65 ? root.target.maximumHeight : maxSafeInt
88 ? root.target.maximumHeight : maxSafeInt
66 onMaximumHeightChanged: {
89 onMaximumHeightChanged: {
67− if (target.windowedHeight > maximumHeight) {
90+ if (readyToAssesBounds && target.windowedHeight > maximumHeight) {
68 target.windowedHeight = maximumHeight;
91 target.windowedHeight = maximumHeight;
69 }
92 }
70 }
93 }
qml/Stage/WindowStateSaver.qml+4−1
@@ -43,7 +43,10 @@ QtObject {
43 (target.fullscreen ? 0 : root.leftMargin)); });
43 (target.fullscreen ? 0 : root.leftMargin)); });
44 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
44 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
45
45
46− target.updateNormalGeometry();
46+ target.normalWidth = target.windowedWidth;
47+ target.normalHeight = target.windowedHeight;
48+ target.normalX = target.windowedX;
49+ target.normalY = target.windowedY;
47
50
48 // initialize the x/y to restore to
51 // initialize the x/y to restore to
49 target.restoredX = target.normalX;
52 target.restoredX = target.normalX;
qml/Stage/WorkspaceSwitcher.qmlnew file+196
@@ -0,0 +1,196 @@
1+/*
2+ * Copyright (C) 2014-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 "Spread"
20+import WindowManager 1.0
21+import Unity.Application 0.1
22+
23+Item {
24+ id: root
25+
26+ opacity: d.shown ? 1 : 0
27+ visible: opacity > 0
28+ Behavior on opacity { UbuntuNumberAnimation {} }
29+
30+ property var screensProxy: Screens.createProxy();
31+ property string background
32+
33+ readonly property alias active: d.active
34+
35+ function showLeft() {
36+ show();
37+ d.previousWorkspace();
38+ }
39+ function showRight() {
40+ show();
41+ d.nextWorkspace();
42+ }
43+ function showUp() {
44+ show();
45+ d.previousScreen();
46+ }
47+ function showDown() {
48+ show();
49+ d.nextScreen();
50+ }
51+
52+ function show() {
53+ hideTimer.stop();
54+ d.altPressed = true;
55+ d.ctrlPressed = true;
56+ d.active = true;
57+ d.shown = true;
58+ focus = true;
59+
60+ d.highlightedScreenIndex = screensProxy.activeScreen;
61+ var activeScreen = screensProxy.get(screensProxy.activeScreen);
62+ d.highlightedWorkspaceIndex = activeScreen.workspaces.indexOf(activeScreen.currentWorkspace)
63+ }
64+
65+ QtObject {
66+ id: d
67+
68+ property bool active: false
69+ property bool shown: false
70+ property bool altPressed: false
71+ property bool ctrlPressed: false
72+
73+ property int rowHeight: root.height - units.gu(4)
74+
75+ property int highlightedScreenIndex: -1
76+ property int highlightedWorkspaceIndex: -1
77+
78+ function previousWorkspace() {
79+ highlightedWorkspaceIndex = Math.max(highlightedWorkspaceIndex - 1, 0);
80+ }
81+ function nextWorkspace() {
82+ var screen = screensProxy.get(highlightedScreenIndex);
83+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex + 1, screen.workspaces.count - 1);
84+ }
85+ function previousScreen() {
86+ highlightedScreenIndex = Math.max(highlightedScreenIndex - 1, 0);
87+ var screen = screensProxy.get(highlightedScreenIndex);
88+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex, screen.workspaces.count - 1)
89+ }
90+ function nextScreen() {
91+ highlightedScreenIndex = Math.min(highlightedScreenIndex + 1, screensProxy.count - 1);
92+ var screen = screensProxy.get(highlightedScreenIndex);
93+ highlightedWorkspaceIndex = Math.min(highlightedWorkspaceIndex, screen.workspaces.count - 1)
94+ }
95+ }
96+
97+ Timer {
98+ id: hideTimer
99+ interval: 300
100+ onTriggered: d.shown = false;
101+ }
102+
103+ Keys.onPressed: {
104+ switch (event.key) {
105+ case Qt.Key_Left:
106+ d.previousWorkspace();
107+ break;
108+ case Qt.Key_Right:
109+ d.nextWorkspace()
110+ break;
111+ case Qt.Key_Up:
112+ d.previousScreen();
113+ break;
114+ case Qt.Key_Down:
115+ d.nextScreen();
116+ }
117+ }
118+ Keys.onReleased: {
119+ switch (event.key) {
120+ case Qt.Key_Alt:
121+ d.altPressed = false;
122+ break;
123+ case Qt.Key_Control:
124+ d.ctrlPressed = false;
125+ break;
126+ }
127+
128+ if (!d.altPressed && !d.ctrlPressed) {
129+ d.active = false;
130+ hideTimer.start();
131+ focus = false;
132+ screensProxy.get(d.highlightedScreenIndex).workspaces.get(d.highlightedWorkspaceIndex).activate();
133+ }
134+ }
135+
136+ UbuntuShape {
137+ backgroundColor: "#F2111111"
138+ clip: true
139+ width: Math.min(parent.width, screensColumn.width + units.gu(4))
140+ anchors.horizontalCenter: parent.horizontalCenter
141+ height: parent.height
142+
143+ Column {
144+ id: screensColumn
145+ anchors {
146+ top: parent.top; topMargin: units.gu(2) - d.highlightedScreenIndex * (d.rowHeight + screensColumn.spacing)
147+ left: parent.left; leftMargin: units.gu(2)
148+ }
149+ width: screensRepeater.itemAt(d.highlightedScreenIndex).width
150+ spacing: units.gu(2)
151+ Behavior on anchors.topMargin { UbuntuNumberAnimation {} }
152+ Behavior on width { UbuntuNumberAnimation {} }
153+
154+ Repeater {
155+ id: screensRepeater
156+ model: screensProxy
157+
158+ delegate: Item {
159+ height: d.rowHeight
160+ width: workspaces.width
161+ anchors.horizontalCenter: parent.horizontalCenter
162+ opacity: d.highlightedScreenIndex == index ? 1 : 0
163+ Behavior on opacity { UbuntuNumberAnimation {} }
164+
165+ UbuntuShape {
166+ id: header
167+ anchors { left: parent.left; top: parent.top; right: parent.right }
168+ height: units.gu(4)
169+ backgroundColor: "white"
170+
171+ Label {
172+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
173+ text: model.screen.name
174+ color: UbuntuColors.ash
175+ }
176+ }
177+
178+ Workspaces {
179+ id: workspaces
180+ height: parent.height - header.height - units.gu(2)
181+ width: Math.min(implicitWidth, root.width - units.gu(4))
182+
183+ anchors.bottom: parent.bottom
184+ anchors.bottomMargin: units.gu(1)
185+ anchors.horizontalCenter: parent.horizontalCenter
186+ screen: model.screen
187+ background: root.background
188+ selectedIndex: d.highlightedScreenIndex == index ? d.highlightedWorkspaceIndex : -1
189+
190+ workspaceModel: model.screen.workspaces
191+ }
192+ }
193+ }
194+ }
195+ }
196+}
qml/Stage/graphics/multi-monitor_drop-here.pngnew file

Binary file not shown.

qml/Stage/graphics/multi-monitor_leave.pngnew file

Binary file not shown.

qml/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ShellNotifier 0.1 ShellNotifier.qml
src/ApplicationArguments.cpp+11−2
@@ -16,7 +16,16 @@
16
16
17#include "ApplicationArguments.h"
17#include "ApplicationArguments.h"
18
18
19−ApplicationArguments::ApplicationArguments(QObject *parent)
19+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
20− : QObject(parent)
20+ : QObject(app)
21+ , UnityCommandLineParser(*app)
21{
22{
22}
23}
24+
25+void ApplicationArguments::setDeviceName(const QString &deviceName)
26+{
27+ if (deviceName != m_deviceName) {
28+ m_deviceName = deviceName;
29+ Q_EMIT deviceNameChanged(m_deviceName);
30+ }
31+}
src/ApplicationArguments.h+17−15
@@ -23,31 +23,33 @@
23#include <QSize>
23#include <QSize>
24#include <QString>
24#include <QString>
25
25
26+#include "UnityCommandLineParser.h"
27+
26−class ApplicationArguments : public QObject
28+class ApplicationArguments : public QObject,
29+ public UnityCommandLineParser
27{
30{
28 Q_OBJECT
31 Q_OBJECT
29 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
32 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
30 Q_PROPERTY(QString mode READ mode CONSTANT)
33 Q_PROPERTY(QString mode READ mode CONSTANT)
34+
35+ Q_PROPERTY(bool hasGeometry READ hasGeometry CONSTANT)
36+ Q_PROPERTY(QSize windowGeometry READ windowGeometry CONSTANT)
37+ Q_PROPERTY(bool hasTestability READ hasTestability CONSTANT)
38+ Q_PROPERTY(bool hasFrameless READ hasFrameless CONSTANT)
39+ Q_PROPERTY(bool hasFullscreen READ hasFullscreen CONSTANT)
40+#ifdef UNITY8_ENABLE_TOUCH_EMULATION
41+ Q_PROPERTY(bool hasMouseToTouch READ hasMouseToTouch CONSTANT)
42+#endif
43+
31public:
44public:
32− ApplicationArguments(QObject *parent = nullptr);
45+ ApplicationArguments(QCoreApplication *app);
33
46
34− void setDeviceName(const QString &deviceName) {
47+ void setDeviceName(const QString &deviceName);
35− if (deviceName != m_deviceName) {
36− m_deviceName = deviceName;
37− Q_EMIT deviceNameChanged(m_deviceName);
38− }
39− }
40− QString deviceName() const { return m_deviceName; }
41
48
42− void setMode(const QString &mode) { m_mode = mode; }
49+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
43− QString mode() const { return m_mode; }
44
50
45Q_SIGNALS:
51Q_SIGNALS:
46 void deviceNameChanged(const QString&);
52 void deviceNameChanged(const QString&);
47−
48−private:
49− QString m_deviceName;
50− QString m_mode;
51};
53};
52
54
53#endif // APPLICATION_ARGUMENTS_H
55#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt+12−4
@@ -9,6 +9,7 @@ include_directories(
9 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
9 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10 ${GSETTINGS_QT_INCLUDE_DIRS}
10 ${GSETTINGS_QT_INCLUDE_DIRS}
11 ${CONNECTIVITY_INCLUDE_DIRS}
11 ${CONNECTIVITY_INCLUDE_DIRS}
12+ ${QTMIRSERVER_INCLUDE_DIRS}
12)
13)
13
14
14include_directories(
15include_directories(
@@ -29,12 +30,12 @@ set(SOURCE_FILES
29 ApplicationArguments.cpp
30 ApplicationArguments.cpp
30 main.cpp
31 main.cpp
31 CachingNetworkManagerFactory.cpp
32 CachingNetworkManagerFactory.cpp
32− SecondaryWindow.cpp
33+ DisplayConfigurationStorage.cpp
33− ShellApplication.cpp
34+ UnityApplication.cpp
34− ShellView.cpp
35 UnityCommandLineParser.cpp
35 UnityCommandLineParser.cpp
36 UnixSignalHandler.cpp
36 UnixSignalHandler.cpp
37 DebuggingController.cpp
37 DebuggingController.cpp
38+ WindowManagementPolicy.cpp
38 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
39 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
39)
40)
40
41
@@ -49,7 +50,14 @@ qt5_use_modules(${SHELL_APP} DBus Gui Qml Quick Test)
49if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
50if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
50 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
51 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
51endif()
52endif()
52−target_link_libraries(${SHELL_APP} ${ANDROID_PROPERTIES_LDFLAGS} ${GSETTINGS_QT_LDFLAGS} UbuntuGestures connectivity-qt1 unity8-private)
53+target_link_libraries(${SHELL_APP}
54+ ${ANDROID_PROPERTIES_LDFLAGS}
55+ ${GSETTINGS_QT_LDFLAGS}
56+ ${QTMIRSERVER_LDFLAGS}
57+ UbuntuGestures
58+ connectivity-qt1
59+ unity8-private
60+)
53
61
54if (ENABLE_TOUCH_EMULATION)
62if (ENABLE_TOUCH_EMULATION)
55 target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
63 target_link_libraries(${SHELL_APP} ${MOUSETOUCHADAPTOR_LIBS_LDFLAGS})
src/DisplayConfigurationStorage.cppnew file+90
@@ -0,0 +1,90 @@
1+#include "DisplayConfigurationStorage.h"
2+
3+#include <QFile>
4+#include <QStandardPaths>
5+#include <QJsonObject>
6+#include <QJsonDocument>
7+
8+namespace {
9+
10+inline QString stringFromEdid(const miral::Edid& edid)
11+{
12+ QString str;
13+ str += QString::fromStdString(edid.vendor);
14+ str += QString("%1%2").arg(edid.product_code).arg(edid.serial_number);
15+
16+ for (int i = 0; i < 4; i++) {
17+ str += QString::fromStdString(edid.descriptors[i].string_value());
18+ }
19+ return str;
20+}
21+
22+}
23+
24+DisplayConfigurationStorage::DisplayConfigurationStorage()
25+{
26+}
27+
28+void DisplayConfigurationStorage::save(const miral::DisplayId &displayId, const miral::DisplayConfigurationOptions &options)
29+{
30+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
31+ QFile f(dbPath + stringFromEdid(displayId.edid) + ".edid");
32+
33+ QJsonObject json;
34+ if (options.used.is_set()) json.insert("used", options.used.value());
35+ if (options.clone_output_index.is_set()) json.insert("clone_output_index", static_cast<int>(options.clone_output_index.value()));
36+ if (options.mode.is_set()) {
37+ auto const& mode = options.mode.value();
38+
39+ QString sz(QString("%1x%2").arg(mode.size.width.as_int()).arg(mode.size.height.as_int()));
40+ QJsonObject jsonMode({
41+ {"size", sz},
42+ {"refresh_rate", mode.refresh_rate }
43+ });
44+ json.insert("mode", jsonMode);
45+ }
46+ if (options.orientation.is_set()) json.insert("orientation", static_cast<int>(options.orientation.value()));
47+ if (options.form_factor.is_set()) json.insert("form_factor", static_cast<int>(options.form_factor.value()));
48+ if (options.scale.is_set()) json.insert("scale", options.scale.value());
49+
50+ if (f.open(QIODevice::WriteOnly)) {
51+ QJsonDocument saveDoc(json);
52+ f.write(saveDoc.toJson());
53+ }
54+}
55+
56+bool DisplayConfigurationStorage::load(const miral::DisplayId &displayId, miral::DisplayConfigurationOptions &options) const
57+{
58+ const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
59+ QFile f(dbPath + stringFromEdid(displayId.edid) + ".edid");
60+
61+ if (f.open(QIODevice::ReadOnly)) {
62+ QByteArray saveData = f.readAll();
63+ QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
64+
65+ QJsonObject json(loadDoc.object());
66+ if (json.contains("used")) options.used = json["used"].toBool();
67+ if (json.contains("clone_output_index")) options.clone_output_index = json["clone_output_index"].toInt();
68+ if (json.contains("mode")) {
69+ QJsonObject jsonMode = json["mode"].toObject();
70+
71+ if (jsonMode.contains("size") && jsonMode.contains("refresh_rate")) {
72+ QString sz(jsonMode["size"].toString());
73+ QStringList geo = sz.split("x", QString::SkipEmptyParts);
74+ if (geo.count() == 2) {
75+ miral::DisplayConfigurationOptions::DisplayMode mode;
76+ mode.size = mir::geometry::Size(geo[0].toInt(), geo[1].toInt());
77+ mode.refresh_rate = jsonMode["refresh_rate"].toDouble();
78+ options.mode = mode;
79+ }
80+ }
81+ }
82+ if (json.contains("orientation")) options.orientation = static_cast<MirOrientation>(json["orientation"].toInt());
83+ if (json.contains("form_factor")) options.form_factor = static_cast<MirFormFactor>(json["form_factor"].toInt());
84+ if (json.contains("scale")) options.scale = json["form_factor"].toDouble();
85+
86+ return true;
87+ }
88+
89+ return false;
90+}
src/DisplayConfigurationStorage.hnew file+15
@@ -0,0 +1,15 @@
1+#ifndef UNITY_DISPLAYCONFIGURATIONSTORAGE_H
2+#define UNITY_DISPLAYCONFIGURATIONSTORAGE_H
3+
4+#include <qtmir/miral/display_configuration_storage.h>
5+
6+class DisplayConfigurationStorage : public miral::DisplayConfigurationStorage
7+{
8+public:
9+ DisplayConfigurationStorage();
10+
11+ void save(const miral::DisplayId& displayId, const miral::DisplayConfigurationOptions& options) override;
12+ bool load(const miral::DisplayId& displayId, miral::DisplayConfigurationOptions& options) const override;
13+};
14+
15+#endif // UNITY_DISPLAYCONFIGURATIONSTORAGE_H
src/SecondaryWindow.cppdeleted−40
@@ -1,40 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "SecondaryWindow.h"
18−
19−// local
20−#include <paths.h>
21−
22−#include <QQmlContext>
23−
24−SecondaryWindow::SecondaryWindow(QQmlEngine *engine)
25− : QQuickView(engine, nullptr)
26−{
27− QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
28− bool ok;
29− int pxpgu = pxpguEnv.toInt(&ok);
30− if (!ok) {
31− pxpgu = 8;
32− }
33− engine->rootContext()->setContextProperty(QStringLiteral("internalGu"), pxpgu);
34− setResizeMode(QQuickView::SizeRootObjectToView);
35− setColor("black");
36− setTitle(QStringLiteral("Unity8 Shell - Secondary Screen"));
37−
38− QUrl source(::qmlDirectory() + "/DisabledScreenNotice.qml");
39− setSource(source);
40−}
src/ShellApplication.cppdeleted−221
@@ -1,221 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "ShellApplication.h"
18−
19−// Qt
20−#include <QLibrary>
21−#include <QProcess>
22−#include <QScreen>
23−
24−#include <QGSettings>
25−
26−#include <libintl.h>
27−
28−// libandroid-properties
29−#include <hybris/properties/properties.h>
30−
31−// local
32−#include <paths.h>
33−#include "CachingNetworkManagerFactory.h"
34−#include "UnityCommandLineParser.h"
35−#include "DebuggingController.h"
36−
37−ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
38− : QGuiApplication(argc, argv)
39−{
40− setApplicationName(QStringLiteral("unity8"));
41− setOrganizationName(QStringLiteral("Canonical"));
42−
43− connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
44−
45− setupQmlEngine(isMirServer);
46−
47− UnityCommandLineParser parser(*this);
48−
49− if (!parser.deviceName().isEmpty()) {
50− m_deviceName = parser.deviceName();
51− } else {
52− char buffer[200];
53− property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
54− m_deviceName = QString(buffer);
55− }
56− m_qmlArgs.setDeviceName(m_deviceName);
57−
58− m_qmlArgs.setMode(parser.mode());
59−
60− // The testability driver is only loaded by QApplication but not by QGuiApplication.
61− // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
62− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
63− QLibrary testLib(QStringLiteral("qttestability"));
64− if (testLib.load()) {
65− typedef void (*TasInitialize)(void);
66− TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
67− if (initFunction) {
68− initFunction();
69− } else {
70− qCritical("Library qttestability resolve failed!");
71− }
72− } else {
73− qCritical("Library qttestability load failed!");
74− }
75− }
76−
77− bindtextdomain("unity8", translationDirectory().toUtf8().data());
78− textdomain("unity8");
79−
80− QScopedPointer<QGSettings> gSettings(new QGSettings("com.canonical.Unity8"));
81− gSettings->reset(QStringLiteral("alwaysShowOsk"));
82−
83− m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
84−
85− if (parser.windowGeometry().isValid()) {
86− m_shellView->setWidth(parser.windowGeometry().width());
87− m_shellView->setHeight(parser.windowGeometry().height());
88− }
89−
90− if (parser.hasFrameless()) {
91− m_shellView->setFlags(Qt::FramelessWindowHint);
92− }
93−
94−
95− #ifdef UNITY8_ENABLE_TOUCH_EMULATION
96− // You will need this if you want to interact with touch-only components using a mouse
97− // Needed only when manually testing on a desktop.
98− if (parser.hasMouseToTouch()) {
99− m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
100− }
101− #endif
102−
103− new DebuggingController(this);
104−
105− // Some hard-coded policy for now.
106− // NB: We don't support more than two screens at the moment
107− //
108− // TODO: Support an arbitrary number of screens and different policies
109− // (eg cloned desktop, several desktops, etc)
110− if (isMirServer && screens().count() == 2) {
111− m_shellView->setScreen(screens().at(1));
112− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
113−
114− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
115− m_secondaryWindow->setScreen(screens().at(0));
116− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
117− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
118− m_secondaryWindow->setVisible(true);
119− }
120−
121− if (parser.mode().compare("greeter") == 0) {
122− QSize primaryScreenSize = this->primaryScreen()->size();
123− m_shellView->setHeight(primaryScreenSize.height());
124− m_shellView->setWidth(primaryScreenSize.width());
125− m_shellView->show();
126− m_shellView->requestActivate();
127− if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
128− qDebug() << "Unable to send unity8-greeter-started event to Upstart";
129− }
130− } else if (isMirServer || parser.hasFullscreen()) {
131− m_shellView->showFullScreen();
132− } else {
133− m_shellView->show();
134− }
135−}
136−
137−ShellApplication::~ShellApplication()
138−{
139− destroyResources();
140−}
141−
142−void ShellApplication::destroyResources()
143−{
144− // Deletion order is important. Don't use QScopedPointers and the like
145− // Otherwise the process will hang on shutdown (bug somewhere I guess).
146− delete m_shellView;
147− m_shellView = nullptr;
148−
149− delete m_secondaryWindow;
150− m_secondaryWindow = nullptr;
151−
152− #ifdef UNITY8_ENABLE_TOUCH_EMULATION
153− delete m_mouseTouchAdaptor;
154− m_mouseTouchAdaptor = nullptr;
155− #endif
156−
157− delete m_qmlEngine;
158− m_qmlEngine = nullptr;
159−}
160−
161−void ShellApplication::setupQmlEngine(bool isMirServer)
162−{
163− m_qmlEngine = new QQmlEngine(this);
164−
165− m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
166−
167− prependImportPaths(m_qmlEngine, ::overrideImportPaths());
168− if (!isMirServer) {
169− prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
170− }
171− appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
172−
173− m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
174−
175− QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
176−}
177−
178−void ShellApplication::onScreenAdded(QScreen * /*screen*/)
179−{
180− // TODO: Support an arbitrary number of screens and different policies
181− // (eg cloned desktop, several desktops, etc)
182− if (screens().count() == 2) {
183− m_shellView->setScreen(screens().at(1));
184− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
185− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
186− // its backing QPlatformWindow recreated). So lets refocus it.
187− m_shellView->requestActivate();
188− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
189− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
190− m_shellView->setVisible(true);
191−
192− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
193− m_secondaryWindow->setScreen(screens().at(0));
194−
195− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
196− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
197− m_secondaryWindow->setVisible(true);
198− }
199−}
200−
201−void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
202−{
203− // TODO: Support an arbitrary number of screens and different policies
204− // (eg cloned desktop, several desktops, etc)
205− if (screen == m_shellView->screen()) {
206− const QList<QScreen *> allScreens = screens();
207− Q_ASSERT(allScreens.count() > 1);
208− Q_ASSERT(allScreens.at(0) != screen);
209− Q_ASSERT(m_secondaryWindow);
210− delete m_secondaryWindow;
211− m_secondaryWindow = nullptr;
212− m_shellView->setScreen(allScreens.first());
213− m_qmlArgs.setDeviceName(m_deviceName);
214− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
215− // its backing QPlatformWindow recreated). So lets refocus it.
216− m_shellView->requestActivate();
217− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
218− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
219− m_shellView->setVisible(true);
220− }
221−}
src/ShellView.cppdeleted−85
@@ -1,85 +0,0 @@
1−/*
2− * Copyright (C) 2015 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 "ShellView.h"
18−
19−// Qt
20−#include <QQmlContext>
21−#include <QQuickItem>
22−#include <QtQuick/private/qquickitem_p.h>
23−#include <QtQuick/private/qquickrectangle_p.h>
24−#include <QtQuick/private/qquicktext_p.h>
25−
26−// local
27−#include <paths.h>
28−
29−ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
30− : QQuickView(engine, nullptr)
31−{
32− setResizeMode(QQuickView::SizeRootObjectToView);
33− setColor("black");
34− setTitle(QStringLiteral("Unity8"));
35−
36− rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
37−
38− connect(this, &QQuickView::statusChanged, this, [this] {
39− if (status() == QQuickView::Error) {
40− QQuickRectangle *rect = new QQuickRectangle(contentItem());
41− rect->setColor(Qt::white);
42− QQuickItemPrivate::get(rect)->anchors()->setFill(contentItem());
43−
44− QString errorsString;
45− for(const QQmlError &e: errors()) {
46− errorsString += e.toString() + "\n";
47− }
48− QQuickText *text = new QQuickText(rect);
49− text->setColor(Qt::black);
50− text->setWrapMode(QQuickText::Wrap);
51− text->setText(QString("There was an error loading Unity8:\n%1").arg(errorsString));
52− QQuickItemPrivate::get(text)->anchors()->setFill(rect);
53− }
54− }
55− );
56−
57− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
58− setSource(source);
59−
60− connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
61− connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
62−}
63−
64−void ShellView::onWidthChanged(int w)
65−{
66− // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
67− //
68− // There's at least one situation that's know to leave the root object with an outdated size.
69− // (really looks like Qt bug)
70− // Happens when starting unity8 with an external monitor already connected.
71− // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
72− // from the resize event handler, the root item doesn't get resized.
73− // TODO: Confirm the Qt bug and submit a patch upstream
74− if (rootObject()) {
75− rootObject()->setWidth(w);
76− }
77−}
78−
79−void ShellView::onHeightChanged(int h)
80−{
81− // See comment in ShellView::onWidthChanged()
82− if (rootObject()) {
83− rootObject()->setHeight(h);
84− }
85−}
src/UnityApplication.cppnew file+152
@@ -0,0 +1,152 @@
1+/*
2+ * Copyright (C) 2015 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 "UnityApplication.h"
18+
19+// Qt
20+#include <QLibrary>
21+#include <QProcess>
22+#include <QScreen>
23+#include <QQmlContext>
24+#include <QQmlComponent>
25+
26+#include <QGSettings>
27+
28+#include <libintl.h>
29+
30+// libandroid-properties
31+#include <hybris/properties/properties.h>
32+
33+// qtmir
34+#include <qtmir/displayconfigurationstorage.h>
35+
36+// local
37+#include <paths.h>
38+#include "CachingNetworkManagerFactory.h"
39+#include "UnityCommandLineParser.h"
40+#include "DebuggingController.h"
41+#include "WindowManagementPolicy.h"
42+#include "DisplayConfigurationStorage.h"
43+
44+#include <QDebug>
45+
46+
47+
48+UnityApplication::UnityApplication(int & argc, char ** argv)
49+ : qtmir::MirServerApplication(argc, argv, { qtmir::SetWindowManagementPolicy<WindowManagementPolicy>(),
50+ qtmir::SetDisplayConfigurationStorage<DisplayConfigurationStorage>() })
51+ , m_qmlArgs(this)
52+{
53+ setApplicationName(QStringLiteral("unity8"));
54+ setOrganizationName(QStringLiteral("Canonical"));
55+
56+ setupQmlEngine();
57+
58+ if (m_qmlArgs.deviceName().isEmpty()) {
59+ char buffer[200];
60+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
61+ m_qmlArgs.setDeviceName(QString(buffer));
62+ }
63+
64+ // The testability driver is only loaded by QApplication but not by QGuiApplication.
65+ // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
66+ if (m_qmlArgs.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
67+ QLibrary testLib(QStringLiteral("qttestability"));
68+ if (testLib.load()) {
69+ typedef void (*TasInitialize)(void);
70+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
71+ if (initFunction) {
72+ initFunction();
73+ } else {
74+ qCritical("Library qttestability resolve failed!");
75+ }
76+ } else {
77+ qCritical("Library qttestability load failed!");
78+ }
79+ }
80+
81+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
82+ textdomain("unity8");
83+
84+ QScopedPointer<QGSettings> gSettings(new QGSettings("com.canonical.Unity8"));
85+ gSettings->reset(QStringLiteral("alwaysShowOsk"));
86+
87+
88+ QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
89+ bool ok;
90+ int pxpgu = pxpguEnv.toInt(&ok);
91+ if (!ok) {
92+ pxpgu = 8;
93+ }
94+ m_qmlEngine->rootContext()->setContextProperty("internalGu", pxpgu);
95+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &m_qmlArgs);
96+ m_qmlEngine->rootContext()->setContextProperty("DebuggingController", new DebuggingController(this));
97+
98+ auto component(new QQmlComponent(m_qmlEngine, m_qmlArgs.qmlfie()));
99+ component->create();
100+ if (component->status() == QQmlComponent::Error) {
101+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("errorString"), component->errorString());
102+ auto errorComponent(new QQmlComponent(m_qmlEngine,
103+ QUrl::fromLocalFile(::qmlDirectory() + "/ErrorApplication.qml")));
104+ errorComponent->create();
105+ qDebug() << errorComponent->errorString();
106+ return;
107+ }
108+
109+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
110+ // You will need this if you want to interact with touch-only components using a mouse
111+ // Needed only when manually testing on a desktop.
112+ if (m_qmlArgs.hasMouseToTouch()) {
113+ m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
114+ }
115+ #endif
116+
117+ if (m_qmlArgs.mode().compare("greeter") == 0) {
118+ if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
119+ qDebug() << "Unable to send unity8-greeter-started event to Upstart";
120+ }
121+ }
122+}
123+
124+UnityApplication::~UnityApplication()
125+{
126+ destroyResources();
127+}
128+
129+void UnityApplication::destroyResources()
130+{
131+ #ifdef UNITY8_ENABLE_TOUCH_EMULATION
132+ delete m_mouseTouchAdaptor;
133+ m_mouseTouchAdaptor = nullptr;
134+ #endif
135+
136+ delete m_qmlEngine;
137+ m_qmlEngine = nullptr;
138+}
139+
140+void UnityApplication::setupQmlEngine()
141+{
142+ m_qmlEngine = new QQmlEngine(this);
143+
144+ m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
145+
146+ prependImportPaths(m_qmlEngine, ::overrideImportPaths());
147+ appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
148+
149+ m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
150+
151+ QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
152+}
src/ShellApplication.h → src/UnityApplication.hrenamed+9−19
@@ -14,11 +14,11 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef SHELLAPPLICATION_H
17+#ifndef UNITYAPPLICATION_H
18−#define SHELLAPPLICATION_H
18+#define UNITYAPPLICATION_H
19
19
20#include <QGuiApplication>
20#include <QGuiApplication>
21−#include <QQmlEngine>
21+#include <QQmlApplicationEngine>
22#include <QQuickView>
22#include <QQuickView>
23#include <QScopedPointer>
23#include <QScopedPointer>
24
24
@@ -28,30 +28,20 @@
28#include "MouseTouchAdaptor.h"
28#include "MouseTouchAdaptor.h"
29#endif
29#endif
30
30
31−#include "SecondaryWindow.h"
31+#include <qtmir/mirserverapplication.h>
32−#include "ShellView.h"
33
32
34−class ShellApplication : public QGuiApplication
33+class UnityApplication : public qtmir::MirServerApplication
35{
34{
36 Q_OBJECT
35 Q_OBJECT
37public:
36public:
38− ShellApplication(int & argc, char ** argv, bool isMirServer);
37+ UnityApplication(int & argc, char ** argv);
39− virtual ~ShellApplication();
38+ virtual ~UnityApplication();
40
39
41 void destroyResources();
40 void destroyResources();
42−public Q_SLOTS:
43− // called by qtmir
44− void onScreenAboutToBeRemoved(QScreen *screen);
45−
46−private Q_SLOTS:
47− void onScreenAdded(QScreen*);
48
41
49private:
42private:
50− void setupQmlEngine(bool isMirServer);
43+ void setupQmlEngine();
51− QString m_deviceName;
52 ApplicationArguments m_qmlArgs;
44 ApplicationArguments m_qmlArgs;
53− ShellView *m_shellView{nullptr};
54− SecondaryWindow *m_secondaryWindow{nullptr};
55
45
56 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
46 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
57 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
47 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
@@ -60,4 +50,4 @@ private:
60 QQmlEngine *m_qmlEngine{nullptr};
50 QQmlEngine *m_qmlEngine{nullptr};
61};
51};
62
52
63−#endif // SHELLAPPLICATION_H
53+#endif // UNITYAPPLICATION_H
src/UnityCommandLineParser.cpp+8
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include "UnityCommandLineParser.h"
17#include "UnityCommandLineParser.h"
18+#include <paths.h>
18
19
19#include <QDebug>
20#include <QDebug>
20
21
@@ -60,6 +61,11 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
60 QStringLiteral("mode"), QStringLiteral("full-greeter"));
61 QStringLiteral("mode"), QStringLiteral("full-greeter"));
61 parser.addOption(modeOption);
62 parser.addOption(modeOption);
62
63
64+ QCommandLineOption qmlfileOption(QStringLiteral("qmlfile"),
65+ QStringLiteral("The base qml file to load"),
66+ QStringLiteral("qmlfile"), ::qmlDirectory() + "/ShellApplication.qml");
67+ parser.addOption(qmlfileOption);
68+
63 // Treat args with single dashes the same as arguments with two dashes
69 // Treat args with single dashes the same as arguments with two dashes
64 // Ex: -fullscreen == --fullscreen
70 // Ex: -fullscreen == --fullscreen
65 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
71 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
@@ -85,6 +91,8 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
85 m_hasFullscreen = parser.isSet(fullscreenOption);
91 m_hasFullscreen = parser.isSet(fullscreenOption);
86 m_deviceName = parser.value(devicenameOption);
92 m_deviceName = parser.value(devicenameOption);
87 resolveMode(parser, modeOption);
93 resolveMode(parser, modeOption);
94+
95+ m_qmlfile = parser.value(qmlfileOption);
88}
96}
89
97
90int UnityCommandLineParser::parsePixelsValue(const QString &str)
98int UnityCommandLineParser::parsePixelsValue(const QString &str)
src/UnityCommandLineParser.h+4−1
@@ -36,8 +36,10 @@ public:
36 bool hasFullscreen() const { return m_hasFullscreen; }
36 bool hasFullscreen() const { return m_hasFullscreen; }
37 QString deviceName() const { return m_deviceName; }
37 QString deviceName() const { return m_deviceName; }
38 QString mode() const { return m_mode; }
38 QString mode() const { return m_mode; }
39−private:
40
39
40+ QString qmlfie() const { return m_qmlfile; }
41+
42+protected:
41 int parsePixelsValue(const QString &str);
43 int parsePixelsValue(const QString &str);
42 static float getenvFloat(const char* name, float defaultValue);
44 static float getenvFloat(const char* name, float defaultValue);
43 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
45 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
@@ -55,6 +57,7 @@ private:
55 bool m_hasFullscreen;
57 bool m_hasFullscreen;
56 QString m_deviceName;
58 QString m_deviceName;
57 QString m_mode;
59 QString m_mode;
60+ QString m_qmlfile;
58};
61};
59
62
60#endif // UNITY_COMMAND_LINE_PARSER_H
63#endif // UNITY_COMMAND_LINE_PARSER_H
src/WindowManagementPolicy.cppnew file+69
@@ -0,0 +1,69 @@
1+/*
2+ * Copyright (C) 2017 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 "WindowManagementPolicy.h"
18+
19+WindowManagementPolicy::WindowManagementPolicy(const miral::WindowManagerTools &tools, std::shared_ptr<qtmir::WindowManagementPolicyPrivate> dd)
20+ : qtmir::WindowManagementPolicy(tools, dd)
21+ , m_dummyWorkspace(this->tools.create_workspace())
22+{
23+ wmPolicyInterface = this;
24+
25+ // we must always have a active workspace.
26+ m_activeWorkspace = m_dummyWorkspace;
27+}
28+
29+void WindowManagementPolicy::advise_new_window(miral::WindowInfo const& window_info)
30+{
31+ qtmir::WindowManagementPolicy::advise_new_window(window_info);
32+
33+ auto const parent = window_info.parent();
34+
35+ auto activeWorkspace = m_activeWorkspace.lock();
36+ if (!parent && activeWorkspace) {
37+ tools.add_tree_to_workspace(window_info.window(), activeWorkspace);
38+ }
39+}
40+
41+std::shared_ptr<miral::Workspace> WindowManagementPolicy::createWorkspace()
42+{
43+ auto workspace = tools.create_workspace();
44+ m_workspaces.insert(workspace);
45+
46+ if (m_activeWorkspace.lock() == m_dummyWorkspace) {
47+ tools.move_workspace_content_to_workspace(workspace, m_dummyWorkspace);
48+ m_activeWorkspace = workspace;
49+ }
50+ return workspace;
51+}
52+
53+void WindowManagementPolicy::releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
54+{
55+ auto iter = m_workspaces.find(workspace);
56+ if (iter != m_workspaces.end()) m_workspaces.erase(iter);
57+
58+ if (m_workspaces.size() == 0) {
59+ m_activeWorkspace = m_dummyWorkspace;
60+ tools.move_workspace_content_to_workspace(m_dummyWorkspace, workspace);
61+ }
62+}
63+
64+void WindowManagementPolicy::setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
65+{
66+ if (m_activeWorkspace.lock() == workspace)
67+ return;
68+ m_activeWorkspace = workspace ? workspace : m_dummyWorkspace;
69+}
src/WindowManagementPolicy.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_WINDOWMANAGEMENTPOLICY_H
18+#define UNITY_WINDOWMANAGEMENTPOLICY_H
19+
20+#include <qtmir/windowmanagementpolicy.h>
21+#include "wmpolicyinterface.h"
22+
23+#include <unordered_set>
24+
25+class Q_DECL_EXPORT WindowManagementPolicy : public qtmir::WindowManagementPolicy,
26+ public WMPolicyInterface
27+{
28+public:
29+ WindowManagementPolicy(const miral::WindowManagerTools &tools, std::shared_ptr<qtmir::WindowManagementPolicyPrivate> dd);
30+
31+ void advise_new_window(miral::WindowInfo const& window_info) override;
32+
33+ // From WMPolicyInterface
34+ std::shared_ptr<miral::Workspace> createWorkspace() override;
35+
36+ void releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace) override;
37+
38+ void setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace) override;
39+
40+private:
41+ std::weak_ptr<miral::Workspace> m_activeWorkspace;
42+
43+ std::unordered_set<std::shared_ptr<miral::Workspace>> m_workspaces;
44+ const std::shared_ptr<miral::Workspace> m_dummyWorkspace;
45+};
46+
47+#endif // UNITY_WINDOWMANAGEMENTPOLICY_H
src/libunity8-private/CMakeLists.txt+1
@@ -8,6 +8,7 @@ set(lib${LIB_NAME}_SRCS
8 abstractdbusservicemonitor.cpp
8 abstractdbusservicemonitor.cpp
9 unitydbusobject.cpp
9 unitydbusobject.cpp
10 unitydbusvirtualobject.cpp
10 unitydbusvirtualobject.cpp
11+ wmpolicyinterface.cpp
11 )
12 )
12
13
13add_library(${LIB_NAME} SHARED
14add_library(${LIB_NAME} SHARED
src/SecondaryWindow.h → src/libunity8-private/wmpolicyinterface.cpprenamed+6−12
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,11 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_SECONDARY_WINDOW_H
17+#include "wmpolicyinterface.h"
18−#define UNITY_SECONDARY_WINDOW_H
19
18
20−#include <QQuickView>
19+WMPolicyInterface* wmPolicyInterface = nullptr;
21
20
22−class SecondaryWindow : public QQuickView
21+WMPolicyInterface *WMPolicyInterface::instance()
23{
22{
24− Q_OBJECT
23+ return wmPolicyInterface;
25−
26−public:
27− SecondaryWindow(QQmlEngine *engine);
28−};
24+}
29−
30−#endif // UNITY_SECONDARY_WINDOW_H
src/libunity8-private/wmpolicyinterface.hnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WMPOLICYINTERFACE_H
18+#define WMPOLICYINTERFACE_H
19+
20+#include <memory>
21+#include <functional>
22+
23+#include <qglobal.h>
24+
25+namespace miral {
26+class Workspace;
27+class Window;
28+}
29+
30+class Q_DECL_EXPORT WMPolicyInterface
31+{
32+public:
33+ virtual ~WMPolicyInterface() {}
34+
35+ static WMPolicyInterface *instance();
36+
37+ virtual std::shared_ptr<miral::Workspace> createWorkspace() = 0;
38+
39+ virtual void releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace) = 0;
40+
41+ virtual void setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace) = 0;
42+};
43+
44+extern Q_DECL_EXPORT WMPolicyInterface* wmPolicyInterface;
45+
46+#endif // WMPOLICYINTERFACE_H
src/main.cpp+4−8
@@ -15,9 +15,10 @@
15 */
15 */
16
16
17// local
17// local
18−#include "ShellApplication.h"
18+#include "UnityApplication.h"
19#include "qmldebuggerutils.h"
19#include "qmldebuggerutils.h"
20#include "UnixSignalHandler.h"
20#include "UnixSignalHandler.h"
21+#include <paths.h>
21
22
22#include <QTranslator>
23#include <QTranslator>
23#include <QLibraryInfo>
24#include <QLibraryInfo>
@@ -27,17 +28,12 @@ int main(int argc, const char *argv[])
27{
28{
28 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
29 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
29
30
30− bool isMirServer = qgetenv("QT_QPA_PLATFORM") == "mirserver";
31− if (!isMirServer && qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
32− setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
33− isMirServer = true;
34− }
35−
36 if (enableQmlDebugger(argc, argv)) {
31 if (enableQmlDebugger(argc, argv)) {
37 QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
32 QQmlDebuggingEnabler qQmlEnableDebuggingHelper(true);
38 }
33 }
39
34
40− ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
35+ auto *application = new UnityApplication(argc,
36+ (char**)argv);
41
37
42 UnixSignalHandler signalHandler([]{
38 UnixSignalHandler signalHandler([]{
43 QGuiApplication::exit(0);
39 QGuiApplication::exit(0);
tests/CMakeLists.txt+1
@@ -47,6 +47,7 @@ macro(unity8_parse_arguments)
47 list(APPEND ld_paths
47 list(APPEND ld_paths
48 ${UNITY_MOCKPATH}/liblightdm
48 ${UNITY_MOCKPATH}/liblightdm
49 ${UNITY_MOCKPATH}/libusermetrics
49 ${UNITY_MOCKPATH}/libusermetrics
50+ ${UNITY_MOCKPATH}/WindowManager
50 )
51 )
51
52
52 string(REPLACE ";" ":" ld_library_path "${ld_paths}")
53 string(REPLACE ";" ":" ld_library_path "${ld_paths}")
tests/mocks/CMakeLists.txt+1
@@ -43,6 +43,7 @@ add_subdirectory(SessionBroadcast)
43add_subdirectory(Ubuntu)
43add_subdirectory(Ubuntu)
44add_subdirectory(Unity)
44add_subdirectory(Unity)
45add_subdirectory(QtMultimedia)
45add_subdirectory(QtMultimedia)
46+add_subdirectory(WindowManager)
46add_subdirectory(Wizard)
47add_subdirectory(Wizard)
47add_subdirectory(Utils)
48add_subdirectory(Utils)
48add_subdirectory(UInput)
49add_subdirectory(UInput)
tests/mocks/Cursor/Cursor.qml+49−2
@@ -15,8 +15,11 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
18
22
19−Item {
20 property int topBoundaryOffset // effectively panel height
23 property int topBoundaryOffset // effectively panel height
21 property Item confiningItem
24 property Item confiningItem
22
25
@@ -30,5 +33,49 @@ Item {
30 signal pushStopped()
33 signal pushStopped()
31 signal mouseMoved()
34 signal mouseMoved()
32
35
36+ width: units.gu(2)
37+ height: units.gu(2)
38+ antialiasing: true
39+
40+ onPaint: {
41+ var ctx = getContext("2d");
42+ ctx.save();
43+ ctx.clearRect(0,0,width, height);
44+ ctx.strokeStyle = "#000000";
45+ ctx.lineWidth = 1
46+ ctx.fillStyle = "#ffffff";
47+ ctx.globalAlpha = 1.0;
48+ ctx.lineJoin = "round";
49+ ctx.beginPath();
50+
51+ // put rectangle in the middle
52+ // draw the rectangle
53+ ctx.moveTo(width/2,height);
54+ ctx.lineTo(width, height/2);
55+ ctx.lineTo(0,0);
56+
57+ ctx.closePath();
58+ ctx.fill();
59+ ctx.stroke();
60+ ctx.restore();
61+ }
62+
63+ Connections {
64+ target: UInput
33− onMouseMoved: opacity = 1;
65+ onMouseMoved: {
66+ var newX = root.x;
67+ newX += dx;
68+ if (newX < 0) newX = 0;
69+ else if (newX >= parent.width) newX = parent.width-1;
70+
71+ var newY = root.y;
72+ newY += dy;
73+ if (newY < 0) newY = 0;
74+ else if (newY >= parent.height) newY = parent.height-1;
75+
76+ root.x = newX;
77+ root.y = newY;
78+ root.mouseMoved();
79+ }
80+ }
34}
81}
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
18#include "fake_gsettings.h"
18#include "fake_gsettings.h"
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
21
23
22static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
24static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23{
25{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
31 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
33 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
32 "GSettingsSchema can only be used inside of a GSettings component");
34 "GSettingsSchema can only be used inside of a GSettings component");
33}
35}
36+
37+void FakeGSettingsQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
38+{
39+ QQmlExtensionPlugin::initializeEngine(engine, uri);
40+
41+ QString usageType = qgetenv("UNITY_MOCK_DESKTOP");
42+ if (!usageType.isEmpty()) {
43+ GSettingsControllerQml::instance()->setUsageMode("Windowed");
44+ }
45+}
tests/mocks/GSettings.1.0/plugin.h+1
@@ -26,6 +26,7 @@ class FakeGSettingsQmlPlugin : public QQmlExtensionPlugin
26
26
27public:
27public:
28 void registerTypes(const char *uri) override;
28 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
29};
30};
30
31
31#endif // PLUGIN_H
32#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
22void MockUInputPlugin::registerTypes(const char *uri)
27void MockUInputPlugin::registerTypes(const char *uri)
23{
28{
24 Q_ASSERT(uri == QLatin1String("UInput"));
29 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
26}
31}
tests/mocks/Unity/Application/CMakeLists.txt+26−1
@@ -1,3 +1,12 @@
1+pkg_check_modules(MIRTEST mirtest>=0.26 REQUIRED)
2+
3+include_directories(
4+ SYSTEM
5+ ${MIRTEST_INCLUDE_DIRS}
6+ ${MIRAL_INCLUDE_DIRS}
7+ ${libunity8-private_SOURCE_DIR}
8+)
9+
1set(FakeUnityApplicationQml_SOURCES
10set(FakeUnityApplicationQml_SOURCES
2 plugin.cpp
11 plugin.cpp
3 ApplicationInfo.cpp
12 ApplicationInfo.cpp
@@ -19,11 +28,27 @@ set(FakeUnityApplicationQml_SOURCES
19 resources/surfaces.qrc
28 resources/surfaces.qrc
20)
29)
21
30
22−add_library(FakeUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
31+add_library(FakeUnityApplicationQml MODULE
32+ ${FakeUnityApplicationQml_SOURCES}
33+)
34+
35+#add_dependencies(FakeUnityApplicationQml windowmanagementpolicy)
36+
37+target_link_libraries(FakeUnityApplicationQml
38+ ${MIRTEST_LDFLAGS}
39+ ${MIRAL_LDFLAGS}
40+ mockwindowmanagmentpolicy
41+)
23
42
24add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
43add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
25set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
44set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
26
45
46+target_link_libraries(NonMirUnityApplicationQml
47+ ${MIRTEST_LDFLAGS}
48+ ${MIRAL_LDFLAGS}
49+ mockwindowmanagmentpolicy
50+)
51+
27qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
52qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
28qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
53qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
29
54
tests/mocks/Unity/Application/MirSurfaceItem.cpp+26−7
@@ -189,11 +189,13 @@ void MirSurfaceItem::createQmlContentItem()
189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
190 m_qmlItem->setParentItem(this);
190 m_qmlItem->setParentItem(this);
191
191
192− m_qmlItem->setWidth(m_surfaceWidth);
192+ if (m_fillMode == FillMode::Stretch && width() != 0 && height() != 0) {
193− m_qmlItem->setHeight(m_surfaceHeight);
193+ m_qmlItem->setSize(QSize(this->width(), this->height()));
194−
194+ } else {
195+ m_qmlItem->setSize(m_qmlSurface->size());
196+ }
195− setImplicitWidth(m_qmlItem->implicitWidth());
197+ setImplicitWidth(m_qmlItem->width());
196− setImplicitHeight(m_qmlItem->implicitHeight());
198+ setImplicitHeight(m_qmlItem->height());
197
199
198 {
200 {
199 QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
201 QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
@@ -308,6 +310,16 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
308 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
310 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
309 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
311 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
310 connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
312 connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
313+ connect(m_qmlSurface, &MirSurface::sizeChanged, this, [this] () {
314+ setImplicitSize(m_qmlSurface->width(), m_qmlSurface->height());
315+ if (m_fillMode == FillMode::Stretch) {
316+ m_qmlItem->setSize(QSize(this->width(), this->height()));
317+ } else {
318+ m_qmlItem->setSize(m_qmlSurface->size());
319+ }
320+ });
321+ m_surfaceWidth = surface->size().width();
322+ m_surfaceHeight = surface->size().height();
311
323
312 QUrl qmlComponentFilePath;
324 QUrl qmlComponentFilePath;
313 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
325 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
@@ -321,6 +333,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
321 switch (m_qmlContentComponent->status()) {
333 switch (m_qmlContentComponent->status()) {
322 case QQmlComponent::Ready:
334 case QQmlComponent::Ready:
323 createQmlContentItem();
335 createQmlContentItem();
336+ qDebug() << "content created" << m_surfaceWidth << implicitWidth() << width();
324 break;
337 break;
325 case QQmlComponent::Loading:
338 case QQmlComponent::Loading:
326 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
339 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
@@ -399,9 +412,15 @@ void MirSurfaceItem::updateSurfaceSize()
399 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
412 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
400 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
413 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
401 if (m_qmlItem) {
414 if (m_qmlItem) {
415+ if (m_fillMode == FillMode::Stretch) {
416+ m_qmlItem->setWidth(width());
417+ m_qmlItem->setHeight(height());
418+ } else {
402− m_qmlItem->setWidth(m_surfaceWidth);
419+ m_qmlItem->setWidth(m_surfaceWidth);
403− m_qmlItem->setHeight(m_surfaceHeight);
420+ m_qmlItem->setHeight(m_surfaceHeight);
421+ }
404 }
422 }
423+ qDebug() << this << "setting implicitsize" << m_surfaceWidth << m_surfaceHeight;
405 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
424 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
406 }
425 }
407}
426}
tests/mocks/Unity/Application/SurfaceManager.cpp+118−11
@@ -18,10 +18,12 @@
18
18
19#include "ApplicationInfo.h"
19#include "ApplicationInfo.h"
20#include "VirtualKeyboard.h"
20#include "VirtualKeyboard.h"
21+#include "../../WindowManager/WindowManagementPolicy.h"
21
22
22#include <paths.h>
23#include <paths.h>
24+#include <mirtest/mir/test/doubles/stub_surface.h>
23
25
24−#define SURFACEMANAGER_DEBUG 0
26+#define SURFACEMANAGER_DEBUG 1
25
27
26#if SURFACEMANAGER_DEBUG
28#if SURFACEMANAGER_DEBUG
27#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
29#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
@@ -31,6 +33,12 @@
31
33
32namespace unityapi = unity::shell::application;
34namespace unityapi = unity::shell::application;
33
35
36+uint qHash(const WindowWrapper &key, uint)
37+{
38+ std::shared_ptr<mir::scene::Surface> surface = key.window;
39+ return (quintptr)surface.get();
40+}
41+
34SurfaceManager *SurfaceManager::m_instance = nullptr;
42SurfaceManager *SurfaceManager::m_instance = nullptr;
35
43
36SurfaceManager *SurfaceManager::instance()
44SurfaceManager *SurfaceManager::instance()
@@ -44,20 +52,63 @@ SurfaceManager::SurfaceManager(QObject *)
44
52
45 Q_ASSERT(m_instance == nullptr);
53 Q_ASSERT(m_instance == nullptr);
46 m_instance = this;
54 m_instance = this;
55+
56+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowAdded,
57+ this, [this](const miral::Window& window) {
58+ Q_EMIT surfaceCreated(surfaceFor(window));
59+ });
60+
61+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAddedToWorkspace,
62+ this, [this](const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows) {
63+ Q_EMIT surfacesAddedToWorkspace(workspace, surfacesFor(windows));
64+ });
65+
66+ connect(WindowManagementPolicy::instance(), &WindowManagementPolicy::windowsAboutToBeRemovedFromWorkspace,
67+ this, [this](const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows) {
68+ Q_EMIT surfacesAboutToBeRemovedFromWorkspace(workspace, surfacesFor(windows));
69+ });
47}
70}
48
71
49SurfaceManager::~SurfaceManager()
72SurfaceManager::~SurfaceManager()
50{
73{
51 DEBUG_MSG("");
74 DEBUG_MSG("");
52
75
53− if (m_virtualKeyboard) {
76+ releaseInputMethodSurface();
54− m_virtualKeyboard->setLive(false);
55− }
56
77
57 Q_ASSERT(m_instance == this);
78 Q_ASSERT(m_instance == this);
58 m_instance = nullptr;
79 m_instance = nullptr;
59}
80}
60
81
82+MirSurfaceInterface *SurfaceManager::surfaceFor(const miral::Window& window) const
83+{
84+ auto iter = m_windowToSurface.find({window});
85+ if (iter != m_windowToSurface.end()) {
86+ return *iter;
87+ }
88+ return nullptr;
89+}
90+
91+QVector<MirSurfaceInterface*> SurfaceManager::surfacesFor(const std::vector<miral::Window> &windows) const
92+{
93+ QVector<unityapi::MirSurfaceInterface*> surfaces;
94+ for (size_t i = 0; i < windows.size(); i++) {
95+ auto mirSurface = surfaceFor(windows[i]);
96+ if (mirSurface) {
97+ surfaces.push_back(mirSurface);
98+ }
99+ }
100+ return surfaces;
101+}
102+
103+miral::Window SurfaceManager::windowFor(MirSurfaceInterface *surface) const
104+{
105+ auto iter = m_surfaceToWindow.find(surface);
106+ if (iter != m_surfaceToWindow.end()) {
107+ return iter->window;
108+ }
109+ return miral::Window();
110+}
111+
61MirSurface *SurfaceManager::createSurface(const QString& name,
112MirSurface *SurfaceManager::createSurface(const QString& name,
62 Mir::Type type,
113 Mir::Type type,
63 Mir::State state,
114 Mir::State state,
@@ -75,7 +126,12 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
75
126
76void SurfaceManager::registerSurface(MirSurface *surface)
127void SurfaceManager::registerSurface(MirSurface *surface)
77{
128{
129+ auto fakeSurface = std::make_shared<mir::test::doubles::StubSurface>();
130+ WindowWrapper window{miral::Window(nullptr, fakeSurface), fakeSurface};
131+
78 m_surfaces.prepend(surface);
132 m_surfaces.prepend(surface);
133+ m_windowToSurface.insert(window, surface);
134+ m_surfaceToWindow.insert(surface, window);
79
135
80 if (!surface->parentSurface()) {
136 if (!surface->parentSurface()) {
81 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
137 surface->setMinimumWidth(m_newSurfaceMinimumWidth);
@@ -90,16 +146,24 @@ void SurfaceManager::registerSurface(MirSurface *surface)
90 this->onStateRequested(surface, state);
146 this->onStateRequested(surface, state);
91 });
147 });
92
148
93− const QString persistentId = surface->persistentId();
94 connect(surface, &QObject::destroyed, this, [=]() {
149 connect(surface, &QObject::destroyed, this, [=]() {
150+ auto iter = m_surfaceToWindow.find(surface);
151+ if (iter != m_surfaceToWindow.end()) {
152+ WindowWrapper key = m_surfaceToWindow.value(surface);
153+ WindowManagementPolicy::instance()->removeWindow(key.window);
95− this->onSurfaceDestroyed(surface, persistentId);
154+ this->onSurfaceDestroyed(surface);
155+ }
96 });
156 });
97−
98}
157}
99
158
100void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
159void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
101{
160{
161+ if (Q_UNLIKELY(!m_surfaceToWindow.contains(surface))) {
102− Q_EMIT surfaceCreated(surface);
162+ Q_EMIT surfaceCreated(surface);
163+ return;
164+ }
165+
166+ WindowManagementPolicy::instance()->addWindow(m_surfaceToWindow[surface].window);
103}
167}
104
168
105void SurfaceManager::setNewSurfaceMinimumWidth(int value)
169void SurfaceManager::setNewSurfaceMinimumWidth(int value)
@@ -209,6 +273,32 @@ void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
209 DEBUG_MSG("("<<surface<<") ended");
273 DEBUG_MSG("("<<surface<<") ended");
210}
274}
211
275
276+void SurfaceManager::forEachSurfaceInWorkspace(const std::shared_ptr<miral::Workspace> &workspace,
277+ const std::function<void(unity::shell::application::MirSurfaceInterface*)> &callback)
278+{
279+ WindowManagementPolicy::instance()->forEachWindowInWorkspace(workspace, [&](const miral::Window &window) {
280+ auto surface = surfaceFor(window);
281+ if (surface) {
282+ callback(surface);
283+ }
284+ });
285+}
286+
287+void SurfaceManager::moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
288+ const std::shared_ptr<miral::Workspace> &workspace)
289+{
290+ auto window = windowFor(surface);
291+ if (window) {
292+ WindowManagementPolicy::instance()->moveWindowToWorkspace(window, workspace);
293+ }
294+}
295+
296+void SurfaceManager::moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &to,
297+ const std::shared_ptr<miral::Workspace> &from)
298+{
299+ WindowManagementPolicy::instance()->moveWorkspaceContentToWorkspace(to, from);
300+}
301+
212void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
302void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
213{
303{
214 DEBUG_MSG("("<<surface<<","<<state<<") started");
304 DEBUG_MSG("("<<surface<<","<<state<<") started");
@@ -230,9 +320,17 @@ void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
230 DEBUG_MSG("("<<surface<<","<<state<<") ended");
320 DEBUG_MSG("("<<surface<<","<<state<<") ended");
231}
321}
232
322
233−void SurfaceManager::onSurfaceDestroyed(MirSurface *surface, const QString& persistentId)
323+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
234{
324{
235 m_surfaces.removeAll(surface);
325 m_surfaces.removeAll(surface);
326+
327+ auto iter = m_surfaceToWindow.find(surface);
328+ if (iter != m_surfaceToWindow.end()) {
329+ WindowWrapper key = iter.value();
330+ m_windowToSurface.remove(key);
331+ m_surfaceToWindow.erase(iter);
332+ }
333+
236 if (m_focusedSurface == surface) {
334 if (m_focusedSurface == surface) {
237 m_focusedSurface = nullptr;
335 m_focusedSurface = nullptr;
238
336
@@ -244,7 +342,7 @@ void SurfaceManager::onSurfaceDestroyed(MirSurface *surface, const QString& pers
244 m_underModification = false;
342 m_underModification = false;
245 Q_EMIT modificationsEnded();
343 Q_EMIT modificationsEnded();
246 }
344 }
247− Q_EMIT surfaceDestroyed(persistentId);
345+ Q_EMIT surfaceRemoved(surface);
248}
346}
249
347
250void SurfaceManager::focusFirstAvailableSurface()
348void SurfaceManager::focusFirstAvailableSurface()
@@ -276,6 +374,15 @@ void SurfaceManager::createInputMethodSurface()
276 if (!m_virtualKeyboard) {
374 if (!m_virtualKeyboard) {
277 m_virtualKeyboard = new VirtualKeyboard;
375 m_virtualKeyboard = new VirtualKeyboard;
278 registerSurface(m_virtualKeyboard);
376 registerSurface(m_virtualKeyboard);
377+
378+ WindowManagementPolicy::instance()->addWindow(m_surfaceToWindow[m_virtualKeyboard].window);
379+ }
380+}
381+
382+void SurfaceManager::releaseInputMethodSurface()
383+{
279− Q_EMIT surfaceCreated(m_virtualKeyboard);
384+ if (m_virtualKeyboard) {
385+ m_virtualKeyboard->setLive(false);
386+ m_virtualKeyboard = nullptr;
280 }
387 }
281}
388}
tests/mocks/Unity/Application/SurfaceManager.h+23−3
@@ -20,12 +20,20 @@
20#include <QObject>
20#include <QObject>
21
21
22#include <unity/shell/application/SurfaceManagerInterface.h>
22#include <unity/shell/application/SurfaceManagerInterface.h>
23+#include <miral/window.h>
23
24
24#include "MirSurface.h"
25#include "MirSurface.h"
25#include "VirtualKeyboard.h"
26#include "VirtualKeyboard.h"
26
27
27class ApplicationInfo;
28class ApplicationInfo;
28
29
30+struct WindowWrapper {
31+ miral::Window window;
32+ std::shared_ptr<mir::scene::Surface> session{nullptr}; // Keeps the window surface alive.
33+ bool operator==(const WindowWrapper& other) const { return window==other.window; }
34+};
35+uint qHash(const WindowWrapper &key, uint seed = 0);
36+
29class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
37class SurfaceManager : public unity::shell::application::SurfaceManagerInterface
30{
38{
31 Q_OBJECT
39 Q_OBJECT
@@ -46,6 +54,13 @@ public:
46 void raise(unity::shell::application::MirSurfaceInterface *surface) override;
54 void raise(unity::shell::application::MirSurfaceInterface *surface) override;
47 void activate(unity::shell::application::MirSurfaceInterface *surface) override;
55 void activate(unity::shell::application::MirSurfaceInterface *surface) override;
48
56
57+ void forEachSurfaceInWorkspace(const std::shared_ptr<miral::Workspace> &workspace,
58+ const std::function<void(unity::shell::application::MirSurfaceInterface*)> &callback) override;
59+ void moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface* surface,
60+ const std::shared_ptr<miral::Workspace> &workspace) override;
61+ void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &to,
62+ const std::shared_ptr<miral::Workspace> &from) override;
63+
49 Q_INVOKABLE MirSurface* createSurface(const QString& name,
64 Q_INVOKABLE MirSurface* createSurface(const QString& name,
50 Mir::Type type,
65 Mir::Type type,
51 Mir::State state,
66 Mir::State state,
@@ -76,10 +91,9 @@ public:
76
91
77public Q_SLOTS:
92public Q_SLOTS:
78 void createInputMethodSurface();
93 void createInputMethodSurface();
94+ void releaseInputMethodSurface();
79
95
80Q_SIGNALS:
96Q_SIGNALS:
81− void surfaceDestroyed(const QString& persistentSurfaceId);
82−
83 void newSurfaceMinimumWidthChanged(int value);
97 void newSurfaceMinimumWidthChanged(int value);
84 void newSurfaceMaximumWidthChanged(int value);
98 void newSurfaceMaximumWidthChanged(int value);
85 void newSurfaceMinimumHeightChanged(int value);
99 void newSurfaceMinimumHeightChanged(int value);
@@ -89,12 +103,15 @@ Q_SIGNALS:
89
103
90private Q_SLOTS:
104private Q_SLOTS:
91 void onStateRequested(MirSurface *surface, Mir::State state);
105 void onStateRequested(MirSurface *surface, Mir::State state);
92− void onSurfaceDestroyed(MirSurface *surface, const QString& persistentId);
106+ void onSurfaceDestroyed(MirSurface *surface);
93
107
94private:
108private:
95 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
109 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
96 void focusFirstAvailableSurface();
110 void focusFirstAvailableSurface();
97 void registerSurface(MirSurface *surface);
111 void registerSurface(MirSurface *surface);
112+ unity::shell::application::MirSurfaceInterface* surfaceFor(const miral::Window &window) const;
113+ QVector<unity::shell::application::MirSurfaceInterface*> surfacesFor(const std::vector<miral::Window> &windows) const;
114+ miral::Window windowFor(unity::shell::application::MirSurfaceInterface* surface) const;
98
115
99 static SurfaceManager *m_instance;
116 static SurfaceManager *m_instance;
100
117
@@ -110,6 +127,9 @@ private:
110
127
111 QList<MirSurface*> m_surfaces;
128 QList<MirSurface*> m_surfaces;
112
129
130+ QHash<WindowWrapper, unity::shell::application::MirSurfaceInterface*> m_windowToSurface;
131+ QHash<unity::shell::application::MirSurfaceInterface*, WindowWrapper> m_surfaceToWindow;
132+
113 VirtualKeyboard *m_virtualKeyboard{nullptr};
133 VirtualKeyboard *m_virtualKeyboard{nullptr};
114};
134};
115
135
tests/mocks/Unity/Application/plugin.cpp+7−1
@@ -38,6 +38,12 @@ QObject* mirSingleton(QQmlEngine*, QJSEngine*)
38{
38{
39 return new MirMock;
39 return new MirMock;
40}
40}
41+
42+QObject* surfaceManagerSingleton(QQmlEngine*, QJSEngine*)
43+{
44+ return new SurfaceManager;
45+}
46+
41} // anonymous namespace
47} // anonymous namespace
42
48
43void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
49void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -59,7 +65,7 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
59
65
60 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
66 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
61 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
67 qmlRegisterSingletonType<MirMock>(uri, 0, 1, "Mir", mirSingleton);
62− qmlRegisterType<SurfaceManager>(uri, 0, 1, "SurfaceManager");
68+ qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
63}
69}
64
70
65void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
71void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml−3
@@ -20,9 +20,6 @@ Rectangle {
20 id: root
20 id: root
21 color: "pink"
21 color: "pink"
22
22
23− implicitWidth: width
24− implicitHeight: height
25−
26 property alias screenshotSource: screenshotImage.source
23 property alias screenshotSource: screenshotImage.source
27 property int orientationAngle
24 property int orientationAngle
28
25
tests/mocks/Unity/CMakeLists.txt−1
@@ -5,5 +5,4 @@ add_subdirectory(Indicators)
5add_subdirectory(InputInfo)
5add_subdirectory(InputInfo)
6add_subdirectory(Launcher)
6add_subdirectory(Launcher)
7add_subdirectory(Notifications)
7add_subdirectory(Notifications)
8−add_subdirectory(Screens)
9add_subdirectory(Platform)
8add_subdirectory(Platform)
tests/mocks/Unity/InputInfo/mockcontroller.cpp+10
@@ -18,10 +18,20 @@
18
18
19#include "qinputdeviceinfo_mock_p.h"
19#include "qinputdeviceinfo_mock_p.h"
20
20
21+#include <QQuickView>
22+#include <QQmlComponent>
23+#include <QQuickItem>
24+#include <paths.h>
25+
21MockController::MockController(QObject *parent):
26MockController::MockController(QObject *parent):
22 QObject(parent)
27 QObject(parent)
23{
28{
29+}
24
30
31+MockController *MockController::instance()
32+{
33+ static MockController* controller = new MockController();
34+ return controller;
25}
35}
26
36
27QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
37QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
tests/mocks/Unity/InputInfo/mockcontroller.h+3−1
@@ -24,9 +24,11 @@ class MockController: public QObject
24{
24{
25 Q_OBJECT
25 Q_OBJECT
26public:
26public:
27− MockController(QObject *parent = 0);
27+ MockController(QObject *parent = nullptr);
28 ~MockController() = default;
28 ~MockController() = default;
29
29
30+ static MockController *instance();
31+
30 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
32 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
31 Q_INVOKABLE void removeDevice(const QString &devicePath);
33 Q_INVOKABLE void removeDevice(const QString &devicePath);
32};
34};
tests/mocks/Unity/InputInfo/plugin.cpp+1−2
@@ -26,9 +26,8 @@
26
26
27static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
27static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
28{
28{
29− Q_UNUSED(engine)
30 Q_UNUSED(scriptEngine)
29 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
30+ return MockController::instance();
32}
31}
33
32
34void InputInfoPlugin::registerTypes(const char *uri)
33void InputInfoPlugin::registerTypes(const char *uri)
tests/mocks/Unity/Screens/CMakeLists.txtdeleted−14
@@ -1,14 +0,0 @@
1−include_directories(
2− ${CMAKE_CURRENT_SOURCE_DIR}
3−)
4−
5−set(MockScreens_SOURCES
6− plugin.cpp
7− screens.cpp
8−)
9−
10−add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
11−
12−qt5_use_modules(MockScreensPlugin Gui Qml)
13−
14−add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
tests/mocks/Unity/Screens/qmldirdeleted−2
@@ -1,2 +0,0 @@
1−module Unity.Screens
2−plugin MockScreensPlugin
tests/mocks/Unity/Screens/screens.cppdeleted−71
@@ -1,71 +0,0 @@
1−/*
2− * Copyright (C) 2015 Canonical, Ltd.
3− *
4− * This program is free software: you can redistribute it and/or modify it under
5− * the terms of the GNU Lesser General Public License version 3, as published by
6− * the Free Software Foundation.
7− *
8− * This program is distributed in the hope that it will be useful, but WITHOUT
9− * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10− * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11− * Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−#include "screens.h"
18−
19−// Qt
20−#include <QGuiApplication>
21−#include <QScreen>
22−#include <QDebug>
23−
24−Q_DECLARE_METATYPE(QScreen*)
25−
26−Screens::Screens(QObject *parent) :
27− QAbstractListModel(parent)
28−{
29− // start with one screen attached
30− m_screenList.append(new Screen());
31−}
32−
33−Screens::~Screens() noexcept
34−{
35− qDeleteAll(m_screenList);
36− m_screenList.clear();
37−}
38−
39−QHash<int, QByteArray> Screens::roleNames() const
40−{
41− QHash<int, QByteArray> roles;
42− roles[ScreenRole] = "screen";
43− roles[OutputTypeRole] = "outputType";
44− return roles;
45−}
46−
47−QVariant Screens::data(const QModelIndex &index, int role) const
48−{
49− if (!index.isValid() || index.row() >= m_screenList.size()) {
50− return QVariant();
51− }
52−
53− switch(role) {
54− case ScreenRole:
55− return QVariant::fromValue(m_screenList.at(index.row())->qScreen);
56− case OutputTypeRole:
57− return m_screenList.at(index.row())->outputTypes;
58− }
59−
60− return QVariant();
61−}
62−
63−int Screens::rowCount(const QModelIndex &) const
64−{
65− return count();
66−}
67−
68−int Screens::count() const
69−{
70− return m_screenList.size();
71−}
tests/mocks/Unity/Screens/screens.hdeleted−82
@@ -1,82 +0,0 @@
1−/*
2− * Copyright (C) 2015 Canonical, Ltd.
3− *
4− * This program is free software: you can redistribute it and/or modify it under
5− * the terms of the GNU Lesser General Public License version 3, as published by
6− * the Free Software Foundation.
7− *
8− * This program is distributed in the hope that it will be useful, but WITHOUT
9− * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10− * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11− * Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−#ifndef SCREENS_H
18−#define SCREENS_H
19−
20−#include <QAbstractListModel>
21−#include <QScreen>
22−
23−class Screen;
24−
25−class Screens : public QAbstractListModel
26−{
27− Q_OBJECT
28−
29− Q_PROPERTY(int count READ count NOTIFY countChanged)
30−
31−public:
32− enum ItemRoles {
33− ScreenRole = Qt::UserRole + 1,
34− OutputTypeRole
35− };
36−
37− enum OutputTypes {
38− Unknown,
39− VGA,
40− DVII,
41− DVID,
42− DVIA,
43− Composite,
44− SVideo,
45− LVDS,
46− Component,
47− NinePinDIN,
48− DisplayPort,
49− HDMIA,
50− HDMIB,
51− TV,
52− EDP
53− };
54− Q_ENUM(OutputTypes)
55−
56− explicit Screens(QObject *parent = 0);
57− virtual ~Screens() noexcept;
58−
59− /* QAbstractItemModel */
60− QHash<int, QByteArray> roleNames() const override;
61− QVariant data(const QModelIndex &index, int role = ScreenRole) const override;
62− int rowCount(const QModelIndex &parent = QModelIndex()) const override;
63−
64− int count() const;
65−
66−Q_SIGNALS:
67− void countChanged();
68− void screenAdded(QScreen *screen);
69− void screenRemoved(QScreen *screen);
70−
71−private:
72− QList<Screen *> m_screenList;
73−};
74−
75−class Screen
76−{
77−public:
78− Screens::OutputTypes outputTypes = Screens::Unknown;
79− QScreen *qScreen = nullptr;
80−};
81−
82−#endif // SCREENS_H
tests/mocks/WindowManager/CMakeLists.txtnew file+61
@@ -0,0 +1,61 @@
1+include_directories(
2+ SYSTEM
3+ ${QTMIRSERVER_INCLUDE_DIRS}
4+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
5+)
6+
7+include_directories(
8+ ${CMAKE_CURRENT_SOURCE_DIR}
9+ ${CMAKE_CURRENT_BINARY_DIR}
10+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager
11+ ${libunity8-private_SOURCE_DIR}
12+)
13+
14+add_library(mockwindowmanagmentpolicy SHARED
15+ WindowManagementPolicy.cpp
16+)
17+target_link_libraries(mockwindowmanagmentpolicy
18+ ${MIRAL_LDFLAGS}
19+ unity8-private
20+)
21+qt5_use_modules(mockwindowmanagmentpolicy Core)
22+
23+install(TARGETS mockwindowmanagmentpolicy
24+ DESTINATION ${SHELL_INSTALL_QML}/mocks/WindowManager
25+ )
26+
27+set(WINDOWMANAGER_SRC
28+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/AvailableDesktopArea.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Screen.cpp
30+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/ScreenAttached.cpp
31+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Screens.cpp
32+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/ScreenWindow.cpp
33+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/TopLevelWindowModel.cpp
34+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Window.cpp
35+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WindowMargins.cpp
36+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WindowManagerObjects.cpp
37+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceManager.cpp
38+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/WorkspaceModel.cpp
39+ ${CMAKE_SOURCE_DIR}/plugins/WindowManager/Workspace.cpp
40+ MockScreens.cpp
41+ MockScreenWindow.cpp
42+ MockScreensConfiguration.cpp
43+ WindowManagerPlugin.cpp
44+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
45+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
46+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
47+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
48+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
49+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
50+)
51+
52+add_library(MockWindowManager-qml SHARED ${WINDOWMANAGER_SRC})
53+
54+target_link_libraries(MockWindowManager-qml
55+ ${QTMIRSERVER_LDFLAGS}
56+ mockwindowmanagmentpolicy
57+)
58+
59+qt5_use_modules(MockWindowManager-qml Qml Quick Gui)
60+
61+add_unity8_mock(WindowManager 1.0 WindowManager TARGETS MockWindowManager-qml)
tests/mocks/Unity/Screens/plugin.cpp → tests/mocks/WindowManager/MockScreenWindow.cpprenamed+14−9
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it under
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
5 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,16 +14,21 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#include "plugin.h"
17+#include "MockScreenWindow.h"
18−#include "screens.h"
18+#include "MockScreens.h"
19
19
20−#include <QScreen>
20+// Qt
21+#include <QGuiApplication>
22+#include <QDebug>
21
23
22−void UnityScreensPlugin::registerTypes(const char* uri)
24+MockScreenWindow::MockScreenWindow(QQuickWindow *parent)
25+ : ScreenWindow(parent)
23{
26{
24− Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
27+ connect(this, &ScreenWindow::screenWrapperChanged, this, [this]() {
25−
28+ MockScreens::instance()->connectWindow(this);
26− qRegisterMetaType<QScreen*>("QScreen*");
29+ });
30+}
27
31
28− qmlRegisterType<Screens>(uri, 0, 1, "Screens");
32+MockScreenWindow::~MockScreenWindow()
33+{
29}
34}
tests/mocks/Unity/Screens/plugin.h → tests/mocks/WindowManager/MockScreenWindow.hrenamed+11−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it under
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
5 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -14,12 +14,17 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#include <QQmlExtensionPlugin>
17+#ifndef MOCK_SCREENWINDOW_H
18−#include <QtQml/qqml.h>
18+#define MOCK_SCREENWINDOW_H
19
19
20−class UnityScreensPlugin : public QQmlExtensionPlugin {
20+#include "ScreenWindow.h"
21+
22+class MockScreenWindow : public ScreenWindow
23+{
21 Q_OBJECT
24 Q_OBJECT
22− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0")
23public:
25public:
24− void registerTypes(const char* uri) override;
26+ explicit MockScreenWindow(QQuickWindow *parent = 0);
27+ ~MockScreenWindow();
25};
28};
29+
30+#endif // MOCK_SCREENWINDOW_H
tests/mocks/WindowManager/MockScreens.cppnew file+230
@@ -0,0 +1,230 @@
1+/*
2+ * Copyright (C) 2016-2017 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "MockScreens.h"
18+#include "ScreenWindow.h"
19+
20+// qtmirserver
21+#include <qtmir/screen.h>
22+
23+#include <QGuiApplication>
24+#include <QWindow>
25+#include <QWeakPointer>
26+
27+namespace {
28+
29+QWeakPointer<MockScreens> m_screens;
30+
31+class MockScreen : public qtmir::Screen
32+{
33+ Q_OBJECT
34+ Q_PROPERTY(QString outputTypeName READ outputTypeName NOTIFY outputTypeNameChanged)
35+public:
36+ MockScreen()
37+ {
38+ m_sizes.append(new qtmir::ScreenMode(50, QSize(800,568)));
39+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1280,1024)));
40+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1440,900)));
41+ m_sizes.append(new qtmir::ScreenMode(60, QSize(1920,1080)));
42+ m_physicalSize = QSize(800,568);
43+ }
44+ ~MockScreen() {
45+ qDeleteAll(m_sizes);
46+ m_sizes.clear();
47+ }
48+
49+ void connectToWindow(QWindow* w)
50+ {
51+ if (m_connectedWindow == w) return;
52+
53+ if (m_connectedWindow) {
54+ disconnect(m_connectedWindow.data());
55+ }
56+
57+ m_connectedWindow = w;
58+
59+ if (w) {
60+ connect(w, &QWindow::heightChanged, this, [this](int height) {
61+ if (height == 0 || height == m_sizes.first()->size.rheight()) {
62+ return;
63+ }
64+ m_sizes.first()->size.rheight() = height;
65+ Q_EMIT availableModesChanged();
66+
67+ });
68+ connect(w, &QWindow::widthChanged, this, [this](int width) {
69+ if (width == 0 || width == m_sizes.first()->size.rwidth()) {
70+ return;
71+ }
72+ m_sizes.first()->size.rwidth() = width;
73+ Q_EMIT availableModesChanged();
74+ });
75+ connect(w, &QWindow::activeChanged, this, [w, this]() {
76+ if (w->isActive()) setActive(true);
77+ });
78+ if (w->isActive()) setActive(true);
79+
80+ Q_EMIT availableModesChanged();
81+
82+ }
83+ }
84+
85+ miral::DisplayId displayId() const override { return m_id; }
86+ bool used() const override { return m_used; }
87+ QString name() const override { return m_name; }
88+ float scale() const override { return m_scale; }
89+ QSizeF physicalSize() const override { return m_physicalSize; }
90+ qtmir::FormFactor formFactor() const override { return m_formFactor; }
91+ qtmir::OutputTypes outputType() const override { return m_outputType; }
92+ QString outputTypeName() const { return QStringLiteral("Internal"); }
93+ MirPowerMode powerMode() const override { return m_powerMode; }
94+ Qt::ScreenOrientation orientation() const override { return m_orientation; }
95+ QPoint position() const override { return m_position; }
96+ uint currentModeIndex() const override { return m_currentModeIndex; }
97+ bool isActive() const override { return m_active; }
98+
99+ QQmlListProperty<qtmir::ScreenMode> availableModes() override {
100+ return QQmlListProperty<qtmir::ScreenMode>(this, m_sizes);
101+ }
102+
103+ void setActive(bool active) override {
104+ if (m_active != active) {
105+ m_active = active;
106+ Q_EMIT activeChanged(m_active);
107+ }
108+ }
109+
110+ QScreen* qscreen() const override {
111+ if (qGuiApp->topLevelWindows().count() > 0) {
112+ return qGuiApp->topLevelWindows()[0]->screen();
113+ }
114+ return qGuiApp->primaryScreen();
115+ }
116+
117+ qtmir::ScreenConfiguration *beginConfiguration() const override {
118+ auto config = new qtmir::ScreenConfiguration;
119+ config->valid = true;
120+ config->id = m_id.output_id;
121+ config->used = m_used;
122+ config->topLeft = m_position;
123+ config->currentModeIndex = m_currentModeIndex;
124+ config->powerMode = m_powerMode;
125+ config->scale = m_scale;
126+ config->formFactor = m_formFactor;
127+ return config;
128+ }
129+
130+ bool applyConfiguration(qtmir::ScreenConfiguration *configuration) override {
131+ m_used = configuration->used;
132+ m_position = configuration->topLeft;
133+ m_currentModeIndex = configuration->currentModeIndex;
134+ m_powerMode = configuration->powerMode;
135+ m_scale = configuration->scale;
136+ m_formFactor = configuration->formFactor;
137+ return true;
138+ }
139+
140+Q_SIGNALS:
141+ void outputTypeNameChanged();
142+
143+public:
144+ miral::DisplayId m_id;
145+ bool m_active{false};
146+ bool m_used{true};
147+ QString m_name;
148+ qtmir::OutputTypes m_outputType{qtmir::Unknown};
149+ MirPowerMode m_powerMode{mir_power_mode_on};
150+ Qt::ScreenOrientation m_orientation{Qt::PrimaryOrientation};
151+ float m_scale{1.0};
152+ qtmir::FormFactor m_formFactor{qtmir::FormFactorMonitor};
153+ QPoint m_position;
154+ uint m_currentModeIndex{0};
155+ QList<qtmir::ScreenMode*> m_sizes;
156+ QSizeF m_physicalSize;
157+
158+ QPointer<QWindow> m_connectedWindow;
159+};
160+}
161+
162+MockScreens::MockScreens()
163+{
164+ bool ok = false;
165+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
166+ if (!ok) screenCount = 1;
167+ QPoint lastPoint(0,0);
168+ for (int i = 0; i < screenCount; ++i) {
169+ auto screen = new MockScreen();
170+ screen->m_id.output_id = miral::OutputId{i};
171+ screen->m_active = i == 0;
172+ screen->m_name = QString("Monitor %1").arg(i);
173+ screen->m_position = QPoint(lastPoint.x(), lastPoint.y());
174+ screen->m_currentModeIndex = 0;
175+ m_mocks.append(screen);
176+
177+ lastPoint.rx() += screen->m_sizes[screen->m_currentModeIndex]->size.width();
178+
179+ connect(screen, &qtmir::Screen::activeChanged, this, [=](bool active) {
180+ Q_FOREACH(auto otherScreen, m_mocks) {
181+ if (active && otherScreen != screen) {
182+ otherScreen->setActive(false);
183+ }
184+ }
185+ });
186+ }
187+}
188+
189+MockScreens::~MockScreens()
190+{
191+ qDeleteAll(m_mocks);
192+ m_mocks.clear();
193+}
194+
195+QVector<qtmir::Screen *> MockScreens::screens() const
196+{
197+ return m_mocks;
198+}
199+
200+qtmir::Screen *MockScreens::activeScreen() const
201+{
202+ Q_FOREACH(auto screen, m_mocks) {
203+ if (screen->isActive()) return screen;
204+ }
205+ return nullptr;
206+}
207+
208+QSharedPointer<MockScreens> MockScreens::instance()
209+{
210+ if (!m_screens) {
211+ QSharedPointer<MockScreens> screens(new MockScreens());
212+ m_screens = screens.toWeakRef();
213+ return screens;
214+ } else {
215+ return m_screens.lock();
216+ }
217+}
218+
219+void MockScreens::connectWindow(ScreenWindow *w)
220+{
221+ ConcreteScreen* screen = w->screenWrapper();
222+ if (!screen) return;
223+
224+ auto mockScreen = qobject_cast<MockScreen*>(screen->wrapped());
225+ if (mockScreen) {
226+ mockScreen->connectToWindow(w);
227+ }
228+}
229+
230+#include "MockScreens.moc"
tests/mocks/WindowManager/MockScreens.hnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef MOCK_SCREENS_H
18+#define MOCK_SCREENS_H
19+
20+#include <QAbstractListModel>
21+#include <QSharedPointer>
22+
23+#include <qtmir/screens.h>
24+
25+namespace qtmir
26+{
27+class Screens;
28+}
29+
30+class ConcreteScreen;
31+class ScreenWindow;
32+
33+class MockScreens : public qtmir::Screens
34+{
35+ Q_OBJECT
36+public:
37+ MockScreens();
38+ ~MockScreens();
39+
40+ QVector<qtmir::Screen*> screens() const override;
41+
42+ qtmir::Screen *activeScreen() const override;
43+
44+ static QSharedPointer<MockScreens> instance();
45+
46+public Q_SLOTS:
47+ void connectWindow(ScreenWindow *w);
48+
49+private:
50+ QVector<qtmir::Screen*> m_mocks;
51+};
52+
53+
54+#endif // MOCK_SCREENS_H
tests/mocks/WindowManager/MockScreensConfiguration.cppnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2017 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 "ScreensConfiguration.h"
18+#include "Screen.h"
19+#include "Workspace.h"
20+#include "WorkspaceManager.h"
21+
22+namespace
23+{
24+const int DefaultWorkspacesCount = 2;
25+QHash<QString, int> screensConfig;
26+}
27+
28+ScreensConfiguration::ScreensConfiguration()
29+{
30+}
31+
32+ScreensConfiguration::~ScreensConfiguration()
33+{
34+}
35+
36+void ScreensConfiguration::load(Screen *screen)
37+{
38+ int workspaces = screensConfig.value(screen->name(), DefaultWorkspacesCount);
39+ for (int i = 0; i < workspaces; i++) {
40+ WorkspaceManager::instance()->createWorkspace()->assign(screen->workspaces());
41+ }
42+}
43+
44+void ScreensConfiguration::save(Screen *screen)
45+{
46+ screensConfig[screen->name()] = screen->workspaces()->rowCount();
47+}
tests/mocks/WindowManager/WindowManagementPolicy.cppnew file+128
@@ -0,0 +1,128 @@
1+/*
2+ * Copyright (C) 2017 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 "WindowManagementPolicy.h"
18+
19+#include <QDebug>
20+
21+WindowManagementPolicy::WindowManagementPolicy()
22+ : m_dummyWorkspace(std::shared_ptr<miral::Workspace>())
23+{
24+ wmPolicyInterface = this;
25+ m_activeWorkspace = m_dummyWorkspace;
26+}
27+
28+WindowManagementPolicy *WindowManagementPolicy::instance()
29+{
30+ static WindowManagementPolicy* wmPolicy(new WindowManagementPolicy);
31+ return wmPolicy;
32+}
33+
34+std::shared_ptr<miral::Workspace> WindowManagementPolicy::createWorkspace()
35+{
36+ auto workspace = std::make_shared<miral::Workspace>();
37+ m_workspaces.insert(workspace);
38+
39+ if (m_activeWorkspace.lock() == m_dummyWorkspace) {
40+ moveWorkspaceContentToWorkspace(workspace, m_dummyWorkspace);
41+ m_activeWorkspace = workspace;
42+ }
43+ return workspace;
44+}
45+
46+void WindowManagementPolicy::releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
47+{
48+ auto iter = m_workspaces.find(workspace);
49+ if (iter != m_workspaces.end()) m_workspaces.erase(iter);
50+
51+ if (m_workspaces.size() == 0) {
52+ m_activeWorkspace = m_dummyWorkspace;
53+ moveWorkspaceContentToWorkspace(m_dummyWorkspace, workspace);
54+ }
55+}
56+
57+void WindowManagementPolicy::setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace)
58+{
59+ if (m_activeWorkspace.lock() == workspace)
60+ return;
61+ m_activeWorkspace = workspace ? workspace : m_dummyWorkspace;
62+}
63+
64+void WindowManagementPolicy::addWindow(const miral::Window &window)
65+{
66+ Q_EMIT windowAdded(window);
67+
68+ auto activeWorkspace = m_activeWorkspace.lock();
69+ if (activeWorkspace) {
70+ m_windows.insert(activeWorkspace, window);
71+
72+ Q_EMIT windowsAddedToWorkspace(activeWorkspace, {window});
73+ }
74+}
75+
76+void WindowManagementPolicy::removeWindow(const miral::Window &window)
77+{
78+ WorkspaceWindows::iterator i = m_windows.begin();
79+ while (i != m_windows.end()) {
80+ if (i.value() == window) {
81+ Q_EMIT windowsAboutToBeRemovedFromWorkspace(i.key(), { window });
82+ i = m_windows.erase(i);
83+ } else {
84+ ++i;
85+ }
86+ }
87+ Q_EMIT windowRemoved(window);
88+}
89+
90+void WindowManagementPolicy::forEachWindowInWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::function<void(const miral::Window &)> &callback)
91+{
92+ WorkspaceWindows::iterator i = m_windows.find(workspace);
93+ while (i != m_windows.end() && i.key() == workspace) {
94+ callback(i.value());
95+ ++i;
96+ }
97+}
98+
99+void WindowManagementPolicy::moveWindowToWorkspace(const miral::Window &window, const std::shared_ptr<miral::Workspace> &workspace)
100+{
101+ auto from = m_windows.key(window);
102+ if (from) {
103+ auto iter = m_windows.find(from, window);
104+ Q_EMIT windowsAboutToBeRemovedFromWorkspace(from, { window });
105+ m_windows.erase(iter);
106+ }
107+
108+ m_windows.insert(workspace, window);
109+ Q_EMIT windowsAddedToWorkspace(workspace, { window });
110+}
111+
112+void WindowManagementPolicy::moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &to, const std::shared_ptr<miral::Workspace> &from)
113+{
114+ std::vector<miral::Window> windows;
115+
116+ WorkspaceWindows::iterator i = m_windows.find(from);
117+ while (i != m_windows.end() && i.key() == from) {
118+ windows.push_back(i.value());
119+ ++i;
120+ }
121+ Q_EMIT windowsAboutToBeRemovedFromWorkspace(from, windows);
122+ m_windows.remove(from);
123+
124+ Q_FOREACH(miral::Window window, windows) {
125+ m_windows.insert(to, window);
126+ }
127+ Q_EMIT windowsAddedToWorkspace(to, windows);
128+}
tests/mocks/WindowManager/WindowManagementPolicy.hnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef MOCKWINDOMANAGEMENTPOLICY_H
18+#define MOCKWINDOMANAGEMENTPOLICY_H
19+
20+#include "wmpolicyinterface.h"
21+
22+#include <QObject>
23+#include <QMultiMap>
24+#include <QVector>
25+
26+#include <miral/window.h>
27+#include <memory>
28+#include <unordered_set>
29+
30+namespace miral {
31+class Workspace {};
32+}
33+
34+// A Fake window management policy for the mock.
35+class Q_DECL_EXPORT WindowManagementPolicy : public QObject,
36+ public WMPolicyInterface
37+{
38+ Q_OBJECT
39+public:
40+ WindowManagementPolicy();
41+
42+ // for use in mocks
43+ static WindowManagementPolicy *instance();
44+
45+ // From WMPolicyInterface
46+ std::shared_ptr<miral::Workspace> createWorkspace() override;
47+ void releaseWorkspace(const std::shared_ptr<miral::Workspace> &workspace) override;
48+ void setActiveWorkspace(const std::shared_ptr<miral::Workspace> &workspace) override;
49+
50+ void addWindow(const miral::Window& window);
51+ void removeWindow(const miral::Window& window);
52+
53+ void forEachWindowInWorkspace(std::shared_ptr<miral::Workspace> const &workspace,
54+ std::function<void(miral::Window const&)> const &callback);
55+
56+ void moveWindowToWorkspace(const miral::Window &window, const std::shared_ptr<miral::Workspace> &workspace);
57+
58+ void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace> &to,
59+ const std::shared_ptr<miral::Workspace> &from);
60+
61+Q_SIGNALS:
62+ void windowAdded(const miral::Window& window);
63+ void windowRemoved(const miral::Window& window);
64+ void windowsAddedToWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows);
65+ void windowsAboutToBeRemovedFromWorkspace(const std::shared_ptr<miral::Workspace> &workspace, const std::vector<miral::Window> &windows);
66+
67+private:
68+ std::weak_ptr<miral::Workspace> m_activeWorkspace;
69+ std::shared_ptr<miral::Workspace> m_dummyWorkspace;
70+ std::unordered_set<std::shared_ptr<miral::Workspace>> m_workspaces;
71+
72+ typedef QMultiMap<std::shared_ptr<miral::Workspace>, miral::Window> WorkspaceWindows;
73+ WorkspaceWindows m_windows;
74+};
75+#endif // UNITY_WINDOWMANAGEMENTPOLICY_H
tests/mocks/WindowManager/WindowManagerPlugin.cppnew file+89
@@ -0,0 +1,89 @@
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 "WindowManagerPlugin.h"
18+
19+#include "AvailableDesktopArea.h"
20+#include "MockScreens.h"
21+#include "MockScreenWindow.h"
22+#include "Screen.h"
23+#include "ScreenAttached.h"
24+#include "Screens.h"
25+#include "ScreensConfiguration.h"
26+#include "TopLevelWindowModel.h"
27+#include "Window.h"
28+#include "WindowMargins.h"
29+#include "WindowManagementPolicy.h"
30+#include "WindowManagerObjects.h"
31+#include "WorkspaceManager.h"
32+#include "Workspace.h"
33+#include "WorkspaceModel.h"
34+
35+#include <QtQml>
36+
37+static const QString notInstantiatable = QStringLiteral("Not instantiatable");
38+
39+static QObject *workspace_manager(QQmlEngine *engine, QJSEngine *scriptEngine)
40+{
41+ Q_UNUSED(engine)
42+ Q_UNUSED(scriptEngine)
43+ return WorkspaceManager::instance();
44+}
45+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
46+ Q_UNUSED(engine);
47+ Q_UNUSED(scriptEngine);
48+ return ConcreteScreens::self();
49+}
50+QObject* objectsSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
51+ Q_UNUSED(engine);
52+ Q_UNUSED(scriptEngine);
53+ return WindowManagerObjects::instance();
54+}
55+
56+void WindowManagerPlugin::registerTypes(const char *uri)
57+{
58+ qmlRegisterType<AvailableDesktopArea>(uri, 1, 0, "AvailableDesktopArea");
59+ qmlRegisterType<WindowMargins>(uri, 1, 0, "WindowMargins");
60+ qmlRegisterSingletonType<WorkspaceManager>(uri, 1, 0, "WorkspaceManager", workspace_manager);
61+ qmlRegisterSingletonType<ConcreteScreens>(uri, 1, 0, "Screens", screensSingleton);
62+ qmlRegisterUncreatableType<qtmir::ScreenMode>(uri, 1, 0, "ScreenMode", notInstantiatable);
63+ qmlRegisterSingletonType<WindowManagerObjects>(uri, 1, 0, "WindowManagerObjects", objectsSingleton);
64+
65+ qRegisterMetaType<ConcreteScreen*>("ConcreteScreen*");
66+ qRegisterMetaType<ProxyScreens*>("ProxyScreens*");
67+ qRegisterMetaType<Workspace*>("Workspace*");
68+ qRegisterMetaType<TopLevelWindowModel*>("TopLevelWindowModel*");
69+ qRegisterMetaType<ScreenConfig*>("ScreenConfig*");
70+ qRegisterMetaType<WorkspaceModel*>("WorkspaceModel*");
71+
72+ qRegisterMetaType<Window*>("Window*");
73+ qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
74+
75+ qmlRegisterType<MockScreenWindow>(uri, 1, 0, "ScreenWindow");
76+ qmlRegisterRevision<QWindow,1>(uri, 1, 0);
77+
78+ qmlRegisterUncreatableType<WMScreen>(uri, 1, 0, "WMScreen", notInstantiatable);
79+}
80+
81+void WindowManagerPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
82+{
83+ QQmlExtensionPlugin::initializeEngine(engine, uri);
84+
85+ // Make sure we've initialized the wm policy.
86+ WindowManagementPolicy::instance();
87+ // Create Screens
88+ new ConcreteScreens(MockScreens::instance(), new ScreensConfiguration());
89+}
tests/mocks/WindowManager/WindowManagerPlugin.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+#ifndef WINDOWMANAGER_PLUGIN_H
18+#define WINDOWMANAGER_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class WindowManagerPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri) override;
30+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
31+};
32+
33+#endif // WINDOWMANAGER_PLUGIN_H
tests/mocks/WindowManager/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module WindowManager
2+plugin MockWindowManager-qml
tests/plugins/GlobalShortcut/GlobalShortcutTest.cpp+1
@@ -38,6 +38,7 @@ private Q_SLOTS:
38 m_inactiveShortcut = dynamic_cast<GlobalShortcut*>(m_view->rootObject()->property("inactiveShortcut").value<QObject*>());
38 m_inactiveShortcut = dynamic_cast<GlobalShortcut*>(m_view->rootObject()->property("inactiveShortcut").value<QObject*>());
39 QVERIFY(m_inactiveShortcut);
39 QVERIFY(m_inactiveShortcut);
40 m_view->show();
40 m_view->show();
41+ m_view->requestActivate();
41 QTest::qWaitForWindowExposed(m_view);
42 QTest::qWaitForWindowExposed(m_view);
42 }
43 }
43
44
tests/plugins/WindowManager/CMakeLists.txt+5−1
@@ -2,6 +2,7 @@ include_directories(
2 ${CMAKE_SOURCE_DIR}/plugins/WindowManager
2 ${CMAKE_SOURCE_DIR}/plugins/WindowManager
3 ${CMAKE_CURRENT_BINARY_DIR}
3 ${CMAKE_CURRENT_BINARY_DIR}
4 ${CMAKE_SOURCE_DIR}/tests/mocks
4 ${CMAKE_SOURCE_DIR}/tests/mocks
5+ ${libunity8-private_SOURCE_DIR}
5 )
6 )
6
7
7add_executable(TopLevelWindowModelTestExec
8add_executable(TopLevelWindowModelTestExec
@@ -19,7 +20,10 @@ add_executable(TopLevelWindowModelTestExec
19 )
20 )
20qt5_use_modules(TopLevelWindowModelTestExec Test Core Gui Qml Quick)
21qt5_use_modules(TopLevelWindowModelTestExec Test Core Gui Qml Quick)
21
22
22−target_link_libraries(TopLevelWindowModelTestExec windowmanager-qml)
23+target_link_libraries(TopLevelWindowModelTestExec
24+ windowmanager-qml
25+ unity8-private
26+ )
23
27
24install(TARGETS TopLevelWindowModelTestExec
28install(TARGETS TopLevelWindowModelTestExec
25 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/WindowManager"
29 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/WindowManager"
tests/plugins/WindowManager/UnityApplicationMocks.h+25
@@ -25,6 +25,8 @@
25// from tests/mocks
25// from tests/mocks
26#include <Unity/Application/MirSurfaceListModel.h>
26#include <Unity/Application/MirSurfaceListModel.h>
27
27
28+#include "wmpolicyinterface.h"
29+
28using namespace unity::shell::application;
30using namespace unity::shell::application;
29
31
30class MirSurface : public MirSurfaceInterface
32class MirSurface : public MirSurfaceInterface
@@ -88,6 +90,13 @@ class SurfaceManager : public SurfaceManagerInterface
88 Q_OBJECT
90 Q_OBJECT
89
91
90public:
92public:
93+ void forEachSurfaceInWorkspace(const std::shared_ptr<miral::Workspace>&,
94+ const std::function<void(unity::shell::application::MirSurfaceInterface*)>&) override {}
95+ void moveSurfaceToWorkspace(unity::shell::application::MirSurfaceInterface*,
96+ const std::shared_ptr<miral::Workspace>&) override {}
97+ void moveWorkspaceContentToWorkspace(const std::shared_ptr<miral::Workspace>&,
98+ const std::shared_ptr<miral::Workspace>&) override {}
99+
91 void raise(MirSurfaceInterface *) override {}
100 void raise(MirSurfaceInterface *) override {}
92 void activate(MirSurfaceInterface *) override {}
101 void activate(MirSurfaceInterface *) override {}
93};
102};
@@ -208,4 +217,20 @@ private:
208 QList<Application*> m_applications;
217 QList<Application*> m_applications;
209};
218};
210
219
220+namespace miral {
221+class Workspace {};
222+}
223+
224+class WindowManagementPolicy : public WMPolicyInterface
225+{
226+public:
227+ WindowManagementPolicy() {}
228+
229+ std::shared_ptr<miral::Workspace> createWorkspace() override { return std::make_shared<miral::Workspace>(); }
230+
231+ void releaseWorkspace(const std::shared_ptr<miral::Workspace>&) override {}
232+
233+ void setActiveWorkspace(const std::shared_ptr<miral::Workspace>&) override {}
234+};
235+
211#endif // UNITYAPPLICATIONMOCKS_H
236#endif // UNITYAPPLICATIONMOCKS_H
tests/plugins/WindowManager/tst_TopLevelWindowModel.cpp+20−8
@@ -19,8 +19,13 @@
19// WindowManager plugin
19// WindowManager plugin
20#include <TopLevelWindowModel.h>
20#include <TopLevelWindowModel.h>
21#include <Window.h>
21#include <Window.h>
22+#include <WindowManagerObjects.h>
23+#include <Workspace.h>
24+#include <WorkspaceManager.h>
22
25
23#include "UnityApplicationMocks.h"
26#include "UnityApplicationMocks.h"
27+#include "wmpolicyinterface.h"
28+
24
29
25class tst_TopLevelWindowModel : public QObject
30class tst_TopLevelWindowModel : public QObject
26{
31{
@@ -34,6 +39,7 @@ private Q_SLOTS:
34 void secondSurfaceIsHidden();
39 void secondSurfaceIsHidden();
35
40
36private:
41private:
42+ Workspace* workspace{nullptr};
37 ApplicationManager *applicationManager{nullptr};
43 ApplicationManager *applicationManager{nullptr};
38 SurfaceManager *surfaceManager{nullptr};
44 SurfaceManager *surfaceManager{nullptr};
39 TopLevelWindowModel *topLevelWindowModel{nullptr};
45 TopLevelWindowModel *topLevelWindowModel{nullptr};
@@ -41,24 +47,30 @@ private:
41
47
42void tst_TopLevelWindowModel::init()
48void tst_TopLevelWindowModel::init()
43{
49{
50+ wmPolicyInterface = new WindowManagementPolicy();
51+
44 applicationManager = new ApplicationManager;
52 applicationManager = new ApplicationManager;
45 surfaceManager = new SurfaceManager;
53 surfaceManager = new SurfaceManager;
54+ WindowManagerObjects::instance()->setApplicationManager(applicationManager);
55+ WindowManagerObjects::instance()->setSurfaceManager(surfaceManager);
46
56
47− topLevelWindowModel = new TopLevelWindowModel;
57+ workspace = WorkspaceManager::instance()->createWorkspace();
48− topLevelWindowModel->setApplicationManager(applicationManager);
49− topLevelWindowModel->setSurfaceManager(surfaceManager);
58+ topLevelWindowModel = workspace->windowModel();
50}
59}
51
60
52void tst_TopLevelWindowModel::cleanup()
61void tst_TopLevelWindowModel::cleanup()
53{
62{
54− delete topLevelWindowModel;
63+ delete workspace;
55− topLevelWindowModel = nullptr;
64+ workspace = nullptr;
56
65
57 delete surfaceManager;
66 delete surfaceManager;
58 surfaceManager = nullptr;
67 surfaceManager = nullptr;
59
68
60 delete applicationManager;
69 delete applicationManager;
61 applicationManager = nullptr;
70 applicationManager = nullptr;
71+
72+ delete wmPolicyInterface;
73+ wmPolicyInterface = nullptr;
62}
74}
63
75
64void tst_TopLevelWindowModel::singleSurfaceStartsHidden()
76void tst_TopLevelWindowModel::singleSurfaceStartsHidden()
@@ -73,7 +85,7 @@ void tst_TopLevelWindowModel::singleSurfaceStartsHidden()
73 auto surface = new MirSurface;
85 auto surface = new MirSurface;
74 surface->m_state = Mir::HiddenState;
86 surface->m_state = Mir::HiddenState;
75 application->m_surfaceList.addSurface(surface);
87 application->m_surfaceList.addSurface(surface);
76− Q_EMIT surfaceManager->surfaceCreated(surface);
88+ Q_EMIT surfaceManager->surfacesAddedToWorkspace(workspace->workspace(), {surface});
77
89
78 QCOMPARE(topLevelWindowModel->rowCount(), 1);
90 QCOMPARE(topLevelWindowModel->rowCount(), 1);
79 // not showing the surface as it's still hidden
91 // not showing the surface as it's still hidden
@@ -97,7 +109,7 @@ void tst_TopLevelWindowModel::secondSurfaceIsHidden()
97
109
98 auto firstSurface = new MirSurface;
110 auto firstSurface = new MirSurface;
99 application->m_surfaceList.addSurface(firstSurface);
111 application->m_surfaceList.addSurface(firstSurface);
100− Q_EMIT surfaceManager->surfaceCreated(firstSurface);
112+ Q_EMIT surfaceManager->surfacesAddedToWorkspace(workspace->workspace(), {firstSurface});
101
113
102 QCOMPARE(topLevelWindowModel->rowCount(), 1);
114 QCOMPARE(topLevelWindowModel->rowCount(), 1);
103 QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
115 QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
@@ -105,7 +117,7 @@ void tst_TopLevelWindowModel::secondSurfaceIsHidden()
105 auto secondSurface = new MirSurface;
117 auto secondSurface = new MirSurface;
106 secondSurface->m_state = Mir::HiddenState;
118 secondSurface->m_state = Mir::HiddenState;
107 application->m_surfaceList.addSurface(secondSurface);
119 application->m_surfaceList.addSurface(secondSurface);
108− Q_EMIT surfaceManager->surfaceCreated(secondSurface);
120+ Q_EMIT surfaceManager->surfacesAddedToWorkspace(workspace->workspace(), {secondSurface});
109
121
110 // still only the first surface is exposed by TopLevelWindowModel
122 // still only the first surface is exposed by TopLevelWindowModel
111 QCOMPARE(topLevelWindowModel->rowCount(), 1);
123 QCOMPARE(topLevelWindowModel->rowCount(), 1);
tests/qmltests/ApplicationMenuDataLoader.qml+3−6
@@ -22,19 +22,16 @@ import Unity.Indicators 0.1 as Indicators
22
22
23Object {
23Object {
24
24
25− property alias surfaceManager: sMgrHandler.target
26−
27 Connections {
25 Connections {
28− id: sMgrHandler
29− target: null
26+ target: SurfaceManager
30 onSurfaceCreated: {
27 onSurfaceCreated: {
31 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
28 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
32
29
33 ApplicationMenuRegistry.RegisterSurfaceMenu(surface.persistentId, fakeMenuPath, fakeMenuPath, ":1");
30 ApplicationMenuRegistry.RegisterSurfaceMenu(surface.persistentId, fakeMenuPath, fakeMenuPath, ":1");
34 Indicators.UnityMenuModelCache.setCachedModelData(fakeMenuPath, generateTestData(4, 3, 2, 3, "menu"));
31 Indicators.UnityMenuModelCache.setCachedModelData(fakeMenuPath, generateTestData(4, 3, 2, 3, "menu"));
35 }
32 }
36− onSurfaceDestroyed: {
33+ onSurfaceRemoved: {
37− ApplicationMenuRegistry.UnregisterSurfaceMenu(persistentSurfaceId, "/app");
34+ ApplicationMenuRegistry.UnregisterSurfaceMenu(surface.persistentId, "/app");
38 }
35 }
39 }
36 }
40
37
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+2−2
@@ -24,6 +24,7 @@ import Unity.Test 0.1
24import Utils 0.1
24import Utils 0.1
25
25
26import "../../../qml/ApplicationMenus"
26import "../../../qml/ApplicationMenus"
27+import "../../../qml/Components/PanelState"
27import ".."
28import ".."
28
29
29Item {
30Item {
@@ -42,10 +43,8 @@ Item {
42 value: false
43 value: false
43 }
44 }
44
45
45− SurfaceManager { id: sMgr }
46 ApplicationMenuDataLoader {
46 ApplicationMenuDataLoader {
47 id: appMenuData
47 id: appMenuData
48− surfaceManager: sMgr
49 }
48 }
50
49
51 Rectangle {
50 Rectangle {
@@ -67,6 +66,7 @@ Item {
67 id: menuBackend
66 id: menuBackend
68 modelData: null
67 modelData: null
69 }
68 }
69+ panelState: PanelState {}
70 }
70 }
71 }
71 }
72
72
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml+2
@@ -24,6 +24,7 @@ import Unity.Test 0.1
24import Utils 0.1
24import Utils 0.1
25
25
26import "../../../qml/ApplicationMenus"
26import "../../../qml/ApplicationMenus"
27+import "../../../qml/Components/PanelState"
27import ".."
28import ".."
28
29
29Item {
30Item {
@@ -59,6 +60,7 @@ Item {
59 }}
60 }}
60 ]
61 ]
61 }
62 }
63+ panelState: PanelState {}
62 }
64 }
63 }
65 }
64
66
tests/qmltests/CMakeLists.txt+1
@@ -5,6 +5,7 @@ add_unity8_qmltest(. OrientedShell)
5add_unity8_qmltest(. DisabledScreenNotice)
5add_unity8_qmltest(. DisabledScreenNotice)
6add_unity8_qmltest(. Shell)
6add_unity8_qmltest(. Shell)
7add_unity8_qmltest(. ShellWithPin)
7add_unity8_qmltest(. ShellWithPin)
8+add_unity8_qmltest(. ShellApplication)
8add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest_data(. EdgeBarrierControls)
10add_unity8_qmltest_data(. EdgeBarrierControls)
10add_unity8_qmltest_data(. ApplicationMenuDataLoader)
11add_unity8_qmltest_data(. ApplicationMenuDataLoader)
tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2
@@ -39,11 +39,11 @@ Rectangle {
39
39
40 SignalSpy {
40 SignalSpy {
41 id: mouseEventSpy1
41 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
43 }
43 }
44 SignalSpy {
44 SignalSpy {
45 id: mouseEventSpy2
45 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
47 }
47 }
48
48
49 UnityTestCase {
49 UnityTestCase {
tests/qmltests/Panel/tst_ActiveCallHint.qml−2
@@ -41,8 +41,6 @@ Item {
41 elapsedTimerRunning: true
41 elapsedTimerRunning: true
42 }
42 }
43
43
44− SurfaceManager {}
45−
46 ActiveCallHint {
44 ActiveCallHint {
47 id: callHint
45 id: callHint
48 anchors {
46 anchors {
tests/qmltests/Panel/tst_Panel.qml+39−15
@@ -42,10 +42,10 @@ PanelTest {
42 value: keyboardAttached.checked
42 value: keyboardAttached.checked
43 }
43 }
44
44
45− SurfaceManager { id: sMgr }
45+ readonly property alias panelState: panel.panelState
46+
46 ApplicationMenuDataLoader {
47 ApplicationMenuDataLoader {
47 id: appMenuData
48 id: appMenuData
48− surfaceManager: sMgr
49 }
49 }
50
50
51 Component.onCompleted: {
51 Component.onCompleted: {
@@ -99,6 +99,8 @@ PanelTest {
99 model: root.indicatorsModel
99 model: root.indicatorsModel
100 hides: [ panel.applicationMenus ]
100 hides: [ panel.applicationMenus ]
101 }
101 }
102+
103+ panelState: PanelState {}
102 }
104 }
103 }
105 }
104 }
106 }
@@ -153,7 +155,7 @@ PanelTest {
153 Layout.fillWidth: true
155 Layout.fillWidth: true
154 CheckBox {
156 CheckBox {
155 id: windowControlsCB
157 id: windowControlsCB
156− onClicked: PanelState.decorationsVisible = checked
158+ onClicked: panelState.decorationsVisible = checked
157 }
159 }
158 Label {
160 Label {
159 text: "Show window decorations"
161 text: "Show window decorations"
@@ -164,7 +166,7 @@ PanelTest {
164 RowLayout {
166 RowLayout {
165 Layout.fillWidth: true
167 Layout.fillWidth: true
166 CheckBox {
168 CheckBox {
167− onClicked: PanelState.title = checked ? "Fake window title" : ""
169+ onClicked: panelState.title = checked ? "Fake window title" : ""
168 }
170 }
169 Label {
171 Label {
170 text: "Show fake window title"
172 text: "Show fake window title"
@@ -250,7 +252,7 @@ PanelTest {
250
252
251 SignalSpy {
253 SignalSpy {
252 id: windowControlButtonsSpy
254 id: windowControlButtonsSpy
253− target: PanelState
255+ target: panelState
254 signalName: "closeClicked"
256 signalName: "closeClicked"
255 }
257 }
256
258
@@ -260,8 +262,8 @@ PanelTest {
260 panel.fullscreenMode = false;
262 panel.fullscreenMode = false;
261 callManager.foregroundCall = null;
263 callManager.foregroundCall = null;
262
264
263− PanelState.title = "";
265+ panelState.title = "";
264− PanelState.decorationsVisible = false;
266+ panelState.decorationsVisible = false;
265
267
266 // Put the mouse somewhere neutral so it doesn't hover over things
268 // Put the mouse somewhere neutral so it doesn't hover over things
267 // and mess up the test
269 // and mess up the test
@@ -462,7 +464,7 @@ PanelTest {
462 }
464 }
463
465
464 function test_hint(data) {
466 function test_hint(data) {
465− PanelState.title = "Fake Title"
467+ panelState.title = "Fake Title"
466 panel.fullscreenMode = data.fullscreen;
468 panel.fullscreenMode = data.fullscreen;
467 callManager.foregroundCall = data.call;
469 callManager.foregroundCall = data.call;
468
470
@@ -504,7 +506,7 @@ PanelTest {
504 // menus, first by running the hint animation, then after dragging down will
506 // menus, first by running the hint animation, then after dragging down will
505 // expose more of the panel. Releasing the touch will complete the show.
507 // expose more of the panel. Releasing the touch will complete the show.
506 function test_drag_applicationMenu_down_shows_menu(data) {
508 function test_drag_applicationMenu_down_shows_menu(data) {
507− PanelState.title = "Fake Title";
509+ panelState.title = "Fake Title";
508 panel.fullscreenMode = data.fullscreen;
510 panel.fullscreenMode = data.fullscreen;
509 callManager.foregroundCall = data.call;
511 callManager.foregroundCall = data.call;
510
512
@@ -596,7 +598,7 @@ PanelTest {
596 }
598 }
597
599
598 function test_darkenedAreaEatsAllApplicationMenuEvents() {
600 function test_darkenedAreaEatsAllApplicationMenuEvents() {
599− PanelState.title = "Fake Title"
601+ panelState.title = "Fake Title"
600
602
601 // The center of the area not covered by the indicators menu
603 // The center of the area not covered by the indicators menu
602 // Ie, the visible darkened area behind the menu
604 // Ie, the visible darkened area behind the menu
@@ -717,7 +719,7 @@ PanelTest {
717 var windowControlArea = findChild(panel, "windowControlArea");
719 var windowControlArea = findChild(panel, "windowControlArea");
718 verify(windowControlArea, "Window control area should have been created in windowed mode")
720 verify(windowControlArea, "Window control area should have been created in windowed mode")
719
721
720− PanelState.decorationsVisible = true;
722+ panelState.decorationsVisible = true;
721 // click in very topleft corner and verify the close button got clicked too
723 // click in very topleft corner and verify the close button got clicked too
722 mouseMove(panel, 0, 0);
724 mouseMove(panel, 0, 0);
723 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
725 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
@@ -779,8 +781,26 @@ PanelTest {
779 }
781 }
780 }
782 }
781
783
782− function test_windowedApplicationMenuBarShowOnMouseHover() {
784+ function test_stagedApplicationMenuBarShowOnMouseHover() {
785+ panelState.title = "Fake Title";
786+ panel.mode = "staged";
787+ mouseEmulation.checked = false;
788+
789+ var appTitle = findChild(panel, "panelTitle"); verify(appTitle);
790+ var appMenuRow = findChild(panel.applicationMenus, "panelRow"); verify(appMenuRow);
791+ var appMenuBar = findChild(panel, "menuBar"); verify(appMenuBar);
792+
793+ tryCompare(appTitle, "visible", true, undefined, "App title should be visible");
794+ tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should not be visible");
795+
796+ mouseMove(panel, panel.width/2, panel.panelHeight);
797+
798+ tryCompare(appTitle, "visible", false, undefined, "App title should not be visible on mouse hover");
799+ tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover");
800+ }
801+
802+ function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
783− PanelState.title = "Fake Title";
803+ panelState.title = "Fake Title";
784 panel.mode = "windowed";
804 panel.mode = "windowed";
785 mouseEmulation.checked = false;
805 mouseEmulation.checked = false;
786
806
@@ -794,6 +814,10 @@ PanelTest {
794 mouseMove(panel, panel.width/2, panel.panelHeight);
814 mouseMove(panel, panel.width/2, panel.panelHeight);
795
815
796 waitForRendering(panel);
816 waitForRendering(panel);
817+ tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
818+ tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
819+
820+ panelState.decorationsVisible = true;
797
821
798 var appMenuBarLoader = findChild(panel, "menuBarLoader")
822 var appMenuBarLoader = findChild(panel, "menuBarLoader")
799 tryVerify(function() {return appMenuBarLoader.item});
823 tryVerify(function() {return appMenuBarLoader.item});
@@ -844,7 +868,7 @@ PanelTest {
844
868
845 var indicatorsBar = findChild(panel.applicationMenus, "indicatorsBar");
869 var indicatorsBar = findChild(panel.applicationMenus, "indicatorsBar");
846
870
847− PanelState.title = "Fake Title";
871+ panelState.title = "Fake Title"
848 waitForRendering(panel);
872 waitForRendering(panel);
849 pullDownApplicationsMenu(0 /*xPos*/);
873 pullDownApplicationsMenu(0 /*xPos*/);
850 compare(aboutToShowCalledSpy.count, 1);
874 compare(aboutToShowCalledSpy.count, 1);
@@ -878,7 +902,7 @@ PanelTest {
878
902
879 var indicatorsBar = findChild(panel.applicationMenus, "indicatorsBar");
903 var indicatorsBar = findChild(panel.applicationMenus, "indicatorsBar");
880
904
881− PanelState.title = "Fake Title"
905+ panelState.title = "Fake Title"
882 pullDownApplicationsMenu(0 /*xPos*/);
906 pullDownApplicationsMenu(0 /*xPos*/);
883
907
884 tryCompare(indicatorsBar, "currentItemIndex", 0);
908 tryCompare(indicatorsBar, "currentItemIndex", 0);
tests/qmltests/Stage/tst_ApplicationWindow.qml+10−44
@@ -37,8 +37,6 @@ Rectangle {
37 }
37 }
38 property QtObject fakeApplication: null
38 property QtObject fakeApplication: null
39
39
40− SurfaceManager{}
41−
42 Loader {
40 Loader {
43 id: applicationWindowLoader
41 id: applicationWindowLoader
44 focus: true
42 focus: true
@@ -247,13 +245,12 @@ Rectangle {
247 function test_showSplashUntilAppFullyInit_data() {
245 function test_showSplashUntilAppFullyInit_data() {
248 return [
246 return [
249 {tag: "state=Running then create surface", swapInitOrder: false},
247 {tag: "state=Running then create surface", swapInitOrder: false},
250−
251 {tag: "create surface then state=Running", swapInitOrder: true},
248 {tag: "create surface then state=Running", swapInitOrder: true},
252 ]
249 ]
253 }
250 }
254
251
255 function test_showSplashUntilAppFullyInit() {
252 function test_showSplashUntilAppFullyInit() {
256− verify(stateGroup.state === "splashScreen");
253+ verify(stateGroup.state === "splash");
257
254
258 if (data.swapInitOrder) {
255 if (data.swapInitOrder) {
259 surfaceCheckbox.checked = true;
256 surfaceCheckbox.checked = true;
@@ -261,7 +258,7 @@ Rectangle {
261 setApplicationState(appRunning);
258 setApplicationState(appRunning);
262 }
259 }
263
260
264− verify(stateGroup.state === "splashScreen");
261+ verify(stateGroup.state === "splash");
265
262
266 if (data.swapInitOrder) {
263 if (data.swapInitOrder) {
267 setApplicationState(appRunning);
264 setApplicationState(appRunning);
@@ -286,27 +283,8 @@ Rectangle {
286 waitUntilTransitionsEnd(stateGroup);
283 waitUntilTransitionsEnd(stateGroup);
287 }
284 }
288
285
289− function test_killedAppShowsScreenshot() {
290− surfaceCheckbox.checked = true;
291− setApplicationState(appRunning);
292− tryCompare(stateGroup, "state", "surface");
293−
294− setApplicationState(appSuspended);
295−
296− verify(stateGroup.state === "surface");
297− verify(fakeApplication.surface !== null);
298−
299− // kill it!
300− surfaceCheckbox.checked = false;
301− setApplicationState(appStopped);
302−
303− tryCompare(stateGroup, "state", "screenshot");
304− tryCompare(fakeApplication.surfaceList, "count", 0);
305− }
306−
307 function test_restartApp() {
286 function test_restartApp() {
308− var screenshotImage = findChild(applicationWindow, "screenshotImage");
287+ tryCompare(stateGroup, "state", "splash");
309−
310 surfaceCheckbox.checked = true;
288 surfaceCheckbox.checked = true;
311 setApplicationState(appRunning);
289 setApplicationState(appRunning);
312 tryCompare(stateGroup, "state", "surface");
290 tryCompare(stateGroup, "state", "surface");
@@ -318,26 +296,23 @@ Rectangle {
318 surfaceCheckbox.checked = false;
296 surfaceCheckbox.checked = false;
319 setApplicationState(appStopped);
297 setApplicationState(appStopped);
320
298
321− tryCompare(stateGroup, "state", "screenshot");
322 waitUntilTransitionsEnd(stateGroup);
299 waitUntilTransitionsEnd(stateGroup);
323− tryCompare(applicationWindow, "surface", null);
300+ tryCompare(stateGroup, "state", "surface");
324
301
325 // and restart it
302 // and restart it
326 setApplicationState(appStarting);
303 setApplicationState(appStarting);
327
304
328 waitUntilTransitionsEnd(stateGroup);
305 waitUntilTransitionsEnd(stateGroup);
329− verify(stateGroup.state === "screenshot");
306+ verify(stateGroup.state === "surface");
330− verify(applicationWindow.surface === null);
331
307
332 setApplicationState(appRunning);
308 setApplicationState(appRunning);
333
309
334 waitUntilTransitionsEnd(stateGroup);
310 waitUntilTransitionsEnd(stateGroup);
335− verify(stateGroup.state === "screenshot");
311+ verify(stateGroup.state === "surface");
336
312
337 surfaceCheckbox.checked = true;
313 surfaceCheckbox.checked = true;
338
314
339 tryCompare(stateGroup, "state", "surface");
315 tryCompare(stateGroup, "state", "surface");
340− tryCompare(screenshotImage, "status", Image.Null);
341 }
316 }
342
317
343 function test_appCrashed() {
318 function test_appCrashed() {
@@ -345,13 +320,15 @@ Rectangle {
345 setApplicationState(appRunning);
320 setApplicationState(appRunning);
346 tryCompare(stateGroup, "state", "surface");
321 tryCompare(stateGroup, "state", "surface");
347 waitUntilTransitionsEnd(stateGroup);
322 waitUntilTransitionsEnd(stateGroup);
323+ var surface = applicationWindow.surface;
348
324
349 // oh, it crashed...
325 // oh, it crashed...
350 surfaceCheckbox.checked = false;
326 surfaceCheckbox.checked = false;
351 setApplicationState(appStopped);
327 setApplicationState(appStopped);
352
328
329+ waitUntilTransitionsEnd(stateGroup);
353− tryCompare(stateGroup, "state", "screenshot");
330+ tryCompare(stateGroup, "state", "surface");
354− tryCompare(applicationWindow, "surface", null);
331+ tryCompare(applicationWindow, "surface", surface);
355 }
332 }
356
333
357 function test_keepSurfaceWhileInvisible() {
334 function test_keepSurfaceWhileInvisible() {
@@ -395,17 +372,6 @@ Rectangle {
395 verify(surfaceItem.touchReleaseCount === 1);
372 verify(surfaceItem.touchReleaseCount === 1);
396 }
373 }
397
374
398− function test_showNothingOnSuddenSurfaceLoss() {
399− surfaceCheckbox.checked = true;
400− setApplicationState(appRunning);
401− tryCompare(stateGroup, "state", "surface");
402− waitUntilTransitionsEnd(stateGroup);
403−
404− applicationWindow.surface = null;
405−
406− tryCompare(stateGroup, "state", "void");
407− }
408−
409 function test_surfaceActiveFocusFollowsAppWindowInterative() {
375 function test_surfaceActiveFocusFollowsAppWindowInterative() {
410 applicationWindow.interactive = false;
376 applicationWindow.interactive = false;
411 applicationWindow.interactive = true;
377 applicationWindow.interactive = true;
tests/qmltests/Stage/tst_DecoratedWindow.qml+5−3
@@ -46,10 +46,12 @@ Rectangle {
46 value: false
46 value: false
47 }
47 }
48
48
49− SurfaceManager { id: sMgr }
50 ApplicationMenuDataLoader {
49 ApplicationMenuDataLoader {
51 id: appMenuData
50 id: appMenuData
52− surfaceManager: sMgr
51+ }
52+
53+ PanelState {
54+ id: panelState
53 }
55 }
54
56
55 Item {
57 Item {
@@ -99,7 +101,7 @@ Rectangle {
99 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
101 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
100
102
101 Binding {
103 Binding {
102− target: PanelState
104+ target: panelState
103 property: "focusedPersistentSurfaceId"
105 property: "focusedPersistentSurfaceId"
104 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
106 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105 }
107 }
tests/qmltests/Stage/tst_DesktopStage.qml+7−13
@@ -63,17 +63,11 @@ Item {
63 }
63 }
64 }
64 }
65
65
66− SurfaceManager { id: sMgr }
67 ApplicationMenuDataLoader {
66 ApplicationMenuDataLoader {
68 id: appMenuData
67 id: appMenuData
69− surfaceManager: sMgr
70 }
68 }
71
69
72− TopLevelWindowModel {
70+ readonly property var topSurfaceList: WorkspaceManager.activeWorkspace.windowModel
73− id: topSurfaceList
74− applicationManager: ApplicationManager
75− surfaceManager: sMgr
76− }
77
71
78 Loader {
72 Loader {
79 id: stageLoader
73 id: stageLoader
@@ -103,11 +97,12 @@ Item {
103 availableDesktopArea: availableDesktopAreaItem
97 availableDesktopArea: availableDesktopAreaItem
104 interactive: true
98 interactive: true
105 mode: "windowed"
99 mode: "windowed"
100+ panelState: PanelState {}
106
101
107 Item {
102 Item {
108 id: availableDesktopAreaItem
103 id: availableDesktopAreaItem
109 anchors.fill: parent
104 anchors.fill: parent
110− anchors.topMargin: PanelState.panelHeight
105+ anchors.topMargin: parent.panelState.panelHeight
111 }
106 }
112 }
107 }
113 }
108 }
@@ -180,6 +175,7 @@ Item {
180
175
181 stage: stageLoader.status === Loader.Ready ? stageLoader.item : null
176 stage: stageLoader.status === Loader.Ready ? stageLoader.item : null
182 topLevelSurfaceList: topSurfaceList
177 topLevelSurfaceList: topSurfaceList
178+ property var panelState: stage ? stage.panelState : null
183
179
184 function init() {
180 function init() {
185 // wait until unity8-dash is up and running.
181 // wait until unity8-dash is up and running.
@@ -562,8 +558,6 @@ Item {
562 var gmailDelegate = startApplication("gmail-webapp");
558 var gmailDelegate = startApplication("gmail-webapp");
563 verify(gmailDelegate);
559 verify(gmailDelegate);
564
560
565− wait(2000)
566−
567 var gmailMaximizeButton = findChild(gmailDelegate, "maximizeWindowButton");
561 var gmailMaximizeButton = findChild(gmailDelegate, "maximizeWindowButton");
568 verify(gmailMaximizeButton);
562 verify(gmailMaximizeButton);
569 mouseClick(gmailMaximizeButton);
563 mouseClick(gmailMaximizeButton);
@@ -638,19 +632,19 @@ Item {
638 maximizeDelegate(facebookAppDelegate);
632 maximizeDelegate(facebookAppDelegate);
639
633
640 // verify the drop shadow is still not visible
634 // verify the drop shadow is still not visible
641− verify(PanelState.dropShadow == false);
635+ verify(panelState.dropShadow == false);
642
636
643 // start a foreground app, not maximized
637 // start a foreground app, not maximized
644 var dialerAppDelegate = startApplication("dialer-app");
638 var dialerAppDelegate = startApplication("dialer-app");
645
639
646 // verify the drop shadow becomes visible
640 // verify the drop shadow becomes visible
647− tryCompareFunction(function() { return PanelState.dropShadow; }, true);
641+ tryCompareFunction(function() { return panelState.dropShadow; }, true);
648
642
649 // close the maximized app
643 // close the maximized app
650 ApplicationManager.stopApplication("facebook-webapp");
644 ApplicationManager.stopApplication("facebook-webapp");
651
645
652 // verify the drop shadow is gone
646 // verify the drop shadow is gone
653− tryCompare(PanelState, "dropShadow", false);
647+ tryCompare(panelState, "dropShadow", false);
654 }
648 }
655
649
656 function test_threeFingerTapShowsWindowControls_data() {
650 function test_threeFingerTapShowsWindowControls_data() {
tests/qmltests/Stage/tst_PhoneStage.qml+7−8
@@ -19,6 +19,7 @@ import QtTest 1.0
19import Unity.Test 0.1 as UT
19import Unity.Test 0.1 as UT
20import ".."
20import ".."
21import "../../../qml/Components"
21import "../../../qml/Components"
22+import "../../../qml/Components/PanelState"
22import "../../../qml/Stage"
23import "../../../qml/Stage"
23import Ubuntu.Components 1.3
24import Ubuntu.Components 1.3
24import Unity.Application 0.1
25import Unity.Application 0.1
@@ -31,10 +32,10 @@ Item {
31
32
32 property var greeter: { fullyShown: true }
33 property var greeter: { fullyShown: true }
33
34
34− SurfaceManager { id: sMgr }
35+ readonly property var topLevelSurfaceList: WorkspaceManager.activeWorkspace.windowModel
36+
35 ApplicationMenuDataLoader {
37 ApplicationMenuDataLoader {
36 id: appMenuData
38 id: appMenuData
37− surfaceManager: sMgr
38 }
39 }
39
40
40 Stage {
41 Stage {
@@ -47,19 +48,17 @@ Item {
47 orientations: Orientations {}
48 orientations: Orientations {}
48 applicationManager: ApplicationManager
49 applicationManager: ApplicationManager
49 mode: "staged"
50 mode: "staged"
50− topLevelSurfaceList: TopLevelWindowModel {
51− id: topLevelSurfaceList
51+ topLevelSurfaceList: root.topLevelSurfaceList
52− applicationManager: ApplicationManager
53− surfaceManager: sMgr
54− }
55 availableDesktopArea: availableDesktopAreaItem
52 availableDesktopArea: availableDesktopAreaItem
56 Item {
53 Item {
57 id: availableDesktopAreaItem
54 id: availableDesktopAreaItem
58 anchors.fill: parent
55 anchors.fill: parent
59 }
56 }
57+
60 Component.onCompleted: {
58 Component.onCompleted: {
61 ApplicationManager.startApplication("unity8-dash");
59 ApplicationManager.startApplication("unity8-dash");
62 }
60 }
61+ panelState: PanelState {}
63 }
62 }
64
63
65 Flickable {
64 Flickable {
@@ -305,7 +304,6 @@ Item {
305
304
306 performEdgeSwipeToShowAppSpread();
305 performEdgeSwipeToShowAppSpread();
307
306
308− print("tapping", selectedAppDeleage.appId, selectedAppDeleage.visible)
309 if (selectedAppDeleage.x > stage.width - units.gu(5)) {
307 if (selectedAppDeleage.x > stage.width - units.gu(5)) {
310 touchFlick(stage, stage.width - units.gu(2), stage.height / 2, units.gu(2), stage.height / 2, true, true, units.gu(2), 10)
308 touchFlick(stage, stage.width - units.gu(2), stage.height / 2, units.gu(2), stage.height / 2, true, true, units.gu(2), 10)
311 }
309 }
@@ -534,6 +532,7 @@ Item {
534 performEdgeSwipeToShowAppSpread();
532 performEdgeSwipeToShowAppSpread();
535
533
536 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
534 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
535+ verify(appDelegate);
537 var dragArea = findChild(appDelegate, "dragArea");
536 var dragArea = findChild(appDelegate, "dragArea");
538 verify(dragArea);
537 verify(dragArea);
539 tryCompare(dragArea, "closeable", true);
538 tryCompare(dragArea, "closeable", true);
tests/qmltests/Stage/tst_SurfaceContainer.qml−2
@@ -51,8 +51,6 @@ Rectangle {
51 }
51 }
52 }
52 }
53
53
54− SurfaceManager {}
55−
56 Loader {
54 Loader {
57 id: surfaceContainerLoader
55 id: surfaceContainerLoader
58 focus: true
56 focus: true
tests/qmltests/Stage/tst_TabletStage.qml+65−22
@@ -26,6 +26,7 @@ import WindowManager 1.0
26import ".."
26import ".."
27import "../../../qml/Stage"
27import "../../../qml/Stage"
28import "../../../qml/Components"
28import "../../../qml/Components"
29+import "../../../qml/Components/PanelState"
29
30
30Rectangle {
31Rectangle {
31 id: root
32 id: root
@@ -35,10 +36,10 @@ Rectangle {
35
36
36 property var greeter: { fullyShown: true }
37 property var greeter: { fullyShown: true }
37
38
38− SurfaceManager { id: sMgr }
39+ readonly property var topLevelSurfaceList: WorkspaceManager.activeWorkspace.windowModel
40+
39 ApplicationMenuDataLoader {
41 ApplicationMenuDataLoader {
40 id: appMenuData
42 id: appMenuData
41− surfaceManager: sMgr
42 }
43 }
43
44
44 Stage {
45 Stage {
@@ -56,11 +57,8 @@ Rectangle {
56 focus: true
57 focus: true
57 mode: "stagedWithSideStage"
58 mode: "stagedWithSideStage"
58 applicationManager: ApplicationManager
59 applicationManager: ApplicationManager
59− topLevelSurfaceList: TopLevelWindowModel {
60+ topLevelSurfaceList: root.topLevelSurfaceList
60− id: topLevelSurfaceList
61+ panelState: PanelState {}
61− applicationManager: ApplicationManager
62− surfaceManager: sMgr
63− }
64 availableDesktopArea: availableDesktopAreaItem
62 availableDesktopArea: availableDesktopAreaItem
65 Item {
63 Item {
66 id: availableDesktopAreaItem
64 id: availableDesktopAreaItem
@@ -151,7 +149,6 @@ Rectangle {
151 name: "TabletStage"
149 name: "TabletStage"
152 when: windowShown
150 when: windowShown
153
151
154− readonly property alias topSurfaceList: stage.topLevelSurfaceList
155 property Item sideStage: stage ? findChild(stage, "sideStage") : null
152 property Item sideStage: stage ? findChild(stage, "sideStage") : null
156
153
157 function init() {
154 function init() {
@@ -165,6 +162,7 @@ Rectangle {
165
162
166 sideStage.hideNow()
163 sideStage.hideNow()
167 tryCompare(sideStage, "x", stage.width)
164 tryCompare(sideStage, "x", stage.width)
165+
168 }
166 }
169
167
170 function cleanup() {
168 function cleanup() {
@@ -226,7 +224,7 @@ Rectangle {
226 tryCompare(stage, "state", "spread");
224 tryCompare(stage, "state", "spread");
227 }
225 }
228
226
229− function swipeSurfaceUpwards(surfaceId) {
227+ function swipeSurfaceDownwards(surfaceId) {
230 var appWindow = findAppWindowForSurfaceId(surfaceId);
228 var appWindow = findAppWindowForSurfaceId(surfaceId);
231 verify(appWindow);
229 verify(appWindow);
232
230
@@ -234,7 +232,7 @@ Rectangle {
234 // to not be covered by other surfaces when they're all being shown in the spread
232 // to not be covered by other surfaces when they're all being shown in the spread
235 touchFlick(appWindow,
233 touchFlick(appWindow,
236 appWindow.width * 0.1, appWindow.height / 2,
234 appWindow.width * 0.1, appWindow.height / 2,
237− appWindow.width * 0.1, -appWindow.height / 2);
235+ appWindow.width * 0.1, appWindow.height * 1.5);
238 }
236 }
239
237
240 function dragToSideStage(surfaceId) {
238 function dragToSideStage(surfaceId) {
@@ -308,6 +306,8 @@ Rectangle {
308 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
306 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
309
307
310 var webbrowserSurfaceId = launchApp("morph");
308 var webbrowserSurfaceId = launchApp("morph");
309+ webbrowserCheckBox.checked = true;
310+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
311 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
311 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
312 verify(webbrowserDelegate);
312 verify(webbrowserDelegate);
313 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
313 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
@@ -317,6 +317,8 @@ Rectangle {
317 tryCompare(webbrowserWindow.surface, "activeFocus", true);
317 tryCompare(webbrowserWindow.surface, "activeFocus", true);
318
318
319 var dialerSurfaceId = launchApp("dialer");
319 var dialerSurfaceId = launchApp("dialer");
320+ dialerCheckBox.checked = true;
321+ waitUntilAppSurfaceShowsUp(dialerSurfaceId);
320
322
321 var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
323 var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
322 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
324 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
@@ -347,6 +349,8 @@ Rectangle {
347 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
349 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
348
350
349 var dialerSurfaceId = launchApp("dialer");
351 var dialerSurfaceId = launchApp("dialer");
352+ dialerCheckBox.checked = true;
353+ waitUntilAppSurfaceShowsUp(dialerSurfaceId);
350
354
351 performEdgeSwipeToShowAppSpread();
355 performEdgeSwipeToShowAppSpread();
352
356
@@ -356,7 +360,7 @@ Rectangle {
356 compare(appDelegate.stage, ApplicationInfoInterface.SideStage);
360 compare(appDelegate.stage, ApplicationInfoInterface.SideStage);
357 tryCompare(dragArea, "closeable", true);
361 tryCompare(dragArea, "closeable", true);
358
362
359− swipeSurfaceUpwards(dialerSurfaceId);
363+ swipeSurfaceDownwards(dialerSurfaceId);
360
364
361 // Check that dialer-app has been closed
365 // Check that dialer-app has been closed
362
366
@@ -367,10 +371,16 @@ Rectangle {
367 tryCompareFunction(function() {
371 tryCompareFunction(function() {
368 return ApplicationManager.findApplication(dialerCheckBox.appId);
372 return ApplicationManager.findApplication(dialerCheckBox.appId);
369 }, null);
373 }, null);
374+
375+ stage.closeSpread();
370 }
376 }
371
377
372 function test_suspendsAndResumesAppsInMainStage() {
378 function test_suspendsAndResumesAppsInMainStage() {
373 var webbrowserSurfaceId = launchApp("morph");
379 var webbrowserSurfaceId = launchApp("morph");
380+
381+ webbrowserCheckBox.checked = true;
382+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
383+
374 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
384 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
375 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
385 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
376 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
386 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
@@ -378,6 +388,9 @@ Rectangle {
378 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
388 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
379
389
380 var gallerySurfaceId = launchApp("gallery");
390 var gallerySurfaceId = launchApp("gallery");
391+ galleryCheckBox.checked = true;
392+ waitUntilAppSurfaceShowsUp(gallerySurfaceId);
393+
381 var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
394 var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
382 var galleryDelegate = findChild(stage, "appDelegate_" + gallerySurfaceId);
395 var galleryDelegate = findChild(stage, "appDelegate_" + gallerySurfaceId);
383 compare(galleryDelegate.stage, ApplicationInfoInterface.MainStage);
396 compare(galleryDelegate.stage, ApplicationInfoInterface.MainStage);
@@ -406,13 +419,20 @@ Rectangle {
406 // launch two main stage apps
419 // launch two main stage apps
407 // gallery will be on foreground and webbrowser on background
420 // gallery will be on foreground and webbrowser on background
408
421
422+
409 var webbrowserSurfaceId = launchApp("morph");
423 var webbrowserSurfaceId = launchApp("morph");
424+
425+ webbrowserCheckBox.checked = true;
426+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
427+
410 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
428 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
411 verify(webbrowserDelegate);
429 verify(webbrowserDelegate);
412 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
430 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
413 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
431 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
414
432
415 var gallerySurfaceId = launchApp("gallery");
433 var gallerySurfaceId = launchApp("gallery");
434+ galleryCheckBox.checked = true;
435+ waitUntilAppSurfaceShowsUp(gallerySurfaceId);
416 var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
436 var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
417 var galleryDelegate = findChild(stage, "appDelegate_" + gallerySurfaceId);
437 var galleryDelegate = findChild(stage, "appDelegate_" + gallerySurfaceId);
418 compare(galleryDelegate.stage, ApplicationInfoInterface.MainStage);
438 compare(galleryDelegate.stage, ApplicationInfoInterface.MainStage);
@@ -423,12 +443,16 @@ Rectangle {
423 // facebook will be on foreground and dialer on background
443 // facebook will be on foreground and dialer on background
424
444
425 var dialerSurfaceId = launchApp("dialer");
445 var dialerSurfaceId = launchApp("dialer");
446+ dialerCheckBox.checked = true;
447+ waitUntilAppSurfaceShowsUp(dialerSurfaceId);
426 var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
448 var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
427 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
449 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
428 compare(dialerDelegate.stage, ApplicationInfoInterface.SideStage);
450 compare(dialerDelegate.stage, ApplicationInfoInterface.SideStage);
429
451
430 var facebookSurfaceId = launchApp("facebook");
452 var facebookSurfaceId = launchApp("facebook");
431− var facebookApp = ApplicationManager.findApplication(facebookCheckBox.appId);
453+ facebookCheckBox.checked = true;
454+ waitUntilAppSurfaceShowsUp(facebookSurfaceId);
455+
432 var facebookDelegate = findChild(stage, "appDelegate_" + facebookSurfaceId);
456 var facebookDelegate = findChild(stage, "appDelegate_" + facebookSurfaceId);
433 compare(facebookDelegate.stage, ApplicationInfoInterface.SideStage);
457 compare(facebookDelegate.stage, ApplicationInfoInterface.SideStage);
434
458
@@ -461,12 +485,16 @@ Rectangle {
461 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
485 WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
462
486
463 var webbrowserSurfaceId = launchApp("morph");
487 var webbrowserSurfaceId = launchApp("morph");
488+ webbrowserCheckBox.checked = true;
489+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
464 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
490 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
465 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
491 var webbrowserDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
466 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
492 compare(webbrowserDelegate.stage, ApplicationInfoInterface.MainStage);
467
493
468 var dialerSurfaceId = launchApp("dialer");
494 var dialerSurfaceId = launchApp("dialer");
469− var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
495+ dialerCheckBox.checked = true;
496+ waitUntilAppSurfaceShowsUp(dialerSurfaceId);
497+
470 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
498 var dialerDelegate = findChild(stage, "appDelegate_" + dialerSurfaceId);
471 compare(dialerDelegate.stage, ApplicationInfoInterface.SideStage);
499 compare(dialerDelegate.stage, ApplicationInfoInterface.SideStage);
472
500
@@ -539,7 +567,7 @@ Rectangle {
539 tryCompare(stagesPriv, "mainStageAppId", "facebook-webapp");
567 tryCompare(stagesPriv, "mainStageAppId", "facebook-webapp");
540 tryCompare(stagesPriv, "sideStageAppId", "");
568 tryCompare(stagesPriv, "sideStageAppId", "");
541
569
542− var appSurfaceId = topSurfaceList.nextId;
570+ var appSurfaceId = topLevelSurfaceList.nextId;
543 var app = ApplicationManager.startApplication(data.appId);
571 var app = ApplicationManager.startApplication(data.appId);
544 waitUntilAppSurfaceShowsUp(appSurfaceId);
572 waitUntilAppSurfaceShowsUp(appSurfaceId);
545
573
@@ -566,6 +594,8 @@ Rectangle {
566 tryCompare(stagesPriv, "sideStageAppId", "");
594 tryCompare(stagesPriv, "sideStageAppId", "");
567
595
568 var webbrowserSurfaceId = launchApp("morph");
596 var webbrowserSurfaceId = launchApp("morph");
597+ webbrowserCheckBox.checked = true;
598+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
569
599
570 tryCompare(stagesPriv, "mainStageAppId", data.mainStageAppId);
600 tryCompare(stagesPriv, "mainStageAppId", data.mainStageAppId);
571 tryCompare(stagesPriv, "sideStageAppId", data.sideStageAppId);
601 tryCompare(stagesPriv, "sideStageAppId", data.sideStageAppId);
@@ -591,6 +621,8 @@ Rectangle {
591 tryCompare(stagesPriv, "sideStageAppId", "");
621 tryCompare(stagesPriv, "sideStageAppId", "");
592
622
593 var webbrowserSurfaceId = launchApp("morph");
623 var webbrowserSurfaceId = launchApp("morph");
624+ webbrowserCheckBox.checked = true;
625+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
594
626
595 if (data.toStage === ApplicationInfoInterface.SideStage) {
627 if (data.toStage === ApplicationInfoInterface.SideStage) {
596 dragToSideStage(webbrowserSurfaceId);
628 dragToSideStage(webbrowserSurfaceId);
@@ -606,6 +638,8 @@ Rectangle {
606 function test_loadSideStageByDraggingFromMainStage() {
638 function test_loadSideStageByDraggingFromMainStage() {
607 sideStage.showNow();
639 sideStage.showNow();
608 var webbrowserSurfaceId = launchApp("morph")
640 var webbrowserSurfaceId = launchApp("morph")
641+ webbrowserCheckBox.checked = true;
642+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
609
643
610 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
644 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
611 verify(appDelegate);
645 verify(appDelegate);
@@ -625,6 +659,8 @@ Rectangle {
625 return WindowStateStorage.getStage(webbrowserCheckBox.appId, ApplicationInfoInterface.MainStage) === ApplicationInfoInterface.SideStage
659 return WindowStateStorage.getStage(webbrowserCheckBox.appId, ApplicationInfoInterface.MainStage) === ApplicationInfoInterface.SideStage
626 }, true);
660 }, true);
627 var webbrowserSurfaceId = launchApp("morph");
661 var webbrowserSurfaceId = launchApp("morph");
662+ webbrowserCheckBox.checked = true;
663+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
628
664
629 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
665 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
630 verify(appDelegate);
666 verify(appDelegate);
@@ -638,7 +674,7 @@ Rectangle {
638
674
639 /*
675 /*
640 1- Suspended app gets killed behind the scenes, causing its surface to go zombie.
676 1- Suspended app gets killed behind the scenes, causing its surface to go zombie.
641− 2- Surface gets screenshotted and removed. Its slot in the topSurfaceList remains,
677+ 2- Surface gets screenshotted and removed. Its slot in the topLevelSurfaceList remains,
642 though (so ApplicationWindow can display the screenshot in its place).
678 though (so ApplicationWindow can display the screenshot in its place).
643 3- User taps on the screenshot of the long-gone surface.
679 3- User taps on the screenshot of the long-gone surface.
644
680
@@ -653,6 +689,9 @@ Rectangle {
653
689
654 var webbrowserSurfaceId = topSurfaceList.nextId;
690 var webbrowserSurfaceId = topSurfaceList.nextId;
655 launchApp("morph");
691 launchApp("morph");
692+
693+ webbrowserCheckBox.checked = true;
694+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
656 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
695 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
657
696
658 switchToSurface(facebookSurfaceId);
697 switchToSurface(facebookSurfaceId);
@@ -665,24 +704,22 @@ Rectangle {
665 // simulate the suspended app being killed by the out-of-memory daemon
704 // simulate the suspended app being killed by the out-of-memory daemon
666 webbrowserApp.surfaceList.get(0).setLive(false);
705 webbrowserApp.surfaceList.get(0).setLive(false);
667
706
668− // wait until the surface is gone
669− tryCompare(webbrowserApp.surfaceList, "count", 0);
670− compare(topSurfaceList.surfaceAt(topSurfaceList.indexForId(webbrowserSurfaceId)), null);
671−
672 switchToSurface(webbrowserSurfaceId);
707 switchToSurface(webbrowserSurfaceId);
673
708
674 // webbrowser should have been brought to front
709 // webbrowser should have been brought to front
675− tryCompareFunction(function(){return topSurfaceList.idAt(0);}, webbrowserSurfaceId);
710+ tryCompareFunction(function(){return topLevelSurfaceList.idAt(0);}, webbrowserSurfaceId);
676
711
677 // and it should eventually get a new surface and get resumed
712 // and it should eventually get a new surface and get resumed
678− tryCompareFunction(function(){return topSurfaceList.surfaceAt(0) !== null;}, true);
713+ tryCompareFunction(function(){return topLevelSurfaceList.surfaceAt(0) !== null;}, true);
679− compare(topSurfaceList.count, 2); // still two top-level items
714+ compare(topLevelSurfaceList.count, 2); // still two top-level items
680 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
715 tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
681 compare(webbrowserApp.surfaceList.count, 1);
716 compare(webbrowserApp.surfaceList.count, 1);
682 }
717 }
683
718
684 function test_draggingSurfaceKeepsSurfaceFocus() {
719 function test_draggingSurfaceKeepsSurfaceFocus() {
685 var webbrowserSurfaceId = launchApp("morph");
720 var webbrowserSurfaceId = launchApp("morph");
721+ webbrowserCheckBox.checked = true;
722+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
686
723
687 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
724 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
688 verify(appDelegate);
725 verify(appDelegate);
@@ -699,6 +736,8 @@ Rectangle {
699 function test_switchRestoreStageOnRotation() {
736 function test_switchRestoreStageOnRotation() {
700 WindowStateStorage.saveStage(webbrowserCheckBox.appId, ApplicationInfoInterface.SideStage)
737 WindowStateStorage.saveStage(webbrowserCheckBox.appId, ApplicationInfoInterface.SideStage)
701 var webbrowserSurfaceId = launchApp("morph");
738 var webbrowserSurfaceId = launchApp("morph");
739+ webbrowserCheckBox.checked = true;
740+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
702
741
703 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
742 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
704 verify(appDelegate);
743 verify(appDelegate);
@@ -716,6 +755,8 @@ Rectangle {
716
755
717 function test_restoreSavedStageOnCloseReopen() {
756 function test_restoreSavedStageOnCloseReopen() {
718 var webbrowserSurfaceId = launchApp("morph");
757 var webbrowserSurfaceId = launchApp("morph");
758+ webbrowserCheckBox.checked = true;
759+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
719
760
720 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
761 var appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
721 verify(appDelegate);
762 verify(appDelegate);
@@ -732,6 +773,8 @@ Rectangle {
732 stage.shellOrientation = Qt.LandscapeOrientation;
773 stage.shellOrientation = Qt.LandscapeOrientation;
733
774
734 webbrowserSurfaceId = launchApp("morph");
775 webbrowserSurfaceId = launchApp("morph");
776+ webbrowserCheckBox.checked = true;
777+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
735
778
736 appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
779 appDelegate = findChild(stage, "appDelegate_" + webbrowserSurfaceId);
737 verify(appDelegate);
780 verify(appDelegate);
tests/qmltests/Stage/tst_WindowDecoration.qml−2
@@ -47,10 +47,8 @@ Item {
47 property string name: "webbrowser"
47 property string name: "webbrowser"
48 }
48 }
49
49
50− SurfaceManager { id: sMgr }
51 ApplicationMenuDataLoader {
50 ApplicationMenuDataLoader {
52 id: appMenuData
51 id: appMenuData
53− surfaceManager: sMgr
54 }
52 }
55
53
56 UnityMenuModel {
54 UnityMenuModel {
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8
@@ -19,7 +19,6 @@ import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20import Unity.Test 0.1
20import Unity.Test 0.1
21import ".."
21import ".."
22−import "../../../qml/Components/PanelState"
23import "../../../qml/Stage"
22import "../../../qml/Stage"
24import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
25import Ubuntu.Components.ListItems 1.3 as ListItem
24import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -31,12 +30,6 @@ Item {
31 height: units.gu(60)
30 height: units.gu(60)
32 width: units.gu(85)
31 width: units.gu(85)
33
32
34− Binding {
35− target: PanelState
36− property: "panelHeight"
37− value: units.gu(3)
38− }
39−
40 Component {
33 Component {
41 id: fakeWindowComponent
34 id: fakeWindowComponent
42
35
@@ -362,7 +355,7 @@ Item {
362
355
363 // Make sure it's again where we left it in normal state before destroying
356 // Make sure it's again where we left it in normal state before destroying
364 compare(fakeWindow.requestedX >= 0, true)
357 compare(fakeWindow.requestedX >= 0, true)
365− compare(fakeWindow.requestedY >= PanelState.panelHeight, true)
358+ compare(fakeWindow.requestedY >= 0, true)
366 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
359 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
367 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
360 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
368
361
tests/qmltests/Tutorial/tst_Tutorial.qml+5−23
@@ -37,20 +37,10 @@ Rectangle {
37 height: units.gu(71)
37 height: units.gu(71)
38
38
39 QtObject {
39 QtObject {
40− id: applicationArguments
40+ id: _screenWindow
41−
41+ property bool primary: true
42− function hasGeometry() {
43− return false;
44− }
45−
46− function width() {
47− return 0;
48− }
49−
50− function height() {
51− return 0;
52− }
53 }
42 }
43+ property alias screenWindow: _screenWindow
54
44
55 Telephony.CallEntry {
45 Telephony.CallEntry {
56 id: phoneCall
46 id: phoneCall
@@ -256,13 +246,7 @@ Rectangle {
256 property real halfWidth: shell ? shell.width / 2 : 0
246 property real halfWidth: shell ? shell.width / 2 : 0
257 property real halfHeight: shell ? shell.height / 2 : 0
247 property real halfHeight: shell ? shell.height / 2 : 0
258
248
259− onShellChanged: {
249+ topLevelSurfaceList: shell ? shell.topLevelSurfaceList : null;
260− if (shell) {
261− topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
262− } else {
263− topLevelSurfaceList = null;
264− }
265− }
266
250
267 function init() {
251 function init() {
268 prepareShell();
252 prepareShell();
@@ -327,9 +311,7 @@ Rectangle {
327 }
311 }
328
312
329 function ensureInputMethodSurface() {
313 function ensureInputMethodSurface() {
330− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
331− verify(surfaceManager);
332− surfaceManager.createInputMethodSurface();
314+ SurfaceManager.createInputMethodSurface();
333
315
334 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
316 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
335 }
317 }
tests/qmltests/tst_OrientedShell.qml+22−10
@@ -30,7 +30,6 @@ import Utils 0.1
30
30
31import "../../qml"
31import "../../qml"
32import "../../qml/Components"
32import "../../qml/Components"
33−import "../../qml/Components/PanelState"
34import "Stage"
33import "Stage"
35
34
36Rectangle {
35Rectangle {
@@ -82,6 +81,12 @@ Rectangle {
82 deviceFilter: InputInfo.Keyboard
81 deviceFilter: InputInfo.Keyboard
83 }
82 }
84
83
84+ QtObject {
85+ id: _screenWindow
86+ property bool primary: true
87+ }
88+ property alias screenWindow: _screenWindow
89+
85 property int physicalOrientation0
90 property int physicalOrientation0
86 property int physicalOrientation90
91 property int physicalOrientation90
87 property int physicalOrientation180
92 property int physicalOrientation180
@@ -444,8 +449,17 @@ Rectangle {
444 when: windowShown
449 when: windowShown
445
450
446 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
451 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
447− property Item shell
452+ property Item shell: null
448− property QtObject topLevelSurfaceList
453+ property QtObject topLevelSurfaceList : shell ? shell.topLevelSurfaceList : null
454+ property var panelState: undefined
455+
456+ onOrientedShellChanged: {
457+ if (orientedShell) {
458+ panelState = findInvisibleChild(orientedShell, "panelState");
459+ } else {
460+ panelState = undefined;
461+ }
462+ }
449
463
450 SignalSpy { id: signalSpy }
464 SignalSpy { id: signalSpy }
451 SignalSpy { id: signalSpy2 }
465 SignalSpy { id: signalSpy2 }
@@ -504,7 +518,6 @@ Rectangle {
504 function cleanup() {
518 function cleanup() {
505 tryCompare(shell, "waitingOnGreeter", false, 10000); // make sure greeter didn't leave us in disabled state
519 tryCompare(shell, "waitingOnGreeter", false, 10000); // make sure greeter didn't leave us in disabled state
506 shell = null;
520 shell = null;
507− topLevelSurfaceList = null;
508
521
509 tearDown();
522 tearDown();
510 }
523 }
@@ -1462,8 +1475,7 @@ Rectangle {
1462 removeTimeConstraintsFromSwipeAreas(orientedShellLoader.item);
1475 removeTimeConstraintsFromSwipeAreas(orientedShellLoader.item);
1463
1476
1464 shell = findChild(orientedShell, "shell");
1477 shell = findChild(orientedShell, "shell");
1465−
1478+ verify(shell);
1466− topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
1467 verify(topLevelSurfaceList);
1479 verify(topLevelSurfaceList);
1468
1480
1469 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
1481 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
@@ -1510,13 +1522,13 @@ Rectangle {
1510
1522
1511 switch (expectedAngle) {
1523 switch (expectedAngle) {
1512 case 0:
1524 case 0:
1513− return point.x === 0 && point.y === PanelState.panelHeight;
1525+ return point.x === 0 && point.y === panelState.panelHeight;
1514 case 90:
1526 case 90:
1515− return point.x === orientedShell.width - PanelState.panelHeight && point.y === 0;
1527+ return point.x === orientedShell.width - panelState.panelHeight && point.y === 0;
1516 case 180:
1528 case 180:
1517− return point.x === orientedShell.width && point.y === orientedShell.height - PanelState.panelHeight;
1529+ return point.x === orientedShell.width && point.y === orientedShell.height - panelState.panelHeight;
1518 default: // 270
1530 default: // 270
1519− return point.x === PanelState.panelHeight && point.y === orientedShell.height;
1531+ return point.x === panelState.panelHeight && point.y === orientedShell.height;
1520 }
1532 }
1521 }
1533 }
1522
1534
tests/qmltests/tst_Shell.qml+40−28
@@ -38,7 +38,6 @@ import Unity.Indicators 0.1 as Indicators
38
38
39import "../../qml"
39import "../../qml"
40import "../../qml/Components"
40import "../../qml/Components"
41−import "../../qml/Components/PanelState"
42import "Stage"
41import "Stage"
43
42
44Rectangle {
43Rectangle {
@@ -57,18 +56,22 @@ Rectangle {
57 id: appMenuData
56 id: appMenuData
58 }
57 }
59
58
60− property var shell: shellLoader.item ? shellLoader.item : null
59+ readonly property var shell: shellLoader.item ? shellLoader.item : null
61 onShellChanged: {
60 onShellChanged: {
62 if (shell) {
61 if (shell) {
63− topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
62+ panelState = testCase.findInvisibleChild(shell, "panelState");
64− appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
65 } else {
63 } else {
66− topLevelSurfaceList = null;
64+ panelState = null;
67− appMenuData.surfaceManager = null;
68 }
65 }
69 }
66 }
67+ property var panelState: null
68+ readonly property alias topLevelSurfaceList: testCase.topLevelSurfaceList
70
69
71− property var topLevelSurfaceList: null
70+ QtObject {
71+ id: _screenWindow
72+ property bool primary: true
73+ }
74+ property alias screenWindow: _screenWindow
72
75
73 Item {
76 Item {
74 id: shellContainer
77 id: shellContainer
@@ -259,6 +262,7 @@ Rectangle {
259 anchors { left: parent.left; right: parent.right }
262 anchors { left: parent.left; right: parent.right }
260 activeFocusOnPress: false
263 activeFocusOnPress: false
261 model: ["phone", "tablet", "desktop"]
264 model: ["phone", "tablet", "desktop"]
265+ selectedIndex: 2
262 onSelectedIndexChanged: {
266 onSelectedIndexChanged: {
263 shellLoader.state = model[selectedIndex];
267 shellLoader.state = model[selectedIndex];
264 }
268 }
@@ -272,6 +276,7 @@ Rectangle {
272 anchors { left: parent.left; right: parent.right }
276 anchors { left: parent.left; right: parent.right }
273 activeFocusOnPress: false
277 activeFocusOnPress: false
274 model: ["phone", "tablet", "desktop"]
278 model: ["phone", "tablet", "desktop"]
279+ selectedIndex: 0
275 }
280 }
276 MouseTouchEmulationCheckbox {
281 MouseTouchEmulationCheckbox {
277 id: mouseEmulation
282 id: mouseEmulation
@@ -557,6 +562,7 @@ Rectangle {
557 when: windowShown
562 when: windowShown
558
563
559 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
564 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
565+ topLevelSurfaceList: shell ? shell.topLevelSurfaceList : null
560
566
561 function initTestCase() {
567 function initTestCase() {
562 tearDown();
568 tearDown();
@@ -595,7 +601,6 @@ Rectangle {
595 waitForGreeterToStabilize();
601 waitForGreeterToStabilize();
596
602
597 // from StageTestCase
603 // from StageTestCase
598− topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
599 verify(topLevelSurfaceList);
604 verify(topLevelSurfaceList);
600 stage = findChild(shell, "stage");
605 stage = findChild(shell, "stage");
601 }
606 }
@@ -672,9 +677,7 @@ Rectangle {
672 }
677 }
673
678
674 function ensureInputMethodSurface() {
679 function ensureInputMethodSurface() {
675− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
676− verify(surfaceManager);
677− surfaceManager.createInputMethodSurface();
680+ SurfaceManager.createInputMethodSurface();
678
681
679 tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
682 tryCompareFunction(function() { return root.topLevelSurfaceList.inputMethodSurface !== null }, true);
680 }
683 }
@@ -1735,9 +1738,9 @@ Rectangle {
1735 var spreadDelegate2 = appRepeater.itemAt(2);
1738 var spreadDelegate2 = appRepeater.itemAt(2);
1736 var closeMouseArea = findChild(spreadDelegate2, "closeMouseArea");
1739 var closeMouseArea = findChild(spreadDelegate2, "closeMouseArea");
1737
1740
1738− // Move the mosue over tile 2 and verify the close button becomes visible
1741+ // Move the mouse over tile 2 and verify the close button becomes visible
1739 var x = 0;
1742 var x = 0;
1740− var y = shell.height * .5;
1743+ var y = shell.height * .6;
1741 mouseMove(shell, x, y)
1744 mouseMove(shell, x, y)
1742 while (spreadItem.highlightedIndex !== 2 && x <= 4000) {
1745 while (spreadItem.highlightedIndex !== 2 && x <= 4000) {
1743 x+=10;
1746 x+=10;
@@ -1745,6 +1748,7 @@ Rectangle {
1745 wait(10); // spin the loop so bindings get evaluated
1748 wait(10); // spin the loop so bindings get evaluated
1746 }
1749 }
1747 tryCompare(closeMouseArea, "enabled", true)
1750 tryCompare(closeMouseArea, "enabled", true)
1751+ waitForRendering(shell)
1748
1752
1749 var countBeforeClickingCloseButton = topLevelSurfaceList.count;
1753 var countBeforeClickingCloseButton = topLevelSurfaceList.count;
1750 verify(topLevelSurfaceList.indexForId(surfaceId) === 2);
1754 verify(topLevelSurfaceList.indexForId(surfaceId) === 2);
@@ -1789,7 +1793,7 @@ Rectangle {
1789
1793
1790 // Move the mouse over tile 2 and verify the highlight becomes visible
1794 // Move the mouse over tile 2 and verify the highlight becomes visible
1791 var x = 0;
1795 var x = 0;
1792− var y = shell.height * (data.tileInfo ? .9 : 0.5)
1796+ var y = shell.height * (data.tileInfo ? .9 : 0.7)
1793 mouseMove(shell, x, y)
1797 mouseMove(shell, x, y)
1794 while (spreadItem.highlightedIndex !== 2 && x <= 4000) {
1798 while (spreadItem.highlightedIndex !== 2 && x <= 4000) {
1795 x+=10;
1799 x+=10;
@@ -1811,6 +1815,15 @@ Rectangle {
1811 function test_progressiveAutoScrolling() {
1815 function test_progressiveAutoScrolling() {
1812 loadDesktopShellWithApps()
1816 loadDesktopShellWithApps()
1813
1817
1818+ // load some more apps
1819+ ApplicationManager.startApplication("twitter-webapp")
1820+ ApplicationManager.startApplication("ubuntu-weather-app")
1821+ ApplicationManager.startApplication("notes-app")
1822+ for (var i = 0; i < topLevelSurfaceList.count; ++i) {
1823+ waitUntilAppWindowIsFullyLoaded(topLevelSurfaceList.idAt(i));
1824+ }
1825+
1826+
1814 var appRepeater = findInvisibleChild(shell, "appRepeater");
1827 var appRepeater = findInvisibleChild(shell, "appRepeater");
1815 var launcherPanel = findChild(shell, "launcherPanel");
1828 var launcherPanel = findChild(shell, "launcherPanel");
1816 verify(appRepeater !== null);
1829 verify(appRepeater !== null);
@@ -1908,21 +1921,21 @@ Rectangle {
1908 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1921 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1909
1922
1910 tryCompare(appDelegate, "state", "normal");
1923 tryCompare(appDelegate, "state", "normal");
1911− tryCompare(PanelState, "decorationsVisible", false)
1924+ tryCompare(panelState, "decorationsVisible", false)
1912
1925
1913 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1926 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1914 tryCompare(appDelegate, "state", "maximized");
1927 tryCompare(appDelegate, "state", "maximized");
1915− tryCompare(PanelState, "decorationsVisible", true)
1928+ tryCompare(panelState, "decorationsVisible", true)
1916
1929
1917 ApplicationManager.stopApplication(application.appId);
1930 ApplicationManager.stopApplication(application.appId);
1918− tryCompare(PanelState, "decorationsVisible", false)
1931+ tryCompare(panelState, "decorationsVisible", false)
1919
1932
1920 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1933 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1921 // They won't be gone until those surface items are destroyed.
1934 // They won't be gone until those surface items are destroyed.
1922 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1935 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1923
1936
1924 ApplicationManager.startApplication(application.appId);
1937 ApplicationManager.startApplication(application.appId);
1925− tryCompare(PanelState, "decorationsVisible", true)
1938+ tryCompare(panelState, "decorationsVisible", true)
1926 }
1939 }
1927
1940
1928 function test_newAppHasValidGeometry() {
1941 function test_newAppHasValidGeometry() {
@@ -1979,7 +1992,7 @@ Rectangle {
1979 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1992 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1980 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1993 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1981
1994
1982− tryVerify(function() {return (appDelegate.y >= PanelState.panelHeight)});
1995+ tryVerify(function() {return (appDelegate.y >= panelState.panelHeight)});
1983 }
1996 }
1984
1997
1985 function test_cantResizeWindowUnderPanel() {
1998 function test_cantResizeWindowUnderPanel() {
@@ -2007,7 +2020,7 @@ Rectangle {
2007 mouseMove(decoration, decoration.width/2, -units.gu(100));
2020 mouseMove(decoration, decoration.width/2, -units.gu(100));
2008
2021
2009 // verify we don't go past the panel
2022 // verify we don't go past the panel
2010− tryVerify(function() {return (appDelegate.y >= PanelState.panelHeight)});
2023+ tryVerify(function() {return (appDelegate.y >= panelState.panelHeight)});
2011 }
2024 }
2012
2025
2013 function test_restoreWindowStateFixesIfUnderPanel() {
2026 function test_restoreWindowStateFixesIfUnderPanel() {
@@ -2031,7 +2044,7 @@ Rectangle {
2031 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
2044 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
2032
2045
2033 appDelegate = appRepeater.itemAt(0);
2046 appDelegate = appRepeater.itemAt(0);
2034− tryVerify(function() {return (appDelegate.y >= PanelState.panelHeight)});
2047+ tryVerify(function() {return (appDelegate.y >= panelState.panelHeight)});
2035 }
2048 }
2036
2049
2037 function test_lifecyclePolicyForNonTouchApp_data() {
2050 function test_lifecyclePolicyForNonTouchApp_data() {
@@ -2698,9 +2711,9 @@ Rectangle {
2698
2711
2699 function test_oskDisplacesWindow_data() {
2712 function test_oskDisplacesWindow_data() {
2700 return [
2713 return [
2701− {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2714+ {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: function() { return units.gu(5); }, oskEnabled: true},
2702− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2715+ {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: function() { return panelState.panelHeight; }, oskEnabled: true},
2703− {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
2716+ {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: function() { return units.gu(10); }, oskEnabled: false},
2704 ]
2717 ]
2705 }
2718 }
2706
2719
@@ -2726,11 +2739,10 @@ Rectangle {
2726 dashAppDelegate.windowedY = data.windowY;
2739 dashAppDelegate.windowedY = data.windowY;
2727 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2740 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2728 var initialY = dashAppDelegate.y;
2741 var initialY = dashAppDelegate.y;
2729− print("intial", initialY, "panel", PanelState.panelHeight);
2730− verify(initialY > PanelState.panelHeight);
2742+ verify(initialY > panelState.panelHeight);
2731
2743
2732 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2744 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
2733− tryCompare(dashAppDelegate, "y", data.targetDisplacement);
2745+ tryCompare(dashAppDelegate, "y", data.targetDisplacement());
2734
2746
2735 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2747 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
2736 tryCompare(dashAppDelegate, "y", initialY);
2748 tryCompare(dashAppDelegate, "y", initialY);
@@ -3161,7 +3173,7 @@ Rectangle {
3161 // double click the panel
3173 // double click the panel
3162 var panel = findChild(shell, "panel");
3174 var panel = findChild(shell, "panel");
3163 verify(panel);
3175 verify(panel);
3164− mouseDoubleClickSequence(panel, panel.width/2, PanelState.panelHeight/2, Qt.LeftButton, Qt.NoModifier, 300);
3176+ mouseDoubleClickSequence(panel, panel.width/2, panelState.panelHeight/2, Qt.LeftButton, Qt.NoModifier, 300);
3165 tryCompare(appDelegate, "state", "restored");
3177 tryCompare(appDelegate, "state", "restored");
3166 }
3178 }
3167
3179
tests/qmltests/tst_ShellApplication.qmlnew file+218
@@ -0,0 +1,218 @@
1+/*
2+ * Copyright (C) 2017 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 LightDM.FullLightDM 0.1 as LightDM
20+import LightDMController 0.1
21+import Unity.Application 0.1
22+import Unity.Test 0.1
23+import Unity.InputInfo 0.1
24+
25+import "../../qml"
26+import "../../qml/Components"
27+import "Stage"
28+
29+Rectangle {
30+ id: root
31+ color: "grey"
32+ width: units.gu(100)
33+ height: units.gu(71)
34+
35+ QtObject {
36+ id: args
37+ property bool hasFullscreen: false
38+ property bool hasFrameless: false
39+ property bool hasGeometry: true
40+ property size windowGeometry: Qt.size(root.width, root.height)
41+ property string deviceName: "desktop"
42+ property string mode: "full-greeter"
43+ }
44+ property alias applicationArguments: args
45+
46+ Component.onCompleted: {
47+ // must set the mock mode before loading the Shell
48+ LightDMController.userMode = "single";
49+ }
50+
51+ Flickable {
52+ id: controls
53+ contentHeight: controlRect.height
54+
55+ anchors.top: root.top
56+ anchors.bottom: root.bottom
57+ anchors.right: root.right
58+ width: units.gu(30)
59+
60+ Rectangle {
61+ id: controlRect
62+ anchors { left: parent.left; right: parent.right }
63+ color: "darkgrey"
64+ height: childrenRect.height + units.gu(2)
65+
66+ Column {
67+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
68+ spacing: units.gu(1)
69+
70+ Flow {
71+ spacing: units.gu(1)
72+ anchors { left: parent.left; right: parent.right }
73+
74+ Button {
75+ text: "Show Greeter"
76+ activeFocusOnPress: false
77+ onClicked: {
78+ LightDM.Greeter.showGreeter();
79+ }
80+ }
81+ Button {
82+ text: "Hide Greeter"
83+ activeFocusOnPress: false
84+ onClicked: {
85+ LightDM.Greeter.hideGreeter()
86+ }
87+ }
88+ }
89+ Button {
90+ anchors {
91+ left: parent.left
92+ right: parent.right
93+ }
94+ action: addMouseAction
95+ color: addMouseAction.checked ? UbuntuColors.red : UbuntuColors.green
96+ }
97+
98+ Button {
99+ anchors {
100+ left: parent.left
101+ right: parent.right
102+ }
103+ action: addKBAction
104+ color: addKBAction.checked ? UbuntuColors.red : UbuntuColors.green
105+ }
106+
107+ MouseTouchEmulationCheckbox {
108+ id: mouseEmulation
109+ checked: true
110+ }
111+
112+ Row {
113+ anchors { left: parent.left; right: parent.right }
114+ CheckBox {
115+ id: autohideLauncherCheckbox
116+ onCheckedChanged: {
117+ GSettingsController.setAutohideLauncher(checked)
118+ }
119+ }
120+ Label {
121+ text: "Autohide launcher"
122+ }
123+ }
124+
125+ Label { text: "Applications"; font.bold: true }
126+
127+ Button {
128+ text: "Start all apps"
129+ width: parent.width
130+ activeFocusOnPress: false
131+ onClicked: {
132+ for (var i = 0; i < ApplicationManager.availableApplications.length; i++) {
133+ var appId = ApplicationManager.availableApplications[i];
134+ ApplicationManager.startApplication(appId)
135+ }
136+ }
137+ }
138+
139+ Repeater {
140+ id: appCheckBoxRepeater
141+ model: ApplicationManager.availableApplications
142+ ApplicationCheckBox {
143+ appId: modelData
144+ }
145+ }
146+
147+ Label {
148+ text: "Fingerprint"
149+ }
150+ Row {
151+ Button {
152+ text: "Success"
153+ onClicked: {
154+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
155+ var uid = 0;
156+ for (var i = 0; i < LightDM.Users.count; i++) {
157+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == AccountsService.user) {
158+ uid = LightDM.Users.data(i, LightDM.UserRoles.UidRole);
159+ break;
160+ }
161+ }
162+ biometryd.operation.mockSuccess(uid);
163+ }
164+ }
165+
166+ Button {
167+ text: "Failure"
168+ onClicked: {
169+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
170+ biometryd.operation.mockFailure("error");
171+ }
172+ }
173+ }
174+ }
175+ }
176+ }
177+
178+ Action {
179+ id: addMouseAction
180+ text: checked ? "Remove Mouse" : "Add Mouse"
181+ onTriggered: {
182+ if (checked) {
183+ console.log("ADD")
184+ MockInputDeviceBackend.addMockDevice("/mouse0", InputInfo.Mouse);
185+ } else {
186+ console.log("REMOVE")
187+ MockInputDeviceBackend.removeDevice("/mouse0");
188+ }
189+ }
190+ iconName: "input-mouse-symbolic"
191+ checkable: true
192+ checked: false
193+ }
194+
195+ Action {
196+ id: addKBAction
197+ text: checked ? "Remove Keyboard" : "Add Keyboard"
198+ onTriggered: {
199+ if (checked) {
200+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
201+ } else {
202+ MockInputDeviceBackend.removeDevice("/kbd0");
203+ }
204+ }
205+ iconName: "input-keyboard-symbolic"
206+ checkable: true
207+ checked: false
208+ }
209+
210+ ShellApplication {
211+ }
212+
213+ UnityTestCase {
214+ id: testCase
215+ name: "ShellApplication"
216+ when: windowShown
217+ }
218+}
tests/qmltests/tst_ShellWithPin.qml+4−16
@@ -40,20 +40,10 @@ Item {
40 }
40 }
41
41
42 QtObject {
42 QtObject {
43− id: applicationArguments
43+ id: _screenWindow
44−
44+ property bool primary: true
45− function hasGeometry() {
46− return false;
47− }
48−
49− function width() {
50− return 0;
51− }
52−
53− function height() {
54− return 0;
55− }
56 }
45 }
46+ property alias screenWindow: _screenWindow
57
47
58 Row {
48 Row {
59 id: contentRow
49 id: contentRow
@@ -123,6 +113,7 @@ Item {
123 when: windowShown
113 when: windowShown
124
114
125 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
115 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
116+ topLevelSurfaceList: shell ? shell.topLevelSurfaceList : null
126
117
127 function init() {
118 function init() {
128 tryCompare(shell, "waitingOnGreeter", false); // will be set when greeter is all ready
119 tryCompare(shell, "waitingOnGreeter", false); // will be set when greeter is all ready
@@ -138,7 +129,6 @@ Item {
138
129
139 // from StageTestCase
130 // from StageTestCase
140 stage = findChild(shell, "stage");
131 stage = findChild(shell, "stage");
141− topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
142 verify(topLevelSurfaceList);
132 verify(topLevelSurfaceList);
143
133
144 startApplication("unity8-dash");
134 startApplication("unity8-dash");
@@ -147,8 +137,6 @@ Item {
147 function cleanup() {
137 function cleanup() {
148 tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
138 tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
149
139
150− topLevelSurfaceList = null;
151−
152 shellLoader.itemDestroyed = false
140 shellLoader.itemDestroyed = false
153
141
154 shellLoader.active = false
142 shellLoader.active = false
tests/uqmlscene/main.cpp+2
@@ -479,6 +479,8 @@ int main(int argc, char ** argv)
479 // TODO: as soon as the engine construction completes, the debug service is
479 // TODO: as soon as the engine construction completes, the debug service is
480 // listening for connections. But actually we aren't ready to debug anything.
480 // listening for connections. But actually we aren't ready to debug anything.
481 QQmlEngine engine;
481 QQmlEngine engine;
482+ engine.rootContext()->setContextProperty("DebuggingController", new DebuggingController(&app));
483+
482 QQmlComponent *component = new QQmlComponent(&engine);
484 QQmlComponent *component = new QQmlComponent(&engine);
483 for (int i = 0; i < imports.size(); ++i)
485 for (int i = 0; i < imports.size(); ++i)
484 engine.addImportPath(imports.at(i));
486 engine.addImportPath(imports.at(i));
tests/utils/modules/Unity/Test/StageTestCase.qml+1−1
@@ -62,7 +62,7 @@ UnityTestCase {
62
62
63 return findChild(stage, "appDelegate_" + surfaceId);
63 return findChild(stage, "appDelegate_" + surfaceId);
64 } catch(err) {
64 } catch(err) {
65− throw new Error("startApplication("+appId+") called from line " + util.callerLine(1) + " failed!");
65+ throw new Error("startApplication("+appId+") called from line " + util.callerLine(1) + " failed! - " + err);
66 }
66 }
67 }
67 }
68}
68}
tests/utils/modules/Unity/Test/UnityTestCase.qml+14
@@ -17,6 +17,7 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import Unity.Application 0.1
19import Unity.Application 0.1
20+import WindowManager 1.0
20import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
21import Ubuntu.Test 1.0 as UbuntuTest
22import Ubuntu.Test 1.0 as UbuntuTest
22import Unity.Test 0.1 as UT
23import Unity.Test 0.1 as UT
@@ -47,6 +48,18 @@ TestCase {
47 }
48 }
48 }
49 }
49
50
51+ Binding {
52+ target: WindowManagerObjects
53+ property: "surfaceManager"
54+ value: SurfaceManager
55+ }
56+
57+ Binding {
58+ target: WindowManagerObjects
59+ property: "applicationManager"
60+ value: ApplicationManager
61+ }
62+
50 // Fake implementation to be provided to items under test
63 // Fake implementation to be provided to items under test
51 property var fakeDateTime: new function() {
64 property var fakeDateTime: new function() {
52 this.currentTimeMs = 0
65 this.currentTimeMs = 0
@@ -641,5 +654,6 @@ TestCase {
641 tryCompare(application, "state", ApplicationInfo.Stopped);
654 tryCompare(application, "state", ApplicationInfo.Stopped);
642 }
655 }
643 compare(ApplicationManager.count, 0);
656 compare(ApplicationManager.count, 0);
657+ SurfaceManager.releaseInputMethodSurface();
644 }
658 }
645}
659}