on f20331b help sign in
76 files changed+2718−614
since splitsnapshots
unifiedsplit
CMakeLists.txt+1−1 debian/control+3−3 debian/unity8.install+4 plugins/Cursor/CMakeLists.txt+1 plugins/Cursor/InputDispatcherFilter.cpp+175 plugins/Cursor/InputDispatcherFilter.h+62 plugins/Cursor/MousePointer.cpp+63−78 plugins/Cursor/MousePointer.h+5−4 plugins/UInput/plugin.cpp+7−1 plugins/Unity/CMakeLists.txt+1 plugins/Unity/Debug/CMakeLists.txt+30 plugins/Unity/Debug/consolelog.cpp+221 plugins/Unity/Debug/consolelog.h+87 plugins/Unity/Debug/plugin.cpp+31 src/ShellView.h → plugins/Unity/Debug/plugin.h+9−11 plugins/Unity/Debug/qmldir+3 qml/CMakeLists.txt+1−1 qml/Components/PanelState/PanelState.qml−1 qml/Components/PanelState/qmldir+1−1 qml/Components/VirtualTouchPad.qml+13−19 qml/DisabledScreenNotice.qml+2−1 qml/Greeter/Greeter.qml+21−3 qml/Greeter/SecondaryGreeter.qml+75 qml/OrientedShell.qml+7−9 qml/Panel/Panel.qml+14−14 qml/Shell.qml+48−12 qml/ShellApplication.qml+52 src/SecondaryWindow.h → qml/ShellNotifier.qml+12−12 qml/ShellScreen.qml+65 qml/Stage/DecoratedWindow.qml+3−1 qml/Stage/FakeMaximizeDelegate.qml+12−11 qml/Stage/Spread/ScreensAndWorkspaces.qml+181 qml/Stage/Spread/Spread.qml+5−5 qml/Stage/Spread/SpreadDelegateInputArea.qml+24−11 qml/Stage/Spread/WorkspacePreview.qml+220 qml/Stage/Spread/Workspaces.qml+316 qml/Stage/Stage.qml+137−103 qml/Stage/SurfaceContainer.qml+2 qml/Stage/WorkspacePreview.qml+212 qml/Stage/graphics/multi-monitor_leave.png qml/qmldir+1 src/ApplicationArguments.cpp+11−2 src/ApplicationArguments.h+17−15 src/CMakeLists.txt−2 src/DebuggingController.cpp+12−1 src/DebuggingController.h+14 src/SecondaryWindow.cpp−40 src/ShellApplication.cpp+26−104 src/ShellApplication.h+2−14 src/ShellView.cpp−63 src/UnityCommandLineParser.h+1−1 tests/mocks/Cursor/Cursor.qml+49−2 tests/mocks/GSettings.1.0/plugin.cpp+12 tests/mocks/GSettings.1.0/plugin.h+1 tests/mocks/UInput/plugin.cpp+6−1 tests/mocks/Unity/Application/MirSurfaceItem.cpp+27−4 tests/mocks/Unity/Application/SurfaceManager.cpp+1−1 tests/mocks/Unity/InputInfo/InputWindow.qml+68 tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2 tests/mocks/Unity/InputInfo/mockcontroller.h+5−1 tests/mocks/Unity/InputInfo/plugin.cpp+1−2 tests/mocks/Unity/InputInfo/qmldir+2 tests/mocks/Unity/Screens/CMakeLists.txt+2−1 tests/mocks/Unity/Screens/plugin.cpp+14−1 tests/mocks/Unity/Screens/screens.cpp+52−9 tests/mocks/Unity/Screens/screens.h+84−4 tests/mocks/Unity/Screens/screenwindow.cpp+35 tests/mocks/Unity/Screens/screenwindow.h+42 tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2 tests/qmltests/Panel/tst_Panel.qml+14−12 tests/qmltests/Stage/tst_DecoratedWindow.qml+5−1 tests/qmltests/Stage/tst_DesktopStage.qml+8−3 tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8 tests/qmltests/tst_OrientedShell.qml+24−5 tests/qmltests/tst_Shell.qml+27−11 tests/uqmlscene/main.cpp+2
CMakeLists.txt+1−1
@@ -70,7 +70,7 @@ find_package(Qt5DBus 5.6 REQUIRED)
7070find_package(Qt5Concurrent 5.6 REQUIRED)
7171find_package(Qt5Sql 5.6 REQUIRED)
7272
73−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=26)
73+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=27)
7474pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
7575pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
7676pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
debian/control+3−3
@@ -38,7 +38,7 @@ Build-Depends: cmake,
3838 libubuntugestures5-private-dev (>= 1.3.2030),
3939 libudev-dev,
4040 libudm-common-dev,
41− libunity-api-dev (>= 8.2),
41+ libunity-api-dev (>= 8.3),
4242 libusermetricsoutput1-dev,
4343# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4444 libx11-dev[!arm64 !armhf],
@@ -162,7 +162,7 @@ Depends: qml-module-qtquick-layouts,
162162 qttranslations5-l10n,
163163 ubuntu-thumbnailer-impl-0,
164164 ubuntu-wallpapers,
165− unity-application-impl-26,
165+ unity-application-impl-27,
166166 unity-notifications-impl-3,
167167 unity-plugin-scopes | unity-scopes-impl,
168168 unity-scopes-impl-12,
@@ -212,7 +212,7 @@ Depends: dbus-test-runner,
212212 ${misc:Depends},
213213 ${shlibs:Depends},
214214Provides: unity-application-impl,
215− unity-application-impl-26,
215+ unity-application-impl-27,
216216 unity8-fake-env,
217217Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1),
218218 unity8-fake-env,
debian/unity8.install+4
@@ -16,7 +16,11 @@ usr/share/unity8/Rotation
1616usr/share/unity8/DeviceConfiguration.qml
1717usr/share/unity8/OrientedShell.qml
1818usr/share/unity8/DisabledScreenNotice.qml
19+usr/share/unity8/qmldir
1920usr/share/unity8/Shell.qml
21+usr/share/unity8/ShellApplication.qml
22+usr/share/unity8/ShellNotifier.qml
23+usr/share/unity8/ShellScreen.qml
2024usr/share/unity8/Stage
2125usr/share/unity8/Tutorial
2226usr/share/unity8/Wizard
plugins/Cursor/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
1515 CursorImageInfo.cpp
1616 CursorImageProvider.cpp
1717 MousePointer.cpp
18+ InputDispatcherFilter.cpp
1819 # We need to run moc on this header
1920 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
2021 )
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 @@
1616
1717#include "MousePointer.h"
1818#include "CursorImageProvider.h"
19−
20−// Unity API
21−#include <unity/shell/application/MirPlatformCursor.h>
19+#include "InputDispatcherFilter.h"
2220
2321#include <QQuickWindow>
24−#include <QGuiApplication>
25−#include <QtMath>
2622
27−#include <qpa/qwindowsysteminterface.h>
23+// Unity API
24+#include <unity/shell/application/MirPlatformCursor.h>
2825
2926MousePointer::MousePointer(QQuickItem *parent)
3027 : MirMousePointerInterface(parent)
3128 , m_cursorName(QStringLiteral("left_ptr"))
3229 , m_themeName(QStringLiteral("default"))
3330{
34−}
35−
36−void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
37− Qt::KeyboardModifiers modifiers)
38−{
39− if (!parentItem()) {
40− return;
41− }
42−
43− if (!movement.isNull()) {
44− Q_EMIT mouseMoved();
45− }
46−
47− m_accumulatedMovement += movement;
48− // don't apply the fractional part
49− QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
50− m_accumulatedMovement -= appliedMovement;
51−
52− qreal newX = x() + appliedMovement.x();
53− qreal newY = y() + appliedMovement.y();
54− const qreal sceneWidth = parentItem()->width();
55− const qreal sceneHeight = parentItem()->height();
56−
57− if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
58− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
59− Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
60− m_pushing = true;
61− } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
62− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
63− Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
64− m_pushing = true;
65− } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
66− const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
67− Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
68− m_pushing = true;
69− } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
70− const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
71− Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
72− m_pushing = true;
73− } else if (newX < 0) { // left edge
74− Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
75− m_pushing = true;
76− } else if (newX >= sceneWidth) { // right edge
77− Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
78− m_pushing = true;
79− } else if (newY < m_topBoundaryOffset) { // top edge
80− Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
81− m_pushing = true;
82− } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
83− if (m_pushing) {
31+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedLeftBoundary,
32+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
33+ if (window() && window()->screen() == screen) {
34+ Q_EMIT pushedLeftBoundary(amount, buttons);
35+ }
36+ });
37+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedRightBoundary,
38+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
39+ if (window() && window()->screen() == screen) {
40+ Q_EMIT pushedRightBoundary(amount, buttons);
41+ }
42+ });
43+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopBoundary,
44+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
45+ if (window() && window()->screen() == screen) {
46+ Q_EMIT pushedTopBoundary(amount, buttons);
47+ }
48+ });
49+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopLeftCorner,
50+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
51+ if (window() && window()->screen() == screen) {
52+ Q_EMIT pushedTopLeftCorner(amount, buttons);
53+ }
54+ });
55+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedTopRightCorner,
56+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
57+ if (window() && window()->screen() == screen) {
58+ Q_EMIT pushedTopRightCorner(amount, buttons);
59+ }
60+ });
61+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomLeftCorner,
62+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
63+ if (window() && window()->screen() == screen) {
64+ Q_EMIT pushedBottomLeftCorner(amount, buttons);
65+ }
66+ });
67+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushedBottomRightCorner,
68+ this, [this](QScreen* screen, qreal amount, Qt::MouseButtons buttons) {
69+ if (window() && window()->screen() == screen) {
70+ Q_EMIT pushedBottomRightCorner(amount, buttons);
71+ }
72+ });
73+ connect(InputDispatcherFilter::instance(), &InputDispatcherFilter::pushStopped,
74+ this, [this](QScreen* screen) {
75+ if (window() && window()->screen() == screen) {
8476 Q_EMIT pushStopped();
85− m_pushing = false;
8677 }
87− }
78+ });
8879
89− applyItemConfinement(newX, newY);
90−
91− setX(qBound(0.0, newX, sceneWidth - 1));
92− setY(qBound(0.0, newY, sceneHeight - 1));
80+ InputDispatcherFilter::instance()->registerPointer(this);
81+}
9382
94− QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
95− QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
96− buttons, modifiers);
83+MousePointer::~MousePointer()
84+{
85+ registerScreen(nullptr);
86+ InputDispatcherFilter::instance()->unregisterPointer(this);
9787}
9888
9989void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
@@ -121,17 +111,6 @@ void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
121111 }
122112}
123113
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−
135114int MousePointer::topBoundaryOffset() const
136115{
137116 return m_topBoundaryOffset;
@@ -182,7 +161,7 @@ void MousePointer::registerScreen(QScreen *screen)
182161 if (m_registeredScreen) {
183162 auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
184163 if (previousCursor) {
185− previousCursor->setMousePointer(nullptr);
164+ previousCursor->unregisterMousePointer(this);
186165 } else {
187166 qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
188167 }
@@ -193,7 +172,7 @@ void MousePointer::registerScreen(QScreen *screen)
193172 if (m_registeredScreen) {
194173 auto cursor = dynamic_cast<MirPlatformCursor*>(m_registeredScreen->handle()->cursor());
195174 if (cursor) {
196− cursor->setMousePointer(this);
175+ cursor->registerMousePointer(this);
197176 } else {
198177 qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
199178 }
@@ -216,6 +195,12 @@ void MousePointer::setThemeName(const QString &themeName)
216195 }
217196}
218197
198+void MousePointer::moveTo(const QPoint &position)
199+{
200+ setPosition(position);
201+ Q_EMIT mouseMoved();
202+}
203+
219204void MousePointer::setCustomCursor(const QCursor &customCursor)
220205{
221206 CursorImageProvider::instance()->setCustomCursor(customCursor);
plugins/Cursor/MousePointer.h+5−4
@@ -31,6 +31,7 @@ class MousePointer : public MirMousePointerInterface {
3131 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
3232public:
3333 MousePointer(QQuickItem *parent = nullptr);
34+ ~MousePointer();
3435
3536 void setCursorName(const QString &qtCursorName) override;
3637 QString cursorName() const override { return m_cursorName; }
@@ -38,6 +39,8 @@ public:
3839 void setThemeName(const QString &themeName) override;
3940 QString themeName() const override { return m_themeName; }
4041
42+ void moveTo(const QPoint& position) override;
43+
4144 void setCustomCursor(const QCursor &) override;
4245
4346 QQuickItem* confiningItem() const;
@@ -46,10 +49,7 @@ public:
4649 int topBoundaryOffset() const;
4750 void setTopBoundaryOffset(int topBoundaryOffset);
4851
49−public Q_SLOTS:
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;
52+ QScreen* screen() const { return m_registeredScreen; }
5353
5454Q_SIGNALS:
5555 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
@@ -79,6 +79,7 @@ private:
7979 QPointer<QScreen> m_registeredScreen;
8080 QString m_cursorName;
8181 QString m_themeName;
82+ bool m_active;
8283
8384 // Accumulated, unapplied, mouse movement.
8485 QPointF m_accumulatedMovement;
plugins/UInput/plugin.cpp+7−1
@@ -19,8 +19,14 @@
1919
2020#include <QtQml/qqml.h>
2121
22+
23+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
24+{
25+ return new UInput;
26+}
27+
2228void UInputPlugin::registerTypes(const char *uri)
2329{
2430 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<UInput>(uri, 0, 1, "UInput");
31+ qmlRegisterSingletonType<UInput>(uri, 0, 1, "UInput", uinputSingleton);
2632}
plugins/Unity/CMakeLists.txt+1
@@ -1,5 +1,6 @@
11add_subdirectory(ApplicationMenu)
22add_subdirectory(Connectivity)
3+add_subdirectory(Debug)
34add_subdirectory(Indicators)
45add_subdirectory(Launcher)
56add_subdirectory(Session)
plugins/Unity/Debug/CMakeLists.txtnew file+30
@@ -0,0 +1,30 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+)
5+
6+include_directories(
7+ SYSTEM
8+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
9+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
10+ ${GIO_INCLUDE_DIRS}
11+)
12+
13+set(QMLPLUGIN_SRC
14+ consolelog.cpp
15+ plugin.cpp
16+ )
17+
18+add_library(Debug-qml SHARED
19+ ${QMLPLUGIN_SRC}
20+ )
21+
22+# Because this is an internal support library, we want
23+# to expose all symbols in it. Consider changing this
24+# either to a static library or just using the
25+# files directly in targets.
26+set_target_properties(Debug-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
27+
28+qt5_use_modules(Debug-qml Qml Quick Concurrent)
29+
30+add_unity8_plugin(Unity.Debug 0.1 Unity/Debug TARGETS Debug-qml)
plugins/Unity/Debug/consolelog.cppnew file+221
@@ -0,0 +1,221 @@
1+/*
2+ * Copyright 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 Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "consolelog.h"
18+
19+#include <unistd.h>
20+#include <stdio.h>
21+#include <thread>
22+#include <iostream>
23+#include <sys/ioctl.h>
24+
25+#include <functional>
26+
27+namespace
28+{
29+
30+int wait_fd(std::function<int(void)> fn)
31+{
32+ int ret = -1;
33+ bool fd_blocked = false;
34+ do
35+ {
36+ ret = fn();
37+ fd_blocked = (errno == EINTR || errno == EBUSY);
38+ if (fd_blocked)
39+ QThread::msleep(10);
40+ }
41+ while (ret < 0);
42+ return ret;
43+}
44+
45+#define PIPE_WAIT(val) wait_fd(std::bind(pipe, val))
46+#define DUP_WAIT(fd) wait_fd(std::bind(dup, fd))
47+#define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2))
48+#define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd))
49+
50+}
51+
52+LogRedirector::LogRedirector()
53+ : m_ref(0)
54+ , m_stop(false)
55+{
56+ setObjectName("ConsoleLog");
57+
58+ // make stdout & stderr streams unbuffered
59+ // so that we don't need to flush the streams
60+ // before capture and after capture
61+ // (fflush can cause a deadlock if the stream is currently being used)
62+ setvbuf(stdout,NULL,_IONBF,0);
63+ setvbuf(stderr,NULL,_IONBF,0);
64+}
65+
66+void LogRedirector::run()
67+{
68+ PIPE_WAIT(m_pipe);
69+
70+ int oldStdOut = DUP_WAIT(fileno(stdout));
71+ int oldStdErr = DUP_WAIT(fileno(stderr));
72+
73+ DUP2_WAIT(m_pipe[WRITE], fileno(stdout));
74+ DUP2_WAIT(m_pipe[WRITE], fileno(stderr));
75+ CLOSE_WAIT(m_pipe[WRITE]);
76+
77+ while(true) {
78+ {
79+ QMutexLocker lock(&m_mutex);
80+ if (m_stop) break;
81+ }
82+ checkLog();
83+ QThread::msleep(50);
84+ }
85+
86+ DUP2_WAIT(oldStdOut, fileno(stdout));
87+ DUP2_WAIT(oldStdErr, fileno(stderr));
88+
89+ CLOSE_WAIT(oldStdOut);
90+ CLOSE_WAIT(oldStdErr);
91+ CLOSE_WAIT(m_pipe[READ]);
92+}
93+
94+
95+void LogRedirector::checkLog()
96+{
97+ // Do not allow read to block with no data.
98+ // If we stop the thread while waiting a read,
99+ // it will block the main thread waiting for this thread to stop.
100+ int count = 0;
101+ ioctl(m_pipe[READ], FIONREAD, &count);
102+ if (count <= 0) return;
103+
104+ std::string captured;
105+ std::string buf;
106+ const int bufSize = 1024;
107+ buf.resize(bufSize);
108+ int bytesRead = 0;
109+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
110+ while(bytesRead == bufSize)
111+ {
112+ captured += buf;
113+ bytesRead = 0;
114+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
115+ }
116+ if (bytesRead > 0)
117+ {
118+ buf.resize(bytesRead);
119+ captured += buf;
120+ }
121+
122+ if (!captured.empty()) {
123+ Q_EMIT log(QString::fromStdString(captured));
124+ }
125+}
126+
127+LogRedirector *LogRedirector::instance()
128+{
129+ static LogRedirector* log = nullptr;
130+ if (!log) {
131+ log = new LogRedirector();
132+ }
133+ return log;
134+}
135+
136+void LogRedirector::add(ConsoleLog* logger)
137+{
138+ QMutexLocker lock(&m_mutex);
139+ connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection);
140+
141+ m_ref++;
142+ if (!LogRedirector::instance()->isRunning()) {
143+ m_stop = false;
144+ LogRedirector::instance()->start();
145+ }
146+}
147+
148+void LogRedirector::remove(ConsoleLog* logger)
149+{
150+ QMutexLocker lock(&m_mutex);
151+ disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged);
152+
153+ m_ref = qMax(m_ref-1, 0);
154+ if (m_ref == 0 && LogRedirector::instance()->isRunning()) {
155+ m_stop = true;
156+ lock.unlock();
157+ LogRedirector::instance()->wait();
158+ }
159+}
160+
161+ConsoleLog::ConsoleLog(QObject *parent)
162+ : QObject(parent)
163+ , m_enabled(false)
164+ , m_maxLines(60)
165+{
166+ auto updateEnabled = [this]() {
167+ if (m_enabled) {
168+ LogRedirector::instance()->add(this);
169+ } else {
170+ LogRedirector::instance()->remove(this);
171+ }
172+ };
173+ connect(this, &ConsoleLog::enabledChanged, this, updateEnabled);
174+}
175+
176+ConsoleLog::~ConsoleLog()
177+{
178+ if (m_enabled) {
179+ LogRedirector::instance()->remove(this);
180+ }
181+}
182+
183+void ConsoleLog::setEnabled(bool enabled)
184+{
185+ if (m_enabled == enabled) {
186+ return;
187+ }
188+
189+ m_enabled = enabled;
190+ Q_EMIT enabledChanged();
191+}
192+
193+QString ConsoleLog::out() const
194+{
195+ return m_out.join("\n");
196+}
197+
198+void ConsoleLog::setMaxLines(int maxLines)
199+{
200+ if (m_maxLines == maxLines) {
201+ return;
202+ }
203+
204+ m_maxLines = maxLines;
205+ while (m_out.count() > m_maxLines) {
206+ m_out.removeLast();
207+ }
208+ Q_EMIT outChanged();
209+ Q_EMIT maxLinesChanged();
210+}
211+
212+void ConsoleLog::logged(QString captured)
213+{
214+ QStringList li = captured.split("\n", QString::SkipEmptyParts);
215+ li << m_out;
216+ m_out = li;
217+ while (m_out.count() > m_maxLines) {
218+ m_out.removeLast();
219+ }
220+ Q_EMIT outChanged();
221+}
plugins/Unity/Debug/consolelog.hnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright 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 Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef CONSOLELOG_H
18+#define CONSOLELOG_H
19+
20+#include <QThread>
21+#include <QMutex>
22+#include <QSet>
23+
24+class ConsoleLog;
25+
26+class LogRedirector : public QThread
27+{
28+ Q_OBJECT
29+public:
30+ static LogRedirector *instance();
31+
32+ void add(ConsoleLog* logger);
33+ void remove(ConsoleLog* logger);
34+
35+private Q_SLOTS:
36+ void checkLog();
37+
38+Q_SIGNALS:
39+ void log(QString log);
40+
41+private:
42+ LogRedirector();
43+ void run() Q_DECL_OVERRIDE;
44+
45+ QMutex m_mutex;
46+ int m_pipe[2];
47+ int m_ref;
48+ bool m_stop;
49+
50+ enum PIPES { READ, WRITE };
51+};
52+
53+class ConsoleLog : public QObject
54+{
55+ Q_OBJECT
56+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
57+ Q_PROPERTY(QString out READ out NOTIFY outChanged)
58+ Q_PROPERTY(int maxLines READ maxLines WRITE setMaxLines NOTIFY maxLinesChanged)
59+public:
60+ explicit ConsoleLog(QObject *parent = 0);
61+ ~ConsoleLog();
62+
63+ bool isEnabled() const { return m_enabled; }
64+ void setEnabled(bool enabled);
65+
66+ QString out() const;
67+
68+ int maxLines() const { return m_maxLines; }
69+ void setMaxLines(int maxLines);
70+
71+public Q_SLOTS:
72+ void logged(QString captured);
73+
74+Q_SIGNALS:
75+ void enabledChanged();
76+ void outChanged();
77+ void maxLinesChanged();
78+
79+private:
80+ void updateEnabled();
81+
82+ QStringList m_out;
83+ bool m_enabled;
84+ int m_maxLines;
85+};
86+
87+#endif // CONSOLELOG_H
plugins/Unity/Debug/plugin.cppnew file+31
@@ -0,0 +1,31 @@
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+// Qt
18+#include <QtQml/qqml.h>
19+
20+// self
21+#include "plugin.h"
22+
23+// local
24+#include "consolelog.h"
25+
26+void UnityDebugPlugin::registerTypes(const char *uri)
27+{
28+ Q_ASSERT(uri == QLatin1String("Unity.Debug"));
29+
30+ qmlRegisterType<ConsoleLog>(uri, 0, 1, "ConsoleLog");
31+}
src/ShellView.h → plugins/Unity/Debug/plugin.hrenamed+9−11
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,21 +14,19 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_SHELL_VIEW_H
18−#define UNITY_SHELL_VIEW_H
17+#ifndef UNITYDEBUG_PLUGIN_H
18+#define UNITYDEBUG_PLUGIN_H
1919
20−#include <QQuickView>
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
2122
22−class ShellView : public QQuickView
23+class UnityDebugPlugin : public QQmlExtensionPlugin
2324{
2425 Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
2527
2628public:
27− ShellView(QQmlEngine *engine, QObject *qmlArgs);
28−
29−private Q_SLOTS:
30− void onWidthChanged(int);
31− void onHeightChanged(int);
29+ void registerTypes(const char *uri) override;
3230};
3331
34−#endif // UNITY_SHELL_VIEW_H
32+#endif
plugins/Unity/Debug/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.Debug
2+plugin Debug-qml
3+typeinfo Debug.qmltypes
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)
22
33install(FILES ${QML_JS_FILES}
44 DESTINATION ${SHELL_APP_DIR}
qml/Components/PanelState/PanelState.qml−1
@@ -14,7 +14,6 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−pragma Singleton
1817import QtQuick 2.4
1918
2019QtObject {
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+13−19
@@ -24,16 +24,14 @@ import "../Components"
2424
2525Item {
2626 id: root
27− property var uinput: UInput {
28− Component.onCompleted: createMouse();
29− Component.onDestruction: removeMouse();
30− }
3127
3228 Component.onCompleted: {
29+ UInput.createMouse();
3330 if (!settings.touchpadTutorialHasRun) {
3431 root.runTutorial()
3532 }
3633 }
34+ Component.onDestruction: UInput.removeMouse()
3735
3836 function runTutorial() {
3937 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
@@ -85,7 +83,7 @@ Item {
8583 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
8684 && clickTimer.running) {
8785 clickTimer.stop();
88− uinput.pressMouse(UInput.ButtonLeft)
86+ UInput.pressMouse(UInput.ButtonLeft)
8987 isDoubleClick = true;
9088 }
9189 isClick = true;
@@ -104,7 +102,7 @@ Item {
104102
105103 onReleased: {
106104 if (isDoubleClick || isDrag) {
107− uinput.releaseMouse(UInput.ButtonLeft)
105+ UInput.releaseMouse(UInput.ButtonLeft)
108106 isDoubleClick = false;
109107 }
110108 if (isClick) {
@@ -120,8 +118,8 @@ Item {
120118 interval: 200
121119 property int button: UInput.ButtonLeft
122120 onTriggered: {
123− uinput.pressMouse(button);
124− uinput.releaseMouse(button);
121+ UInput.pressMouse(button);
122+ UInput.releaseMouse(button);
125123 }
126124 function scheduleClick(button) {
127125 clickTimer.button = button;
@@ -138,7 +136,7 @@ Item {
138136 isDrag = true;
139137 }
140138
141− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
139+ UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
142140 }
143141
144142 function scroll(touchPoints) {
@@ -166,7 +164,7 @@ Item {
166164 dh /= 2;
167165 dv /= 2;
168166
169− uinput.scrollMouse(dh, dv);
167+ UInput.scrollMouse(dh, dv);
170168 }
171169
172170 touchPoints: [
@@ -189,8 +187,8 @@ Item {
189187 objectName: "leftButton"
190188 Layout.fillWidth: true
191189 Layout.fillHeight: true
192− onPressed: uinput.pressMouse(UInput.ButtonLeft);
193− onReleased: uinput.releaseMouse(UInput.ButtonLeft);
190+ onPressed: UInput.pressMouse(UInput.ButtonLeft);
191+ onReleased: UInput.releaseMouse(UInput.ButtonLeft);
194192 property bool highlight: false
195193 UbuntuShape {
196194 anchors.fill: parent
@@ -204,8 +202,8 @@ Item {
204202 objectName: "rightButton"
205203 Layout.fillWidth: true
206204 Layout.fillHeight: true
207− onPressed: uinput.pressMouse(UInput.ButtonRight);
208− onReleased: uinput.releaseMouse(UInput.ButtonRight);
205+ onPressed: UInput.pressMouse(UInput.ButtonRight);
206+ onReleased: UInput.releaseMouse(UInput.ButtonRight);
209207 property bool highlight: false
210208 UbuntuShape {
211209 anchors.fill: parent
@@ -239,14 +237,10 @@ Item {
239237 }
240238 }
241239
242− Screens {
243− id: screens
244− }
245−
246240 InputMethod {
247241 id: inputMethod
248242 // 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
250244 objectName: "inputMethod"
251245 anchors.fill: parent
252246 }
qml/DisabledScreenNotice.qml+2−1
@@ -28,8 +28,9 @@ Item {
2828 property var screen: Screen
2929 property var orientationLock: OrientationLock
3030
31+ property alias deviceConfiguration: _deviceConfiguration
3132 DeviceConfiguration {
32− id: deviceConfiguration
33+ id: _deviceConfiguration
3334 name: applicationArguments.deviceName
3435 }
3536
qml/Greeter/Greeter.qml+21−3
@@ -25,6 +25,7 @@ import Unity.Launcher 0.1
2525import Unity.Session 0.1
2626
2727import "." 0.1
28+import ".." 0.1
2829import "../Components"
2930
3031Showable {
@@ -227,7 +228,7 @@ Showable {
227228 if (forcedUnlock && shown) {
228229 hideView();
229230 if (hideNow) {
230− root.hideNow(); // skip hide animation
231+ ShellNotifier.greeter.hide(true); // skip hide animation
231232 }
232233 }
233234 }
@@ -375,7 +376,7 @@ Showable {
375376 onEmergencyCall: root.emergencyCall()
376377 onRequiredChanged: {
377378 if (!loader.item.required) {
378− root.hide();
379+ ShellNotifier.greeter.hide(false);
379380 }
380381 }
381382 }
@@ -498,12 +499,29 @@ Showable {
498499 onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
499500 }
500501
502+ Connections {
503+ target: ShellNotifier.greeter
504+ onHide: {
505+ if (now) {
506+ root.hideNow(); // skip hide animation
507+ } else {
508+ root.hide();
509+ }
510+ }
511+ }
512+
513+ Binding {
514+ target: ShellNotifier.greeter
515+ property: "shown"
516+ value: root.shown
517+ }
518+
501519 Connections {
502520 target: DBusUnitySessionService
503521 onLockRequested: root.forceShow()
504522 onUnlocked: {
505523 root.forcedUnlock = true;
506− root.hideNow();
524+ ShellNotifier.greeter.hide(true);
507525 }
508526 }
509527
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+7−9
@@ -32,15 +32,17 @@ Item {
3232 implicitWidth: units.gu(40)
3333 implicitHeight: units.gu(71)
3434
35+ property alias deviceConfiguration: _deviceConfiguration
36+ property alias orientations: d.orientations
37+ property alias surfaceManager: shell.surfaceManager
38+
3539 onWidthChanged: calculateUsageMode();
3640
3741 DeviceConfiguration {
38− id: deviceConfiguration
42+ id: _deviceConfiguration
3943 name: applicationArguments.deviceName
4044 }
4145
42− property alias orientations: d.orientations
43−
4446 Item {
4547 id: d
4648
@@ -157,10 +159,6 @@ Item {
157159 return false;
158160 }
159161
160− Screens {
161− id: screens
162− }
163−
164162 property int orientation
165163 onPhysicalOrientationChanged: {
166164 if (!orientationLocked) {
@@ -258,7 +256,7 @@ Item {
258256
259257 Shell {
260258 id: shell
261− objectName: "shell"
259+ objectName: "shell-"+screenWindow.objectName
262260 width: root.width
263261 height: root.height
264262 orientation: root.angleToOrientation(orientationAngle)
@@ -272,7 +270,7 @@ Item {
272270 // have multiple keyboards around. For now we only enable one keyboard at a time
273271 // thus hiding it here if there is a physical one around or if we have a second
274272 // screen (the virtual touchpad & osk on the phone) attached.
275− oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
273+ oskEnabled: (keyboardsModel.count === 0 && Screens.count === 1) ||
276274 forceOSKEnabled
277275
278276 usageScenario: {
qml/Panel/Panel.qml+14−14
@@ -47,8 +47,8 @@ Item {
4747 property bool fullscreenMode: false
4848 property real panelAreaShowProgress: 1.0
4949 property bool greeterShown: false
50−
5150 property string mode: "staged"
51+ property PanelState panelState
5252
5353 MouseArea {
5454 id: backMouseEater
@@ -65,14 +65,14 @@ Item {
6565 }
6666
6767 Binding {
68− target: PanelState
68+ target: panelState
6969 property: "panelHeight"
7070 value: minimizedPanelHeight
7171 }
7272
7373 RegisteredApplicationMenuModel {
7474 id: registeredMenuModel
75− persistentSurfaceId: PanelState.focusedPersistentSurfaceId
75+ persistentSurfaceId: panelState.focusedPersistentSurfaceId
7676 }
7777
7878 QtObject {
@@ -83,11 +83,11 @@ Item {
8383 !indicators.shown &&
8484 (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
8585
86− property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
87− PanelState.decorationsAlwaysVisible
86+ property bool showWindowDecorationControls: (revealControls && panelState.decorationsVisible) ||
87+ panelState.decorationsAlwaysVisible
8888
8989 property bool showPointerMenu: revealControls &&
90− (PanelState.decorationsVisible || mode == "staged")
90+ (panelState.decorationsVisible || mode == "staged")
9191
9292 property bool enablePointerMenu: revealControls &&
9393 applicationMenus.available &&
@@ -139,7 +139,7 @@ Item {
139139 fill: panelAreaBackground
140140 bottomMargin: -units.gu(1)
141141 }
142− visible: PanelState.dropShadow
142+ visible: panelState.dropShadow
143143 source: "graphics/rectangular_dropshadow.sci"
144144 }
145145
@@ -192,12 +192,12 @@ Item {
192192 visible: opacity != 0
193193 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
194194
195− active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
195+ active: panelState.decorationsVisible || panelState.decorationsAlwaysVisible
196196 windowIsMaximized: true
197− onCloseClicked: PanelState.closeClicked()
198− onMinimizeClicked: PanelState.minimizeClicked()
199− onMaximizeClicked: PanelState.restoreClicked()
200− closeButtonShown: PanelState.closeButtonShown
197+ onCloseClicked: panelState.closeClicked()
198+ onMinimizeClicked: panelState.minimizeClicked()
199+ onMaximizeClicked: panelState.restoreClicked()
200+ closeButtonShown: panelState.closeButtonShown
201201 }
202202
203203 Loader {
@@ -217,7 +217,7 @@ Item {
217217 anchors.left: parent.left
218218 anchors.margins: units.gu(1)
219219 height: menuBarLoader.height
220− enableKeyFilter: valid && PanelState.decorationsVisible
220+ enableKeyFilter: valid && panelState.decorationsVisible
221221 unityMenuModel: __applicationMenus.model
222222
223223 Connections {
@@ -265,7 +265,7 @@ Item {
265265 }
266266
267267 showRowTitle: !expanded
268− rowTitle: PanelState.title
268+ rowTitle: panelState.title
269269 rowItemDelegate: ActionItem {
270270 id: actionItem
271271 property int ownIndex: index
qml/Shell.qml+48−12
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
@@ -38,12 +38,14 @@ import "Notifications"
3838import "Stage"
3939import "Tutorial"
4040import "Wizard"
41+import "Components/PanelState"
4142import Unity.Notifications 1.0 as NotificationBackend
4243import Unity.Session 0.1
4344import Unity.DashCommunicator 0.1
4445import Unity.Indicators 0.1 as Indicators
4546import Cursor 1.1
4647import WindowManager 1.0
48+import Unity.Debug 0.1 as Debug
4749
4850
4951StyledItem {
@@ -52,6 +54,7 @@ StyledItem {
5254 theme.name: "Ubuntu.Components.Themes.SuruDark"
5355
5456 // to be set from outside
57+ property alias surfaceManager: topLevelSurfaceList.surfaceManager
5558 property int orientationAngle: 0
5659 property int orientation
5760 property Orientations orientations
@@ -259,21 +262,21 @@ StyledItem {
259262 schema.id: "com.canonical.Unity8"
260263 }
261264
265+ PanelState {
266+ id: panelState
267+ objectName: "panelState"
268+ }
269+
262270 Item {
263271 id: stages
264272 objectName: "stages"
265273 width: parent.width
266274 height: parent.height
267275
268− SurfaceManager {
269− id: surfaceMan
270− objectName: "surfaceManager"
271− }
272276 TopLevelWindowModel {
273277 id: topLevelSurfaceList
274278 objectName: "topLevelSurfaceList"
275279 applicationManager: ApplicationManager // it's a singleton
276− surfaceManager: surfaceMan
277280 }
278281
279282 Stage {
@@ -314,6 +317,7 @@ StyledItem {
314317 altTabPressed: physicalKeysMapper.altTabPressed
315318 oskEnabled: shell.oskEnabled
316319 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
320+ panelState: panelState
317321
318322 onSpreadShownChanged: {
319323 panel.indicators.hide();
@@ -368,8 +372,13 @@ StyledItem {
368372 objectName: "greeterLoader"
369373 anchors.fill: parent
370374 anchors.topMargin: panel.panelHeight
371− sourceComponent: shell.mode != "shell" ? integratedGreeter :
372− Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
375+ sourceComponent: {
376+ if (shell.mode != "shell") {
377+ if (screenWindow.primary) return integratedGreeter;
378+ return secondaryGreeter;
379+ }
380+ return Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
381+ }
373382 onLoaded: {
374383 item.objectName = "greeter"
375384 }
@@ -419,6 +428,13 @@ StyledItem {
419428 }
420429 }
421430
431+ Component {
432+ id: secondaryGreeter
433+ SecondaryGreeter {
434+ hides: [launcher, panel.indicators]
435+ }
436+ }
437+
422438 Timer {
423439 // See powerConnection for why this is useful
424440 id: showGreeterDelayed
@@ -531,6 +547,7 @@ StyledItem {
531547 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
532548 || greeter.hasLockedApp
533549 greeterShown: greeter && greeter.shown
550+ panelState: panelState
534551 }
535552
536553 Launcher {
@@ -743,12 +760,13 @@ StyledItem {
743760
744761 Cursor {
745762 id: cursor
746− objectName: "cursor"
747− visible: shell.hasMouse
763+ objectName: "Pointer-"+screenWindow.objectName
764+
748765 z: itemGrabber.z + 1
766+ opacity: 0
749767 topBoundaryOffset: panel.panelHeight
750−
751− confiningItem: stage.itemConfiningMouseCursor
768+ enabled: shell.hasMouse && screenWindow.active
769+ visible: enabled
752770
753771 property bool mouseNeverMoved: true
754772 Binding {
@@ -760,6 +778,8 @@ StyledItem {
760778 when: cursor.mouseNeverMoved && cursor.visible
761779 }
762780
781+ confiningItem: stage.itemConfiningMouseCursor
782+
763783 height: units.gu(3)
764784
765785 readonly property var previewRectangle: stage.previewRectangle.target &&
@@ -846,4 +866,20 @@ StyledItem {
846866 }
847867 }
848868 }
869+
870+ Loader {
871+ z: shutdownFadeOutRectangle.z + 1
872+ active: DebuggingController.logOverlay
873+
874+ sourceComponent: Text {
875+ width: shell.width
876+ height: shell.height
877+
878+ text: consoleLog.out
879+ Debug.ConsoleLog {
880+ id: consoleLog
881+ enabled: true
882+ }
883+ }
884+ }
849885}
qml/ShellApplication.qmlnew file+52
@@ -0,0 +1,52 @@
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 Unity.Screens 0.1
20+import Unity.Application 0.1
21+
22+Instantiator {
23+ id: root
24+ model: Screens
25+
26+ property QtObject surfaceMan: SurfaceManager {}
27+
28+ ShellScreen {
29+ id: window
30+ objectName: "screen"+index
31+ screen: model.screen
32+ visibility: applicationArguments.hasFullscreen ? Window.FullScreen : Window.Windowed
33+ flags: applicationArguments.hasFrameless ? Qt.FramelessWindowHint : 0
34+ surfaceManager: surfaceMan
35+
36+ Binding {
37+ when: applicationArguments.hasGeometry
38+ target: window
39+ property: "width"
40+ value: applicationArguments.windowGeometry.width
41+ }
42+ Binding {
43+ when: applicationArguments.hasGeometry
44+ target: window
45+ property: "height"
46+ value: applicationArguments.windowGeometry.height
47+ }
48+
49+ Component.onCompleted: screen.active = primary
50+ primary: index == 0
51+ }
52+}
src/SecondaryWindow.h → qml/ShellNotifier.qmlrenamed+12−12
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,17 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_SECONDARY_WINDOW_H
18−#define UNITY_SECONDARY_WINDOW_H
19−
20−#include <QQuickView>
17+/*
18+ Shared signals & properties on multi-window desktop
19+ */
2120
22−class SecondaryWindow : public QQuickView
23−{
24− Q_OBJECT
21+pragma Singleton
22+import QtQuick 2.4
2523
26−public:
27− SecondaryWindow(QQmlEngine *engine);
28−};
24+QtObject {
25+ property var greeter: QtObject {
26+ signal hide(bool now)
2927
30−#endif // UNITY_SECONDARY_WINDOW_H
28+ property bool shown: true
29+ }
30+}
qml/ShellScreen.qmlnew file+65
@@ -0,0 +1,65 @@
1+/*
2+* Copyright (C) 2016 Canonical, Ltd.
3+*
4+* This program is free software; you can redistribute it and/or modify
5+* it under the terms of the GNU General Public License as published by
6+* the Free Software Foundation; version 3.
7+*
8+* This program is distributed in the hope that it will be useful,
9+* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+* GNU General Public License for more details.
12+*
13+* You should have received a copy of the GNU General Public License
14+* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Unity.Screens 0.1
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+ property QtObject surfaceManager: null
30+
31+ DeviceConfiguration {
32+ id: deviceConfiguration
33+ name: applicationArguments.deviceName
34+ }
35+
36+ 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+ surfaceManager: screenWindow.surfaceManager
54+
55+ deviceConfiguration {
56+ name: Screens.count > 1 ? "desktop" : applicationArguments.deviceName
57+ }
58+ }
59+ }
60+
61+ Component {
62+ id: disabledScreenComponent
63+ DisabledScreenNotice {}
64+ }
65+}
qml/Stage/DecoratedWindow.qml+3−1
@@ -72,6 +72,8 @@ FocusScope {
7272
7373 readonly property Item clientAreaItem: applicationWindow
7474
75+ property PanelState panelState
76+
7577 signal closeClicked()
7678 signal maximizeClicked()
7779 signal maximizeHorizontallyClicked()
@@ -225,7 +227,7 @@ FocusScope {
225227 enableMenus: {
226228 return active &&
227229 surface &&
228− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
230+ (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
229231 }
230232 menu: sharedAppModel.model
231233
qml/Stage/FakeMaximizeDelegate.qml+12−11
@@ -35,6 +35,7 @@ Rectangle {
3535 property int leftMargin
3636 property real appContainerWidth
3737 property real appContainerHeight
38+ property PanelState panelState
3839
3940 readonly property real hintThreshold: 0.1
4041
@@ -178,7 +179,7 @@ Rectangle {
178179 ParallelAnimation {
179180 id: fakeMaximizeAnimation
180181 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 }
182183 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183184 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184185 }
@@ -186,39 +187,39 @@ Rectangle {
186187 ParallelAnimation {
187188 id: fakeMaximizeLeftAnimation
188189 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 }
190191 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 }
192193 }
193194
194195 ParallelAnimation {
195196 id: fakeMaximizeRightAnimation
196197 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 }
198199 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 }
200201 }
201202
202203 ParallelAnimation {
203204 id: fakeMaximizeTopLeftAnimation
204205 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 }
206207 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 }
208209 }
209210
210211 ParallelAnimation {
211212 id: fakeMaximizeTopRightAnimation
212213 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 }
214215 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 }
216217 }
217218
218219 ParallelAnimation {
219220 id: fakeMaximizeBottomLeftAnimation
220221 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 }
222223 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223224 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224225 }
@@ -226,7 +227,7 @@ Rectangle {
226227 ParallelAnimation {
227228 id: fakeMaximizeBottomRightAnimation
228229 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 }
230231 UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231232 UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232233 }
qml/Stage/Spread/ScreensAndWorkspaces.qmlnew file+181
@@ -0,0 +1,181 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Ubuntu.Components.Popups 1.3
4+import Unity.Screens 0.1
5+import Unity.Application 0.1
6+import ".."
7+
8+Item {
9+ id: root
10+
11+ property string background
12+
13+ Row {
14+ id: row
15+ anchors.bottom: parent.bottom
16+ anchors.horizontalCenter: parent.horizontalCenter
17+ Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: UbuntuAnimation.SlowDuration } }
18+// anchors.left: parent.left
19+ spacing: units.gu(1)
20+
21+ Repeater {
22+ model: Screens
23+
24+ delegate: Item {
25+ height: root.height - units.gu(6)
26+ width: workspaces.width
27+ clip: true
28+
29+ UbuntuShape {
30+ id: header
31+ anchors { left: parent.left; top: parent.top; right: parent.right }
32+ height: units.gu(7)
33+ backgroundColor: "white"
34+ z: 1
35+
36+ DropArea {
37+ anchors.fill: parent
38+ keys: ["workspace"]
39+
40+ onEntered: {
41+ workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
42+ drag.source.inDropArea = true;
43+ }
44+
45+ onExited: {
46+ workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
47+ drag.source.inDropArea = false;
48+ }
49+
50+ onDropped: {
51+ drag.source.inDropArea = false;
52+ }
53+ }
54+
55+ Column {
56+ anchors.fill: parent
57+ anchors.margins: units.gu(1)
58+
59+ Label {
60+ text: model.screen.name
61+ color: "black"
62+ }
63+
64+ Label {
65+ text: model.screen.outputType === Screens.LVDS ? "Built-in" : "Clone"
66+ color: "black"
67+ fontSize: "x-small"
68+ }
69+
70+ Label {
71+ text: model.screen.physicalSize.width + "x" + model.screen.physicalSize.height
72+ color: "black"
73+ fontSize: "x-small"
74+ }
75+ }
76+
77+ MouseArea {
78+ anchors.fill: parent
79+// acceptedButtons: Qt.RightButton
80+
81+
82+ onClicked: {
83+ print("should open popup")
84+ PopupUtils.open(contextMenu)
85+ }
86+ }
87+
88+ Component {
89+ id: contextMenu
90+ ActionSelectionPopover {
91+ actions: ActionList {
92+ Action {
93+ text: "Add workspace"
94+ onTriggered: workspaces.workspaceModel.append({text: "" + (workspaces.workspaceModel.count + 1)})
95+ }
96+ }
97+ }
98+ }
99+ }
100+
101+
102+ Workspaces {
103+ id: workspaces
104+ height: parent.height - header.height - units.gu(2)
105+ width: Math.min(implicitWidth, index == 0 ? units.gu(80) : units.gu(40), root.width)
106+ Behavior on width { UbuntuNumberAnimation {} }
107+ anchors.bottom: parent.bottom
108+ anchors.bottomMargin: units.gu(1)
109+ anchors.horizontalCenter: parent.horizontalCenter
110+ screen: model.screen
111+ background: root.background
112+
113+ workspaceModel: ListModel {
114+ onCountChanged: print("model count changed to", count)
115+ ListElement {text: "1"}
116+ ListElement {text: "2"}
117+ ListElement {text: "3"}
118+ ListElement {text: "4"}
119+ ListElement {text: "5"}
120+ ListElement {text: "6"}
121+// ListElement {text: "7"}
122+// ListElement {text: "8"}
123+// ListElement {text: "9"}
124+ }
125+ }
126+ }
127+ }
128+ }
129+
130+ Rectangle {
131+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
132+ width: units.gu(5)
133+ color: "#33000000"
134+ visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
135+ MouseArea {
136+ id: leftScrollArea
137+ anchors.fill: parent
138+ hoverEnabled: true
139+ onPressed: mouse.accepted = false;
140+ }
141+ DropArea {
142+ id: leftFakeDropArea
143+ anchors.fill: parent
144+ keys: ["application", "workspace"]
145+ }
146+ }
147+ Rectangle {
148+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
149+ width: units.gu(5)
150+ color: "#33000000"
151+ visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
152+ MouseArea {
153+ id: rightScrollArea
154+ anchors.fill: parent
155+ hoverEnabled: true
156+ onPressed: mouse.accepted = false;
157+ }
158+ DropArea {
159+ id: rightFakeDropArea
160+ anchors.fill: parent
161+ keys: ["application", "workspace"]
162+ }
163+ }
164+ Timer {
165+ repeat: true
166+ running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
167+ interval: UbuntuAnimation.SlowDuration
168+ triggeredOnStart: true
169+ onTriggered: {
170+ var newOffset = row.anchors.horizontalCenterOffset;
171+ var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
172+ if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
173+ newOffset += units.gu(20)
174+ } else {
175+ newOffset -= units.gu(20)
176+ }
177+ newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
178+ row.anchors.horizontalCenterOffset = newOffset;
179+ }
180+ }
181+}
qml/Stage/Spread/Spread.qml+5−5
@@ -28,10 +28,10 @@ Item {
2828 property var spreadFlickable
2929
3030 // some config options
31− property real contentMargin: 0.16 * root.height
32− property real contentTopMargin: contentMargin
33− property real contentBottomMargin: 0.35 * contentMargin
34− property real windowTitleTopMargin: 3/4 * (contentTopMargin - windowTitle.height)
31+ property real contentMargin: 0.1 * root.height
32+ property real contentTopMargin: contentMargin + root.y + windowTitle.height
33+ property real contentBottomMargin: contentMargin
34+ property real windowTitleTopMargin: contentMargin - windowTitle.height
3535 property int stackItemCount: 3
3636 property real leftRotationAngle: 22
3737 property real rightRotationAngle: 32
@@ -51,7 +51,7 @@ Item {
5151
5252 readonly property real spreadWidth: rightStackXPos - leftStackXPos
5353 readonly property real spreadHeight: root.height
54− readonly property real spreadItemHeight: spreadHeight - contentTopMargin - contentBottomMargin
54+ readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
5555 readonly property real spreadItemWidth: stackHeight
5656
5757 readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
qml/Stage/Spread/SpreadDelegateInputArea.qml+24−11
@@ -85,15 +85,15 @@ Item {
8585 }
8686
8787 // Event eater
88− MouseArea {
89− anchors.fill: parent
90− onClicked: root.clicked()
91− onWheel: wheel.accepted = true
92− }
88+// MouseArea {
89+// anchors.fill: parent
90+// onClicked: root.clicked()
91+// onWheel: wheel.accepted = true
92+// }
9393
9494 MultiPointTouchArea {
9595 anchors.fill: parent
96− mouseEnabled: false
96+// mouseEnabled: false
9797 maximumTouchPoints: 1
9898 property int offset: 0
9999
@@ -109,7 +109,7 @@ Item {
109109 }
110110
111111 onTouchUpdated: {
112− if (!d.moving) {
112+ if (!d.moving || !tp.pressed) {
113113 if (Math.abs(tp.startY - tp.y) > d.threshold) {
114114 d.moving = true;
115115 d.dragEvents = []
@@ -119,17 +119,30 @@ Item {
119119 }
120120 }
121121
122− if (root.closeable) {
123− d.distance = tp.y - tp.startY - offset
122+
123+ var value = tp.y - tp.startY - offset;
124+ if (value < 0) {
125+ var coords = mapToItem(shell, tp.x, tp.y);
126+ fakeDragItem.x = coords.x
127+ fakeDragItem.y = coords.y
128+ fakeDragItem.Drag.active = true;
129+ fakeDragItem.surface = model.window.surface;
130+
124131 } else {
125− var value = tp.y - tp.startY - offset;
126− d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
132+ if (root.closeable) {
133+ d.distance = value
134+ } else {
135+ d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
136+ }
127137 }
128138
129139 d.pushDragEvent(tp);
130140 }
131141
132142 onReleased: {
143+ print("released!")
144+ fakeDragItem.surface = null;
145+
133146 if (!d.moving) {
134147 root.clicked()
135148 }
qml/Stage/Spread/WorkspacePreview.qmlnew file+220
@@ -0,0 +1,220 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Screens 0.1
4+import Unity.Application 0.1
5+import ".."
6+import "../../Components"
7+
8+Item {
9+ id: previewSpace
10+ clip: true
11+
12+ property string background
13+ property int screenHeight
14+
15+ property real previewScale: previewSpace.height / previewSpace.screenHeight
16+
17+ property bool containsDrag: false
18+
19+ Image {
20+ source: previewSpace.background
21+ anchors.fill: parent
22+ sourceSize.width: width
23+ sourceSize.height: height
24+
25+ Repeater {
26+ id: topLevelSurfaceRepeater
27+ model: visible ? topLevelSurfaceList : null
28+ delegate: Rectangle {
29+ width: surfaceItem.width
30+ height: surfaceItem.height + decorationHeight * previewScale
31+ x: model.window.position.x * previewScale
32+ y: (model.window.position.y - decorationHeight) * previewScale
33+ color: "blue"
34+ z: topLevelSurfaceRepeater.count - index
35+
36+ property int decorationHeight: units.gu(3)
37+
38+ WindowDecoration {
39+ width: surfaceItem.implicitWidth
40+ height: parent.decorationHeight
41+ transform: Scale {
42+ origin.x: 0
43+ origin.y: 0
44+ xScale: previewScale
45+ yScale: previewScale
46+ }
47+ title: model.window && model.window.surface ? model.window.surface.name : ""
48+ z: 3
49+ }
50+
51+ MirSurfaceItem {
52+ id: surfaceItem
53+ y: parent.decorationHeight * previewScale
54+ width: implicitWidth * previewScale
55+ height: implicitHeight * previewScale
56+ surfaceWidth: -1
57+ surfaceHeight: -1
58+ onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
59+ surface: model.window.surface
60+ }
61+ }
62+ }
63+
64+ }
65+
66+ Rectangle {
67+ anchors.fill: parent
68+ border.color: UbuntuColors.blue
69+ border.width: units.gu(.5)
70+ color: "transparent"
71+ visible: previewSpace.containsDrag
72+ }
73+}
74+
75+ // Repeater {
76+ // id: topLevelSurfaceRepeater
77+ // model: visible ? topLevelSurfaceList : null
78+ // delegate: Rectangle {
79+ // width: surfaceItem.width
80+ // height: surfaceItem.height + decorationHeight * previewScale
81+ // x: model.window.position.x * previewScale
82+ // y: (model.window.position.y - decorationHeight) * previewScale
83+ // color: "blue"
84+ // z: topLevelSurfaceRepeater.count - index
85+
86+ // property int decorationHeight: units.gu(3)
87+
88+ // WindowDecoration {
89+ // width: surfaceItem.implicitWidth
90+ // height: parent.decorationHeight
91+ // transform: Scale {
92+ // origin.x: 0
93+ // origin.y: 0
94+ // xScale: previewScale
95+ // yScale: previewScale
96+ // }
97+ // title: model.window && model.window.surface ? model.window.surface.name : ""
98+ // z: 3
99+ // }
100+
101+ // MirSurfaceItem {
102+ // id: surfaceItem
103+ // y: parent.decorationHeight * previewScale
104+ // width: implicitWidth * previewScale
105+ // height: implicitHeight * previewScale
106+ // surfaceWidth: -1
107+ // surfaceHeight: -1
108+ // onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
109+ // surface: model.window.surface
110+ // }
111+ // }
112+ // }
113+// }
114+// }
115+
116+
117+// MouseArea {
118+// id: mouseArea
119+// anchors.centerIn: parent
120+// anchors.verticalCenterOffset: header.height / 2
121+// width: units.gu(8)
122+// height: width
123+// hoverEnabled: true
124+// opacity: containsMouse ? 1 : 0
125+
126+// Rectangle {
127+// anchors.fill: parent
128+// color: "#80000000"
129+// radius: height / 2
130+// }
131+
132+// NumberAnimation {
133+// target: canvas
134+// property: "progress"
135+// duration: 2000
136+// running: mouseArea.containsMouse
137+// from: 0
138+// to: 1
139+// }
140+
141+// Canvas {
142+// id: canvas
143+// height: parent.height
144+// width: height
145+// anchors.centerIn: parent
146+
147+// property real progress: 0.5
148+// property int lineWidth: units.dp(4)
149+// onProgressChanged: {
150+// requestPaint();
151+// if (progress == 1) {
152+// Screens.activateScreen(index);
153+// }
154+// }
155+
156+// rotation: -90
157+
158+// onPaint: {
159+// var ctx = canvas.getContext("2d");
160+// ctx.save();
161+// ctx.reset();
162+
163+// ctx.lineWidth = lineWidth;
164+
165+// ctx.strokeStyle = "#3bffffff"
166+// ctx.beginPath();
167+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2),false);
168+// ctx.stroke();
169+// ctx.closePath();
170+
171+// ctx.strokeStyle = "white"
172+// ctx.beginPath();
173+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2*(progress)),false);
174+// ctx.stroke();
175+// ctx.closePath();
176+
177+// ctx.restore();
178+// }
179+// }
180+
181+// Icon {
182+// source: "graphics/multi-monitor_leave.png"
183+// height: units.gu(4)
184+// width: height
185+// anchors.centerIn: parent
186+// }
187+// }
188+// }
189+
190+// FloatingFlickable {
191+// id: flickable
192+// anchors.fill: parent
193+// contentWidth: workspaceRepeater.count * previewSpace.screenWidth * previewScale
194+// property real progress: contentX / contentWidth
195+// onContentXChanged: print("contentX:", contentX, "progress:", progress)
196+// }
197+
198+// MouseArea {
199+// anchors.fill: parent
200+// hoverEnabled: true
201+// property int scrollAreaWidth: width / 3
202+
203+// onMouseXChanged: {
204+// var margins = flickable.width * 0.05;
205+
206+// // do we need to scroll?
207+// if (mouseX < scrollAreaWidth + margins) {
208+// var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
209+// var contentX = (1 - progress) * (flickable.contentWidth - flickable.width)
210+// flickable.contentX = Math.max(0, Math.min(flickable.contentX, contentX))
211+// }
212+// if (mouseX > flickable.width - scrollAreaWidth) {
213+// var progress = Math.min(1, (mouseX - (flickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
214+// var contentX = progress * (flickable.contentWidth - flickable.width)
215+// flickable.contentX = Math.min(flickable.contentWidth - flickable.width, Math.max(flickable.contentX, contentX))
216+// }
217+// }
218+// }
219+// }
220+//}
qml/Stage/Spread/Workspaces.qmlnew file+316
@@ -0,0 +1,316 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "MathUtils.js" as MathUtils
4+import "../../Components"
5+
6+Item {
7+ id: root
8+ implicitWidth: listView.count * (height * 16/9 + listView.spacing) - listView.spacing
9+// color: "blue"
10+
11+ property QtObject screen: null
12+ property alias workspaceModel: listView.model
13+ property var background // TODO: should be stored in the workspace data
14+
15+ DropArea {
16+ anchors.fill: root
17+
18+ keys: ['workspace']
19+
20+ onEntered: {
21+ var index = listView.getWorkspaceDropIndex(drag);
22+ listView.model.insert(index, {text: drag.source.text})
23+ listView.dropItemIndex = index;
24+ drag.source.inDropArea = true;
25+ }
26+
27+ onPositionChanged: {
28+ var index = listView.getWorkspaceDropIndex(drag);
29+ if (listView.dropItemIndex == index) return;
30+ listView.model.move(listView.dropItemIndex, index, 1);
31+ listView.dropItemIndex = index;
32+ }
33+
34+ onExited: {
35+ listView.model.remove(listView.dropItemIndex, 1);
36+ listView.dropItemIndex = -1;
37+ drag.source.inDropArea = false;
38+ }
39+
40+ onDropped: {
41+ listView.dropItemIndex = -1;
42+ drag.source.inDropArea = false;
43+ }
44+ }
45+ DropArea {
46+ anchors.fill: parent
47+ keys: ["application"]
48+
49+ onPositionChanged: {
50+ listView.progressiveScroll(drag.x)
51+ listView.hoveredWorkspaceIndex = listView.getApplicationDropIndex(drag)
52+ }
53+ onExited: {
54+ listView.hoveredWorkspaceIndex = -1
55+ }
56+ }
57+
58+ ListView {
59+ id: listView
60+ anchors.fill: parent
61+ anchors.leftMargin: -itemWidth
62+ anchors.rightMargin: -itemWidth
63+// interactive: false
64+
65+ orientation: ListView.Horizontal
66+ spacing: units.gu(1)
67+ leftMargin: itemWidth
68+ rightMargin: itemWidth
69+
70+ property int itemWidth: height * 16 / 9
71+ property int foldingAreaWidth: units.gu(10)
72+ property real realContentX: contentX - originX + leftMargin
73+ property int dropItemIndex: -1
74+ property int hoveredWorkspaceIndex: -1
75+
76+ function getWorkspaceDropIndex(drag) {
77+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
78+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
79+ if (index < 0) index = 0;
80+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
81+ if (index > upperLimit) index = upperLimit;
82+ return index;
83+ }
84+
85+ function getApplicationDropIndex(drag) {
86+ var coords = mapToItem(listView.contentItem, drag.x, drag.y)
87+ var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
88+ if (index < 0) index = 0;
89+ var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
90+ if (index > upperLimit) index = upperLimit;
91+ return index;
92+ }
93+
94+ function progressiveScroll(mouseX) {
95+ var progress = Math.max(0, Math.min(1, (mouseX - listView.foldingAreaWidth) / (width - listView.leftMargin * 2 - listView.foldingAreaWidth * 2)))
96+ print("p", progress, mouseX)
97+ listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
98+ }
99+
100+ displaced: Transition { UbuntuNumberAnimation { properties: "x" } }
101+
102+ delegate: Item {
103+ objectName: "delegate" + index
104+ height: parent.height
105+ width: listView.itemWidth
106+ Behavior on width { UbuntuNumberAnimation {} }
107+ visible: listView.dropItemIndex !== index
108+
109+ property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
110+ property int distanceFromLeft: itemX //- listView.leftMargin
111+ property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
112+
113+ property int maxAngle: 40
114+
115+ property int itemAngle: {
116+ if (index == 0) {
117+ if (distanceFromLeft < 0) {
118+ var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
119+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
120+ }
121+ return 0
122+ }
123+ if (index == listView.count - 1) {
124+ if (distanceFromRight < 0) {
125+ var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
126+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
127+ }
128+ return 0
129+ }
130+
131+ if (distanceFromLeft < listView.foldingAreaWidth) {
132+ // itemX : 10gu = p : 100
133+ var progress = distanceFromLeft / listView.foldingAreaWidth
134+ return MathUtils.linearAnimation(1, -1, 0, maxAngle, Math.max(-1, Math.min(1, progress)));
135+ }
136+ if (distanceFromRight < listView.foldingAreaWidth) {
137+ var progress = distanceFromRight / listView.foldingAreaWidth
138+ return MathUtils.linearAnimation(1, -1, 0, -maxAngle, Math.max(-1, Math.min(1, progress)));
139+ }
140+ return 0
141+ }
142+
143+ property int itemOffset: {
144+ var rotationOffset = (1 - Math.cos(itemAngle * Math.PI / 360)) * listView.itemWidth
145+ if (index == 0) {
146+ if (distanceFromLeft < 0) {
147+ return -distanceFromLeft - rotationOffset
148+ }
149+ return 0
150+ }
151+ if (index == listView.count - 1) {
152+ if (distanceFromRight < 0) {
153+ return distanceFromRight + rotationOffset
154+ }
155+ return 0
156+ }
157+
158+ if (itemX < -listView.foldingAreaWidth) {
159+ return -itemX - rotationOffset
160+ }
161+ if (distanceFromLeft < listView.foldingAreaWidth) {
162+ return (listView.foldingAreaWidth - distanceFromLeft) / 2 - rotationOffset
163+ }
164+
165+ if (distanceFromRight < -listView.foldingAreaWidth) {
166+ return distanceFromRight + rotationOffset
167+ }
168+
169+ if (distanceFromRight < listView.foldingAreaWidth) {
170+ return -(listView.foldingAreaWidth - distanceFromRight) / 2 + rotationOffset
171+ }
172+
173+ return 0
174+ }
175+
176+// onItemXChanged: if (index == 1) print("x", itemX, listView.contentX)
177+
178+ z: itemOffset < 0 ? itemOffset : -itemOffset
179+ transform: [
180+ Rotation {
181+ angle: itemAngle
182+ axis { x: 0; y: 1; z: 0 }
183+ origin { x: listView.itemWidth / 2; y: height / 2 }
184+ },
185+ Translate {
186+ x: itemOffset
187+ }
188+ ]
189+
190+ WorkspacePreview {
191+ height: listView.height
192+ width: listView.itemWidth
193+ background: root.background
194+ screenHeight: root.screen.physicalSize.height
195+ containsDrag: listView.hoveredWorkspaceIndex == index
196+
197+ Label {
198+ anchors.centerIn: parent
199+ text: model.text
200+ color: "red"
201+ fontSize: "large"
202+ }
203+ }
204+ }
205+
206+ MouseArea {
207+ id: mouseArea
208+ anchors.fill: parent
209+ hoverEnabled: true
210+ propagateComposedEvents: true
211+ anchors.leftMargin: listView.leftMargin
212+ anchors.rightMargin: listView.rightMargin
213+
214+ property int draggedIndex: -1
215+
216+ property int startX: 0
217+ property int startY: 0
218+
219+ onMouseXChanged: {
220+ if (!pressed || dragging) {
221+ listView.progressiveScroll(mouseX)
222+ }
223+ }
224+ onMouseYChanged: {
225+ if (Math.abs(mouseY - startY) > units.gu(3)) {
226+ drag.axis = Drag.XAndYAxis;
227+ }
228+ }
229+
230+ onReleased: {
231+ fakeDragItem.Drag.drop();
232+ drag.target = null;
233+ }
234+
235+ property bool dragging: drag.active
236+ onDraggingChanged: {
237+ if (drag.active) {
238+ print("drai", draggedIndex)
239+ listView.model.remove(draggedIndex, 1)
240+ }
241+ }
242+
243+ onPressed: {
244+ startX = mouseX;
245+ startY = mouseY;
246+ if (listView.model.count < 2) return;
247+
248+ var coords = mapToItem(listView.contentItem, mouseX, mouseY)
249+ draggedIndex = listView.indexAt(coords.x, coords.y)
250+ var clickedItem = listView.itemAt(coords.x, coords.y)
251+
252+ var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
253+ fakeDragItem.x = itemCoords.x
254+ fakeDragItem.y = itemCoords.y
255+ fakeDragItem.text = listView.model.get(draggedIndex).text
256+
257+ var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
258+ fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
259+ fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
260+
261+ drag.axis = Drag.YAxis;
262+ drag.target = fakeDragItem;
263+ }
264+
265+ WorkspacePreview {
266+ id: fakeDragItem
267+ height: listView.height
268+ width: listView.itemWidth
269+ background: root.background
270+ screenHeight: root.screen.physicalSize.height
271+ visible: Drag.active
272+
273+ Drag.active: mouseArea.drag.active
274+ Drag.keys: ['workspace']
275+
276+ property string text
277+ property bool inDropArea: false
278+ Label {
279+ anchors.centerIn: parent
280+ text: parent.text
281+ color: "red"
282+ fontSize: "large"
283+ }
284+
285+ Rectangle {
286+ anchors.fill: parent
287+ color: "#33000000"
288+ opacity: parent.inDropArea ? 0 : 1
289+ Behavior on opacity { UbuntuNumberAnimation { } }
290+ Rectangle {
291+ anchors.centerIn: parent
292+ width: units.gu(6)
293+ height: units.gu(6)
294+ radius: width / 2
295+ color: "#aa000000"
296+ }
297+
298+ Icon {
299+ height: units.gu(3)
300+ width: height
301+ anchors.centerIn: parent
302+ name: "edit-delete"
303+ color: "white"
304+ }
305+ }
306+
307+ states: [
308+ State {
309+ when: fakeDragItem.Drag.active
310+ ParentChange { target: fakeDragItem; parent: shell }
311+ }
312+ ]
313+ }
314+ }
315+ }
316+}
qml/Stage/Stage.qml+137−103
@@ -46,6 +46,7 @@ FocusScope {
4646 property int leftMargin: 0
4747 property bool oskEnabled: false
4848 property rect inputMethodRect
49+ property PanelState panelState
4950
5051 // Configuration
5152 property string mode: "staged"
@@ -345,23 +346,23 @@ FocusScope {
345346 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
346347 }
347348
348− Component.onCompleted: priv.updateMainAndSideStageIndexes();
349+ Component.onCompleted: {priv.updateMainAndSideStageIndexes(); print("size", root.width, root.height)}
349350
350351 Connections {
351− target: PanelState
352+ target: panelState
352353 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
353354 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
354355 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
355356 }
356357
357358 Binding {
358− target: PanelState
359+ target: panelState
359360 property: "decorationsVisible"
360361 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
361362 }
362363
363364 Binding {
364− target: PanelState
365+ target: panelState
365366 property: "title"
366367 value: {
367368 if (priv.focusedAppDelegate !== null) {
@@ -376,7 +377,7 @@ FocusScope {
376377 }
377378
378379 Binding {
379− target: PanelState
380+ target: panelState
380381 property: "focusedPersistentSurfaceId"
381382 value: {
382383 if (priv.focusedAppDelegate !== null) {
@@ -390,21 +391,21 @@ FocusScope {
390391 }
391392
392393 Binding {
393− target: PanelState
394+ target: panelState
394395 property: "dropShadow"
395396 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
396397 }
397398
398399 Binding {
399− target: PanelState
400+ target: panelState
400401 property: "closeButtonShown"
401402 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
402403 }
403404
404405 Component.onDestruction: {
405− PanelState.title = "";
406− PanelState.decorationsVisible = false;
407− PanelState.dropShadow = false;
406+ panelState.title = "";
407+ panelState.decorationsVisible = false;
408+ panelState.dropShadow = false;
408409 }
409410
410411 Instantiator {
@@ -450,6 +451,7 @@ FocusScope {
450451 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
451452 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
452453 PropertyChanges { target: wallpaper; visible: false }
454+ PropertyChanges { target: screensAndWorkspaces; opacity: 1 }
453455 },
454456 State {
455457 name: "stagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "staged"
@@ -482,7 +484,7 @@ FocusScope {
482484 },
483485 State {
484486 name: "staged"; when: root.mode === "staged"
485− PropertyChanges { target: wallpaper; visible: false }
487+ PropertyChanges { target: wallpaper; visible: priv.focusedAppDelegate && priv.focusedAppDelegate.x === 0 }
486488 },
487489 State {
488490 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
@@ -498,11 +500,13 @@ FocusScope {
498500 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
499501 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
500502 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
503+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
501504 },
502505 Transition {
503506 to: "spread"
504507 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
505508 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
509+ UbuntuNumberAnimation { target: screensAndWorkspaces; property: "opacity"; duration: priv.animationDuration }
506510 },
507511 Transition {
508512 from: "spread"
@@ -559,10 +563,19 @@ FocusScope {
559563 visible: false
560564 }
561565
566+ ScreensAndWorkspaces {
567+ id: screensAndWorkspaces
568+ anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.leftMargin }
569+ height: parent.height / 3
570+ background: root.background
571+ opacity: 0
572+ visible: opacity > 0
573+ }
574+
562575 Spread {
563576 id: spreadItem
564577 objectName: "spreadItem"
565− anchors.fill: appContainer
578+ anchors { left: parent.left; bottom: parent.bottom; right: parent.right; top: screensAndWorkspaces.bottom }
566579 leftMargin: root.leftMargin
567580 model: root.topLevelSurfaceList
568581 spreadFlickable: floatingFlickable
@@ -571,6 +584,83 @@ FocusScope {
571584 onLeaveSpread: {
572585 priv.goneToSpread = false;
573586 }
587+
588+ FloatingFlickable {
589+ id: floatingFlickable
590+ objectName: "spreadFlickable"
591+ anchors.fill: parent
592+ enabled: false
593+ contentWidth: spreadItem.spreadTotalWidth
594+
595+ function snap(toIndex) {
596+ var delegate = appRepeater.itemAt(toIndex)
597+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
598+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
599+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
600+ snapAnimation.to = floatingFlickable.contentX - offset;
601+ snapAnimation.start();
602+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
603+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
604+ snapAnimation.to = floatingFlickable.contentX - offset;
605+ snapAnimation.start();
606+ }
607+ }
608+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
609+ }
610+
611+ MouseArea {
612+ id: hoverMouseArea
613+ objectName: "hoverMouseArea"
614+ anchors.fill: parent
615+ propagateComposedEvents: true
616+ hoverEnabled: true
617+ enabled: false
618+ visible: enabled
619+
620+ property int scrollAreaWidth: width / 3
621+ property bool progressiveScrollingEnabled: false
622+
623+ onPressed: mouse.accepted = false
624+
625+ onMouseXChanged: {
626+ mouse.accepted = false
627+
628+ if (hoverMouseArea.pressed) {
629+ return;
630+ }
631+
632+ // Find the hovered item and mark it active
633+ for (var i = appRepeater.count - 1; i >= 0; i--) {
634+ var appDelegate = appRepeater.itemAt(i);
635+ var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
636+ var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
637+ if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
638+ spreadItem.highlightedIndex = i;
639+ break;
640+ }
641+ }
642+
643+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
644+ var margins = floatingFlickable.width * 0.05;
645+
646+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
647+ progressiveScrollingEnabled = true
648+ }
649+
650+ // do we need to scroll?
651+ if (mouseX < scrollAreaWidth + margins) {
652+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
653+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
654+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
655+ }
656+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
657+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
658+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
659+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
660+ }
661+ }
662+ }
663+ }
574664 }
575665
576666 Connections {
@@ -665,6 +755,23 @@ FocusScope {
665755 }
666756 }
667757
758+ MirSurfaceItem {
759+ id: fakeDragItem
760+ property real previewScale: .5
761+ width: implicitWidth * previewScale
762+ height: implicitHeight * previewScale
763+ surfaceWidth: -1
764+ surfaceHeight: -1
765+ opacity: surface != null ? 1 : 0
766+ Behavior on opacity { UbuntuNumberAnimation {} }
767+ visible: opacity > 0
768+
769+ Drag.active: surface != null
770+ Drag.keys: ["application"]
771+
772+ z: 1000
773+ }
774+
668775 Item {
669776 id: boundariesForWindowPlacement
670777 anchors.fill: parent
@@ -700,6 +807,9 @@ FocusScope {
700807 }
701808 z: normalZ
702809
810+ opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
811+ Behavior on opacity { UbuntuNumberAnimation {} }
812+
703813 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
704814 // match what the actual surface size is.
705815 // Don't write to those, they will be set by states
@@ -760,7 +870,7 @@ FocusScope {
760870 target: appDelegate
761871 property: "y"
762872 value: appDelegate.requestedY -
763− Math.min(appDelegate.requestedY - PanelState.panelHeight,
873+ Math.min(appDelegate.requestedY - panelState.panelHeight,
764874 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
765875 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
766876 && root.inputMethodRect.height > 0
@@ -1136,7 +1246,7 @@ FocusScope {
11361246 progress: 0
11371247 targetHeight: spreadItem.stackHeight
11381248 targetX: spreadMaths.targetX
1139− startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1249+ startY: appDelegate.fullscreen ? 0 : panelState.panelHeight
11401250 targetY: spreadMaths.targetY
11411251 targetAngle: spreadMaths.targetAngle
11421252 targetScale: spreadMaths.targetScale
@@ -1255,9 +1365,9 @@ FocusScope {
12551365 PropertyChanges {
12561366 target: appDelegate
12571367 x: stageMaths.itemX
1258− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1368+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12591369 requestedWidth: appContainer.width
1260− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1370+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
12611371 visuallyMaximized: true
12621372 visible: appDelegate.x < root.width
12631373 }
@@ -1283,10 +1393,10 @@ FocusScope {
12831393 PropertyChanges {
12841394 target: appDelegate
12851395 x: stageMaths.itemX
1286− y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1396+ y: appDelegate.fullscreen ? 0 : panelState.panelHeight
12871397 z: stageMaths.itemZ
12881398 requestedWidth: stageMaths.itemWidth
1289− requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1399+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - panelState.panelHeight
12901400 visuallyMaximized: true
12911401 visible: appDelegate.x < root.width
12921402 }
@@ -1351,9 +1461,9 @@ FocusScope {
13511461 PropertyChanges {
13521462 target: appDelegate
13531463 windowedX: root.leftMargin
1354− windowedY: PanelState.panelHeight
1464+ windowedY: panelState.panelHeight
13551465 windowedWidth: (appContainer.width - root.leftMargin)/2
1356− windowedHeight: appContainer.height - PanelState.panelHeight
1466+ windowedHeight: appContainer.height - panelState.panelHeight
13571467 }
13581468 },
13591469 State {
@@ -1370,9 +1480,9 @@ FocusScope {
13701480 PropertyChanges {
13711481 target: appDelegate
13721482 windowedX: root.leftMargin
1373− windowedY: PanelState.panelHeight
1483+ windowedY: panelState.panelHeight
13741484 windowedWidth: (appContainer.width - root.leftMargin)/2
1375− windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1485+ windowedHeight: (appContainer.height - panelState.panelHeight)/2
13761486 }
13771487 },
13781488 State {
@@ -1389,7 +1499,7 @@ FocusScope {
13891499 PropertyChanges {
13901500 target: appDelegate
13911501 windowedX: root.leftMargin
1392− windowedY: (appContainer.height + PanelState.panelHeight)/2
1502+ windowedY: (appContainer.height + panelState.panelHeight)/2
13931503 windowedWidth: (appContainer.width - root.leftMargin)/2
13941504 windowedHeight: appContainer.height/2
13951505 }
@@ -1411,8 +1521,8 @@ FocusScope {
14111521 State {
14121522 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
14131523 extend: "normal"
1414− PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1415− windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1524+ PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: panelState.panelHeight;
1525+ windowedWidth: windowedWidth; windowedHeight: appContainer.height - panelState.panelHeight }
14161526 },
14171527 State {
14181528 name: "minimized"; when: appDelegate.minimized
@@ -1511,7 +1621,7 @@ FocusScope {
15111621 ]
15121622
15131623 Binding {
1514− target: PanelState
1624+ target: panelState
15151625 property: "decorationsAlwaysVisible"
15161626 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
15171627 }
@@ -1754,6 +1864,7 @@ FocusScope {
17541864 leftMargin: root.leftMargin
17551865 appContainerWidth: appContainer.width
17561866 appContainerHeight: appContainer.height
1867+ panelState: root.panelState
17571868 }
17581869
17591870 EdgeBarrier {
@@ -1780,83 +1891,6 @@ FocusScope {
17801891 }
17811892 }
17821893
1783− MouseArea {
1784− id: hoverMouseArea
1785− objectName: "hoverMouseArea"
1786− anchors.fill: appContainer
1787− propagateComposedEvents: true
1788− hoverEnabled: true
1789− enabled: false
1790− visible: enabled
1791−
1792− property int scrollAreaWidth: width / 3
1793− property bool progressiveScrollingEnabled: false
1794−
1795− onMouseXChanged: {
1796− mouse.accepted = false
1797−
1798− if (hoverMouseArea.pressed) {
1799− return;
1800− }
1801−
1802− // Find the hovered item and mark it active
1803− for (var i = appRepeater.count - 1; i >= 0; i--) {
1804− var appDelegate = appRepeater.itemAt(i);
1805− var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1806− var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1807− if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1808− spreadItem.highlightedIndex = i;
1809− break;
1810− }
1811− }
1812−
1813− if (floatingFlickable.contentWidth > floatingFlickable.width) {
1814− var margins = floatingFlickable.width * 0.05;
1815−
1816− if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1817− progressiveScrollingEnabled = true
1818− }
1819−
1820− // do we need to scroll?
1821− if (mouseX < scrollAreaWidth + margins) {
1822− var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1823− var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1824− floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1825− }
1826− if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1827− var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1828− var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1829− floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1830− }
1831− }
1832− }
1833−
1834− onPressed: mouse.accepted = false
1835− }
1836−
1837− FloatingFlickable {
1838− id: floatingFlickable
1839− objectName: "spreadFlickable"
1840− anchors.fill: appContainer
1841− enabled: false
1842− contentWidth: spreadItem.spreadTotalWidth
1843−
1844− function snap(toIndex) {
1845− var delegate = appRepeater.itemAt(toIndex)
1846− var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1847− if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1848− var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1849− snapAnimation.to = floatingFlickable.contentX - offset;
1850− snapAnimation.start();
1851− } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1852− var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1853− snapAnimation.to = floatingFlickable.contentX - offset;
1854− snapAnimation.start();
1855− }
1856− }
1857− UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1858− }
1859−
18601894 PropertyAnimation {
18611895 id: shortRightEdgeSwipeAnimation
18621896 property: "x"
qml/Stage/SurfaceContainer.qml+2
@@ -66,6 +66,8 @@ FocusScope {
6666 fillMode: MirSurfaceItem.PadOrCrop
6767 consumesInput: true
6868
69+ onSurfaceChanged: print("surface changed", surface, implicitWidth, implicitHeight)
70+
6971 surfaceWidth: root.requestedWidth
7072 surfaceHeight: root.requestedHeight
7173
qml/Stage/WorkspacePreview.qmlnew file+212
@@ -0,0 +1,212 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Screens 0.1
4+import Unity.Application 0.1
5+import "../Components"
6+
7+UbuntuShape {
8+ id: previewSpace
9+
10+ // Set the height. this preview will then automatically adjust the width, keeping aspect ratio of the screen
11+ width: model.geometry.width * previewScale * 4
12+ color: "white"
13+ property string background
14+
15+ property int screenWidth: model.geometry.width
16+ property int screenHeight: model.geometry.height
17+ property real previewScale: (previewSpace.height - header.height) / previewSpace.screenHeight
18+
19+ Item {
20+ id: header
21+ anchors { left: parent.left; top: parent.top; right: parent.right }
22+ height: units.gu(7)
23+
24+ Column {
25+ anchors.fill: parent
26+ anchors.margins: units.gu(1)
27+
28+ Label {
29+ text: model.name
30+ color: "black"
31+ }
32+
33+ Label {
34+ text: model.outputType === Screens.LVDS ? "Built-in" : "Clone"
35+ color: "black"
36+ fontSize: "x-small"
37+ }
38+
39+ Label {
40+ text: model.geometry.width + "x" + model.geometry.height
41+ color: "black"
42+ fontSize: "x-small"
43+ }
44+ }
45+ }
46+
47+
48+ Rectangle {
49+ anchors.fill: parent
50+ anchors.topMargin: header.height
51+ color: "blue"
52+
53+ Repeater {
54+ id: workspaceRepeater
55+ model: 15
56+
57+ delegate: Item {
58+ x: index * previewSpace.screenWidth * previewScale
59+ height: previewSpace.screenHeight * previewScale
60+ width: previewSpace.screenWidth * previewScale
61+ clip: true
62+
63+ Wallpaper {
64+ source: previewSpace.background
65+ anchors.fill: parent
66+
67+ // Repeater {
68+ // id: topLevelSurfaceRepeater
69+ // model: visible ? topLevelSurfaceList : null
70+ // delegate: Rectangle {
71+ // width: surfaceItem.width
72+ // height: surfaceItem.height + decorationHeight * previewScale
73+ // x: model.window.position.x * previewScale
74+ // y: (model.window.position.y - decorationHeight) * previewScale
75+ // color: "blue"
76+ // z: topLevelSurfaceRepeater.count - index
77+
78+ // property int decorationHeight: units.gu(3)
79+
80+ // WindowDecoration {
81+ // width: surfaceItem.implicitWidth
82+ // height: parent.decorationHeight
83+ // transform: Scale {
84+ // origin.x: 0
85+ // origin.y: 0
86+ // xScale: previewScale
87+ // yScale: previewScale
88+ // }
89+ // title: model.window && model.window.surface ? model.window.surface.name : ""
90+ // z: 3
91+ // }
92+
93+ // MirSurfaceItem {
94+ // id: surfaceItem
95+ // y: parent.decorationHeight * previewScale
96+ // width: implicitWidth * previewScale
97+ // height: implicitHeight * previewScale
98+ // surfaceWidth: -1
99+ // surfaceHeight: -1
100+ // onImplicitHeightChanged: print("item", surfaceItem, "height changed", implicitHeight)
101+ // surface: model.window.surface
102+ // }
103+ // }
104+ // }
105+ }
106+ }
107+
108+
109+// MouseArea {
110+// id: mouseArea
111+// anchors.centerIn: parent
112+// anchors.verticalCenterOffset: header.height / 2
113+// width: units.gu(8)
114+// height: width
115+// hoverEnabled: true
116+// opacity: containsMouse ? 1 : 0
117+
118+// Rectangle {
119+// anchors.fill: parent
120+// color: "#80000000"
121+// radius: height / 2
122+// }
123+
124+// NumberAnimation {
125+// target: canvas
126+// property: "progress"
127+// duration: 2000
128+// running: mouseArea.containsMouse
129+// from: 0
130+// to: 1
131+// }
132+
133+// Canvas {
134+// id: canvas
135+// height: parent.height
136+// width: height
137+// anchors.centerIn: parent
138+
139+// property real progress: 0.5
140+// property int lineWidth: units.dp(4)
141+// onProgressChanged: {
142+// requestPaint();
143+// if (progress == 1) {
144+// Screens.activateScreen(index);
145+// }
146+// }
147+
148+// rotation: -90
149+
150+// onPaint: {
151+// var ctx = canvas.getContext("2d");
152+// ctx.save();
153+// ctx.reset();
154+
155+// ctx.lineWidth = lineWidth;
156+
157+// ctx.strokeStyle = "#3bffffff"
158+// ctx.beginPath();
159+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2),false);
160+// ctx.stroke();
161+// ctx.closePath();
162+
163+// ctx.strokeStyle = "white"
164+// ctx.beginPath();
165+// ctx.arc(canvas.width/2,canvas.height/2, (canvas.height - ctx.lineWidth) / 2, 0, (Math.PI*2*(progress)),false);
166+// ctx.stroke();
167+// ctx.closePath();
168+
169+// ctx.restore();
170+// }
171+// }
172+
173+// Icon {
174+// source: "graphics/multi-monitor_leave.png"
175+// height: units.gu(4)
176+// width: height
177+// anchors.centerIn: parent
178+// }
179+// }
180+ }
181+
182+ FloatingFlickable {
183+ id: flickable
184+ anchors.fill: parent
185+ contentWidth: workspaceRepeater.count * previewSpace.screenWidth * previewScale
186+ property real progress: contentX / contentWidth
187+ onContentXChanged: print("contentX:", contentX, "progress:", progress)
188+ }
189+
190+ MouseArea {
191+ anchors.fill: parent
192+ hoverEnabled: true
193+ property int scrollAreaWidth: width / 3
194+
195+ onMouseXChanged: {
196+ var margins = flickable.width * 0.05;
197+
198+ // do we need to scroll?
199+ if (mouseX < scrollAreaWidth + margins) {
200+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
201+ var contentX = (1 - progress) * (flickable.contentWidth - flickable.width)
202+ flickable.contentX = Math.max(0, Math.min(flickable.contentX, contentX))
203+ }
204+ if (mouseX > flickable.width - scrollAreaWidth) {
205+ var progress = Math.min(1, (mouseX - (flickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
206+ var contentX = progress * (flickable.contentWidth - flickable.width)
207+ flickable.contentX = Math.min(flickable.contentWidth - flickable.width, Math.max(flickable.contentX, contentX))
208+ }
209+ }
210+ }
211+ }
212+}
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 @@
1616
1717#include "ApplicationArguments.h"
1818
19−ApplicationArguments::ApplicationArguments(QObject *parent)
20− : QObject(parent)
19+ApplicationArguments::ApplicationArguments(QCoreApplication *app)
20+ : QObject(app)
21+ , UnityCommandLineParser(*app)
2122{
2223}
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 @@
2323#include <QSize>
2424#include <QString>
2525
26−class ApplicationArguments : public QObject
26+#include "UnityCommandLineParser.h"
27+
28+class ApplicationArguments : public QObject,
29+ public UnityCommandLineParser
2730{
2831 Q_OBJECT
2932 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3033 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+
3144public:
32− ApplicationArguments(QObject *parent = nullptr);
45+ ApplicationArguments(QCoreApplication *app);
3346
34− 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; }
47+ void setDeviceName(const QString &deviceName);
4148
42− void setMode(const QString &mode) { m_mode = mode; }
43− QString mode() const { return m_mode; }
49+ bool hasGeometry() const { return m_windowGeometry.isValid(); }
4450
4551Q_SIGNALS:
4652 void deviceNameChanged(const QString&);
47−
48−private:
49− QString m_deviceName;
50− QString m_mode;
5153};
5254
5355#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt−2
@@ -27,9 +27,7 @@ set(SOURCE_FILES
2727 ApplicationArguments.cpp
2828 main.cpp
2929 CachingNetworkManagerFactory.cpp
30− SecondaryWindow.cpp
3130 ShellApplication.cpp
32− ShellView.cpp
3331 UnityCommandLineParser.cpp
3432 UnixSignalHandler.cpp
3533 DebuggingController.cpp
src/DebuggingController.cpp+12−1
@@ -49,7 +49,8 @@ public:
4949};
5050
5151DebuggingController::DebuggingController(QObject *parent):
52− UnityDBusObject(QStringLiteral("/com/canonical/Unity8/Debugging"), QStringLiteral("com.canonical.Unity8"), true, parent)
52+ UnityDBusObject(QStringLiteral("/com/canonical/Unity8/Debugging"), QStringLiteral("com.canonical.Unity8"), true, parent),
53+ m_logOverlay(false)
5354{
5455}
5556
@@ -90,3 +91,13 @@ void DebuggingController::SetLoggingFilterRules(const QString &filterRules)
9091{
9192 QLoggingCategory::setFilterRules(filterRules);
9293}
94+
95+void DebuggingController::SetLogOverlay(bool logOverlay)
96+{
97+ if (logOverlay == m_logOverlay) {
98+ return;
99+ }
100+
101+ m_logOverlay = logOverlay;
102+ Q_EMIT logOverlayChanged(m_logOverlay);
103+}
src/DebuggingController.h+14
@@ -32,10 +32,14 @@ class DebuggingController: public UnityDBusObject
3232 Q_OBJECT
3333 Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity8.Debugging")
3434
35+ Q_PROPERTY(bool logOverlay READ logOverlay NOTIFY logOverlayChanged)
36+
3537public:
3638 DebuggingController(QObject *parent = nullptr);
3739 ~DebuggingController() = default;
3840
41+ bool logOverlay() const { return m_logOverlay; }
42+
3943public Q_SLOTS:
4044 /**
4145 * Set the QSG_VISUALIZE mode. This follows the vlues supported by Qt in
@@ -53,5 +57,15 @@ public Q_SLOTS:
5357 */
5458 Q_SCRIPTABLE void SetLoggingFilterRules(const QString &filterRules);
5559
60+ /**
61+ * Enable/Disable the shell console log overlay
62+ */
63+ Q_SCRIPTABLE void SetLogOverlay(bool logOverlay);
64+
65+Q_SIGNALS:
66+ void logOverlayChanged(bool logOverlay);
67+
68+private:
69+ bool m_logOverlay;
5670};
5771#endif // DEBUGGINGCONTROLLER_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.cpp+26−104
@@ -20,6 +20,7 @@
2020#include <QLibrary>
2121#include <QProcess>
2222#include <QScreen>
23+#include <QQmlContext>
2324
2425#include <libintl.h>
2526
@@ -34,30 +35,22 @@
3435
3536ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
3637 : QGuiApplication(argc, argv)
38+ , m_qmlArgs(this)
3739{
3840 setApplicationName(QStringLiteral("unity8"));
3941 setOrganizationName(QStringLiteral("Canonical"));
4042
41− connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
42−
4343 setupQmlEngine(isMirServer);
4444
45− UnityCommandLineParser parser(*this);
46−
47− if (!parser.deviceName().isEmpty()) {
48− m_deviceName = parser.deviceName();
49− } else {
45+ if (m_qmlArgs.deviceName().isEmpty()) {
5046 char buffer[200];
5147 property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
52− m_deviceName = QString(buffer);
48+ m_qmlArgs.setDeviceName(QString(buffer));
5349 }
54− m_qmlArgs.setDeviceName(m_deviceName);
55−
56− m_qmlArgs.setMode(parser.mode());
5750
5851 // The testability driver is only loaded by QApplication but not by QGuiApplication.
5952 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
60− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
53+ if (m_qmlArgs.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
6154 QLibrary testLib(QStringLiteral("qttestability"));
6255 if (testLib.load()) {
6356 typedef void (*TasInitialize)(void);
@@ -75,57 +68,39 @@ ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
7568 bindtextdomain("unity8", translationDirectory().toUtf8().data());
7669 textdomain("unity8");
7770
78− m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
79−
80− if (parser.windowGeometry().isValid()) {
81− m_shellView->setWidth(parser.windowGeometry().width());
82− m_shellView->setHeight(parser.windowGeometry().height());
83− }
71+ m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &m_qmlArgs);
72+ m_qmlEngine->rootContext()->setContextProperty("DebuggingController", new DebuggingController(this));
8473
85− if (parser.hasFrameless()) {
86− m_shellView->setFlags(Qt::FramelessWindowHint);
74+ QByteArray pxpguEnv = qgetenv("GRID_UNIT_PX");
75+ bool ok;
76+ int pxpgu = pxpguEnv.toInt(&ok);
77+ if (!ok) {
78+ pxpgu = 8;
8779 }
88−
80+ m_qmlEngine->rootContext()->setContextProperty("internalGu", pxpgu);
81+ m_qmlEngine->load(::qmlDirectory() + "/ShellApplication.qml");
8982
9083 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
9184 // You will need this if you want to interact with touch-only components using a mouse
9285 // Needed only when manually testing on a desktop.
93− if (parser.hasMouseToTouch()) {
86+ if (m_qmlArgs.hasMouseToTouch()) {
9487 m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
9588 }
9689 #endif
9790
98− new DebuggingController(this);
99−
100− // Some hard-coded policy for now.
101− // NB: We don't support more than two screens at the moment
102− //
103− // TODO: Support an arbitrary number of screens and different policies
104− // (eg cloned desktop, several desktops, etc)
105− if (isMirServer && screens().count() == 2) {
106− m_shellView->setScreen(screens().at(1));
107− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
108−
109− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
110− m_secondaryWindow->setScreen(screens().at(0));
111− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
112− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
113− m_secondaryWindow->setVisible(true);
114− }
115−
116− if (parser.mode().compare("greeter") == 0) {
117− QSize primaryScreenSize = this->primaryScreen()->size();
118− m_shellView->setHeight(primaryScreenSize.height());
119− m_shellView->setWidth(primaryScreenSize.width());
120− m_shellView->show();
121− m_shellView->requestActivate();
91+ if (m_qmlArgs.mode().compare("greeter") == 0) {
92+// QSize primaryScreenSize = this->primaryScreen()->size();
93+// m_shellView->setHeight(primaryScreenSize.height());
94+// m_shellView->setWidth(primaryScreenSize.width());
95+// m_shellView->show();
96+// m_shellView->requestActivate();
12297 if (!QProcess::startDetached("initctl emit --no-wait unity8-greeter-started")) {
12398 qDebug() << "Unable to send unity8-greeter-started event to Upstart";
12499 }
125− } else if (isMirServer || parser.hasFullscreen()) {
126− m_shellView->showFullScreen();
127− } else {
128− m_shellView->show();
100+// } else if (isMirServer || parser.hasFullscreen()) {
101+// m_shellView->showFullScreen();
102+// } else {
103+// m_shellView->show();
129104 }
130105}
131106
@@ -136,14 +111,6 @@ ShellApplication::~ShellApplication()
136111
137112void ShellApplication::destroyResources()
138113{
139− // Deletion order is important. Don't use QScopedPointers and the like
140− // Otherwise the process will hang on shutdown (bug somewhere I guess).
141− delete m_shellView;
142− m_shellView = nullptr;
143−
144− delete m_secondaryWindow;
145− m_secondaryWindow = nullptr;
146−
147114 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
148115 delete m_mouseTouchAdaptor;
149116 m_mouseTouchAdaptor = nullptr;
@@ -155,7 +122,7 @@ void ShellApplication::destroyResources()
155122
156123void ShellApplication::setupQmlEngine(bool isMirServer)
157124{
158− m_qmlEngine = new QQmlEngine(this);
125+ m_qmlEngine = new QQmlApplicationEngine(this);
159126
160127 m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
161128
@@ -169,48 +136,3 @@ void ShellApplication::setupQmlEngine(bool isMirServer)
169136
170137 QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
171138}
172−
173−void ShellApplication::onScreenAdded(QScreen * /*screen*/)
174−{
175− // TODO: Support an arbitrary number of screens and different policies
176− // (eg cloned desktop, several desktops, etc)
177− if (screens().count() == 2) {
178− m_shellView->setScreen(screens().at(1));
179− m_qmlArgs.setDeviceName(QStringLiteral("desktop"));
180− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
181− // its backing QPlatformWindow recreated). So lets refocus it.
182− m_shellView->requestActivate();
183− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
184− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
185− m_shellView->setVisible(true);
186−
187− m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
188− m_secondaryWindow->setScreen(screens().at(0));
189−
190− // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
191− m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
192− m_secondaryWindow->setVisible(true);
193− }
194−}
195−
196−void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
197−{
198− // TODO: Support an arbitrary number of screens and different policies
199− // (eg cloned desktop, several desktops, etc)
200− if (screen == m_shellView->screen()) {
201− const QList<QScreen *> allScreens = screens();
202− Q_ASSERT(allScreens.count() > 1);
203− Q_ASSERT(allScreens.at(0) != screen);
204− Q_ASSERT(m_secondaryWindow);
205− delete m_secondaryWindow;
206− m_secondaryWindow = nullptr;
207− m_shellView->setScreen(allScreens.first());
208− m_qmlArgs.setDeviceName(m_deviceName);
209− // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
210− // its backing QPlatformWindow recreated). So lets refocus it.
211− m_shellView->requestActivate();
212− // QWindow::destroy() is called when it changes between screens. We have to manually make it visible again
213− // <dandrader> This bug is supposedly fixed in Qt 5.5.1, although I can still reproduce it there. :-/
214− m_shellView->setVisible(true);
215− }
216−}
src/ShellApplication.h+2−14
@@ -18,7 +18,7 @@
1818#define SHELLAPPLICATION_H
1919
2020#include <QGuiApplication>
21−#include <QQmlEngine>
21+#include <QQmlApplicationEngine>
2222#include <QQuickView>
2323#include <QScopedPointer>
2424
@@ -28,9 +28,6 @@
2828#include "MouseTouchAdaptor.h"
2929#endif
3030
31−#include "SecondaryWindow.h"
32−#include "ShellView.h"
33−
3431class ShellApplication : public QGuiApplication
3532{
3633 Q_OBJECT
@@ -39,25 +36,16 @@ public:
3936 virtual ~ShellApplication();
4037
4138 void destroyResources();
42−public Q_SLOTS:
43− // called by qtmir
44− void onScreenAboutToBeRemoved(QScreen *screen);
45−
46−private Q_SLOTS:
47− void onScreenAdded(QScreen*);
4839
4940private:
5041 void setupQmlEngine(bool isMirServer);
51− QString m_deviceName;
5242 ApplicationArguments m_qmlArgs;
53− ShellView *m_shellView{nullptr};
54− SecondaryWindow *m_secondaryWindow{nullptr};
5543
5644 #ifdef UNITY8_ENABLE_TOUCH_EMULATION
5745 MouseTouchAdaptor *m_mouseTouchAdaptor{nullptr};
5846 #endif
5947
60− QQmlEngine *m_qmlEngine{nullptr};
48+ QQmlApplicationEngine *m_qmlEngine{nullptr};
6149};
6250
6351#endif // SHELLAPPLICATION_H
src/ShellView.cppdeleted−63
@@ -1,63 +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−
23−// local
24−#include <paths.h>
25−
26−ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
27− : QQuickView(engine, nullptr)
28−{
29− setResizeMode(QQuickView::SizeRootObjectToView);
30− setColor("black");
31− setTitle(QStringLiteral("Unity8"));
32−
33− rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
34−
35− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
36− setSource(source);
37−
38− connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
39− connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
40−}
41−
42−void ShellView::onWidthChanged(int w)
43−{
44− // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
45− //
46− // There's at least one situation that's know to leave the root object with an outdated size.
47− // (really looks like Qt bug)
48− // Happens when starting unity8 with an external monitor already connected.
49− // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
50− // from the resize event handler, the root item doesn't get resized.
51− // TODO: Confirm the Qt bug and submit a patch upstream
52− if (rootObject()) {
53− rootObject()->setWidth(w);
54− }
55−}
56−
57−void ShellView::onHeightChanged(int h)
58−{
59− // See comment in ShellView::onWidthChanged()
60− if (rootObject()) {
61− rootObject()->setHeight(h);
62− }
63−}
src/UnityCommandLineParser.h+1−1
@@ -36,8 +36,8 @@ public:
3636 bool hasFullscreen() const { return m_hasFullscreen; }
3737 QString deviceName() const { return m_deviceName; }
3838 QString mode() const { return m_mode; }
39−private:
4039
40+protected:
4141 int parsePixelsValue(const QString &str);
4242 static float getenvFloat(const char* name, float defaultValue);
4343 void resolveMode(QCommandLineParser &parser, QCommandLineOption &modeOption);
tests/mocks/Cursor/Cursor.qml+49−2
@@ -15,8 +15,11 @@
1515 */
1616
1717import QtQuick 2.4
18+import UInput 0.1
19+
20+Canvas {
21+ id: root
1822
19−Item {
2023 property int topBoundaryOffset // effectively panel height
2124 property Item confiningItem
2225
@@ -30,5 +33,49 @@ Item {
3033 signal pushStopped()
3134 signal mouseMoved()
3235
33− onMouseMoved: opacity = 1;
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
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+ }
3481}
tests/mocks/GSettings.1.0/plugin.cpp+12
@@ -18,6 +18,8 @@
1818#include "fake_gsettings.h"
1919
2020#include <QtQml/qqml.h>
21+#include <QQmlEngine>
22+#include <QQmlContext>
2123
2224static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
2325{
@@ -31,3 +33,13 @@ void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
3133 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
3234 "GSettingsSchema can only be used inside of a GSettings component");
3335}
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
2626
2727public:
2828 void registerTypes(const char *uri) override;
29+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
2930};
3031
3132#endif // PLUGIN_H
tests/mocks/UInput/plugin.cpp+6−1
@@ -19,8 +19,13 @@
1919
2020#include <QtQml/qqml.h>
2121
22+QObject* uinputSingleton(QQmlEngine*, QJSEngine*)
23+{
24+ return new MockUInput;
25+}
26+
2227void MockUInputPlugin::registerTypes(const char *uri)
2328{
2429 Q_ASSERT(uri == QLatin1String("UInput"));
25− qmlRegisterType<MockUInput>(uri, 0, 1, "UInput");
30+ qmlRegisterSingletonType<MockUInput>(uri, 0, 1, "UInput", uinputSingleton);
2631}
tests/mocks/Unity/Application/MirSurfaceItem.cpp+27−4
@@ -189,9 +189,15 @@ void MirSurfaceItem::createQmlContentItem()
189189 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
190190 m_qmlItem->setParentItem(this);
191191
192− m_qmlItem->setWidth(m_surfaceWidth);
193− m_qmlItem->setHeight(m_surfaceHeight);
192+ if (m_fillMode == FillMode::Stretch && width() != 0 && height() != 0) {
193+ m_qmlItem->setWidth(width());
194+ m_qmlItem->setHeight(height());
195+ } else {
196+ m_qmlItem->setWidth(m_surfaceWidth);
197+ m_qmlItem->setHeight(m_surfaceHeight);
198+ }
194199
200+ qDebug() << "set size to" << m_surfaceWidth;
195201 setImplicitWidth(m_qmlItem->implicitWidth());
196202 setImplicitHeight(m_qmlItem->implicitHeight());
197203
@@ -308,6 +314,16 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
308314 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
309315 connect(m_qmlSurface, &MirSurface::liveChanged, this, &MirSurfaceItem::liveChanged);
310316 connect(m_qmlSurface, &MirSurface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
317+ connect(m_qmlSurface, &MirSurface::sizeChanged, this, [this] () {
318+ if (m_fillMode == FillMode::Stretch) {
319+ m_qmlItem->setSize(QSize(this->width(), this->height()));
320+ } else {
321+ m_qmlItem->setSize(m_qmlSurface->size());
322+ }
323+ setImplicitSize(m_qmlSurface->width(), m_qmlSurface->height());
324+ });
325+ m_surfaceWidth = surface->size().width();
326+ m_surfaceHeight = surface->size().height();
311327
312328 QUrl qmlComponentFilePath;
313329 if (!m_qmlSurface->qmlFilePath().isEmpty()) {
@@ -321,6 +337,7 @@ void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
321337 switch (m_qmlContentComponent->status()) {
322338 case QQmlComponent::Ready:
323339 createQmlContentItem();
340+ qDebug() << "content created" << m_surfaceWidth << implicitWidth() << width();
324341 break;
325342 case QQmlComponent::Loading:
326343 connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
@@ -399,9 +416,15 @@ void MirSurfaceItem::updateSurfaceSize()
399416 if (m_qmlSurface && m_surfaceWidth > 0 && m_surfaceHeight > 0) {
400417 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
401418 if (m_qmlItem) {
402− m_qmlItem->setWidth(m_surfaceWidth);
403− m_qmlItem->setHeight(m_surfaceHeight);
419+ if (m_fillMode == FillMode::Stretch) {
420+ m_qmlItem->setWidth(width());
421+ m_qmlItem->setHeight(height());
422+ } else {
423+ m_qmlItem->setWidth(m_surfaceWidth);
424+ m_qmlItem->setHeight(m_surfaceHeight);
425+ }
404426 }
427+ qDebug() << this << "setting implicitsize" << m_surfaceWidth << m_surfaceHeight;
405428 setImplicitSize(m_surfaceWidth, m_surfaceHeight);
406429 }
407430}
tests/mocks/Unity/Application/SurfaceManager.cpp+1−1
@@ -21,7 +21,7 @@
2121
2222#include <paths.h>
2323
24−#define SURFACEMANAGER_DEBUG 0
24+#define SURFACEMANAGER_DEBUG 1
2525
2626#if SURFACEMANAGER_DEBUG
2727#define DEBUG_MSG(params) qDebug().nospace() << "SurfaceManager[" << (void*)this << "]::" << __func__ << params
tests/mocks/Unity/InputInfo/InputWindow.qmlnew file+68
@@ -0,0 +1,68 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.InputInfo 0.1
4+
5+Rectangle {
6+ id: rect
7+ color: "white"
8+ width: parent.width
9+ height: parent.height
10+
11+ Action {
12+ id: mouseAction
13+ text: checked ? "Remove Mouse" : "Add Mouse"
14+ onTriggered: {
15+ if (checked) {
16+ console.log("ADD")
17+ MockInputDeviceBackend.addMockDevice("/mouse0", InputInfo.Mouse);
18+ } else {
19+ console.log("REMOVE")
20+ MockInputDeviceBackend.removeDevice("/mouse0");
21+ }
22+ }
23+ iconName: "input-mouse-symbolic"
24+ checkable: true
25+ checked: false
26+ }
27+
28+ Action {
29+ id: kbAction
30+ text: checked ? "Remove Keyboard" : "Add Keyboard"
31+ onTriggered: {
32+ if (checked) {
33+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
34+ } else {
35+ MockInputDeviceBackend.removeDevice("/kbd0");
36+ }
37+ }
38+ iconName: "input-keyboard-symbolic"
39+ checkable: true
40+ checked: false
41+ }
42+
43+ Column {
44+ anchors {
45+ fill: parent
46+ margins: units.gu(1)
47+ }
48+ spacing: units.gu(1)
49+
50+ Button {
51+ anchors {
52+ left: parent.left
53+ right: parent.right
54+ }
55+ action: mouseAction
56+ color: mouseAction.checked ? UbuntuColors.red : UbuntuColors.green
57+ }
58+
59+ Button {
60+ anchors {
61+ left: parent.left
62+ right: parent.right
63+ }
64+ action: kbAction
65+ color: kbAction.checked ? UbuntuColors.red : UbuntuColors.green
66+ }
67+ }
68+}
tests/mocks/Unity/InputInfo/mockcontroller.cpp+24−2
@@ -18,10 +18,32 @@
1818
1919#include "qinputdeviceinfo_mock_p.h"
2020
21−MockController::MockController(QObject *parent):
22− QObject(parent)
21+#include <QQuickView>
22+#include <QQmlComponent>
23+#include <QQuickItem>
24+#include <paths.h>
25+
26+MockController::MockController(QQmlEngine *engine)
2327{
28+ QQmlComponent component(engine);
29+ component.setData("import QtQuick 2.4; import Unity.InputInfo 0.1; InputWindow { }", ::qmlDirectory());
30+ QObject *myObject = component.create();
31+ QQuickItem *item = qobject_cast<QQuickItem*>(myObject);
32+ if (item) {
33+ auto window = new QQuickView();
34+ window->setTitle("Input");
35+ item->setParentItem(window->contentItem());
36+ window->setResizeMode(QQuickView::SizeRootObjectToView);
37+ window->setWidth(200);
38+ window->setHeight(100);
39+ window->show();
40+ }
41+}
2442
43+MockController *MockController::instance(QQmlEngine *engine)
44+{
45+ static MockController* controller = new MockController(engine);
46+ return controller;
2547}
2648
2749QInputDevice *MockController::addMockDevice(const QString &devicePath, QInputDevice::InputType type)
tests/mocks/Unity/InputInfo/mockcontroller.h+5−1
@@ -20,13 +20,17 @@
2020#include <QObject>
2121#include "qinputinfo.h"
2222
23+class QQmlEngine;
24+
2325class MockController: public QObject
2426{
2527 Q_OBJECT
2628public:
27− MockController(QObject *parent = 0);
29+ MockController(QQmlEngine *engine);
2830 ~MockController() = default;
2931
32+ static MockController *instance(QQmlEngine *engine);
33+
3034 Q_INVOKABLE QInputDevice* addMockDevice(const QString &devicePath, QInputDevice::InputType type);
3135 Q_INVOKABLE void removeDevice(const QString &devicePath);
3236};
tests/mocks/Unity/InputInfo/plugin.cpp+1−2
@@ -26,9 +26,8 @@
2626
2727static QObject *backendProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
2828{
29− Q_UNUSED(engine)
3029 Q_UNUSED(scriptEngine)
31− return new MockController(engine);
30+ return MockController::instance(engine);
3231}
3332
3433void InputInfoPlugin::registerTypes(const char *uri)
tests/mocks/Unity/InputInfo/qmldir+2
@@ -1,2 +1,4 @@
11module Unity.InputInfo
22plugin MockInputInfo
3+
4+InputWindow 0.1 InputWindow.qml
tests/mocks/Unity/Screens/CMakeLists.txt+2−1
@@ -5,10 +5,11 @@ include_directories(
55set(MockScreens_SOURCES
66 plugin.cpp
77 screens.cpp
8+ screenwindow.cpp
89)
910
1011add_library(MockScreensPlugin MODULE ${MockScreens_SOURCES})
1112
12−qt5_use_modules(MockScreensPlugin Gui Qml)
13+qt5_use_modules(MockScreensPlugin Gui Qml Quick)
1314
1415add_unity8_mock(Unity.Screens 0.1 Unity/Screens PREFIX mocks TARGETS MockScreensPlugin)
tests/mocks/Unity/Screens/plugin.cpp+14−1
@@ -16,14 +16,27 @@
1616
1717#include "plugin.h"
1818#include "screens.h"
19+#include "screenwindow.h"
1920
2021#include <QScreen>
2122
23+namespace {
24+QObject* screensSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
25+ Q_UNUSED(engine);
26+ Q_UNUSED(scriptEngine);
27+ return new Screens();
28+}
29+}
30+
2231void UnityScreensPlugin::registerTypes(const char* uri)
2332{
2433 Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens"));
2534
2635 qRegisterMetaType<QScreen*>("QScreen*");
36+ qRegisterMetaType<ScreenMode*>("ScreenMode*");
37+ qmlRegisterUncreatableType<ScreenMode>(uri, 0, 1, "ScreenMode", "ScreenMode is not creatable.");
2738
28− qmlRegisterType<Screens>(uri, 0, 1, "Screens");
39+ qmlRegisterSingletonType<Screens>(uri, 0, 1, "Screens", screensSingleton);
40+ qmlRegisterType<ScreenWindow>(uri, 0, 1, "ScreenWindow");
41+ qmlRegisterRevision<QWindow,1>(uri, 0, 1);
2942}
tests/mocks/Unity/Screens/screens.cpp+52−9
@@ -18,16 +18,30 @@
1818
1919// Qt
2020#include <QGuiApplication>
21−#include <QScreen>
2221#include <QDebug>
2322
24−Q_DECLARE_METATYPE(QScreen*)
25−
2623Screens::Screens(QObject *parent) :
2724 QAbstractListModel(parent)
2825{
29− // start with one screen attached
30− m_screenList.append(new Screen());
26+ bool ok = false;
27+ int screenCount = qEnvironmentVariableIntValue("UNITY_MOCK_SCREEN_COUNT", &ok);
28+ if (!ok) screenCount = 1;
29+ QPoint lastPoint(0,0);
30+ for (int i = 0; i < screenCount; ++i) {
31+ auto screen = new Screen();
32+ screen->m_active = i == 0;
33+ screen->m_name = QString("Monitor %1").arg(i);
34+ screen->m_position = QPoint(lastPoint.x(), lastPoint.y());
35+ screen->m_sizes.append(new ScreenMode(50, QSize(640,480)));
36+ screen->m_sizes.append(new ScreenMode(60, QSize(1280,1024)));
37+ screen->m_sizes.append(new ScreenMode(60, QSize(1440,900)));
38+ screen->m_sizes.append(new ScreenMode(60, QSize(1920,1080)));
39+ screen->m_currentModeIndex = 3;
40+ screen->m_physicalSize = QSize(800,568);
41+ m_screenList.append(screen);
42+
43+ lastPoint.rx() += screen->m_sizes[screen->m_currentModeIndex]->size.width();
44+ }
3145}
3246
3347Screens::~Screens() noexcept
@@ -40,7 +54,6 @@ QHash<int, QByteArray> Screens::roleNames() const
4054{
4155 QHash<int, QByteArray> roles;
4256 roles[ScreenRole] = "screen";
43− roles[OutputTypeRole] = "outputType";
4457 return roles;
4558}
4659
@@ -52,9 +65,7 @@ QVariant Screens::data(const QModelIndex &index, int role) const
5265
5366 switch(role) {
5467 case ScreenRole:
55− return QVariant::fromValue(m_screenList.at(index.row())->qScreen);
56− case OutputTypeRole:
57− return m_screenList.at(index.row())->outputTypes;
68+ return QVariant::fromValue(m_screenList.at(index.row()));
5869 }
5970
6071 return QVariant();
@@ -69,3 +80,35 @@ int Screens::count() const
6980{
7081 return m_screenList.size();
7182}
83+
84+void Screens::activateScreen(int)
85+{
86+ qWarning("Not Implemented");
87+}
88+
89+Screen::Screen(QObject* parent)
90+ : QObject(parent)
91+{
92+}
93+
94+Screen::~Screen()
95+{
96+ qDeleteAll(m_sizes);
97+ m_sizes.clear();
98+}
99+
100+QQmlListProperty<ScreenMode> Screen::availableModes()
101+{
102+ return QQmlListProperty<ScreenMode>(this, m_sizes);
103+}
104+
105+Screen *Screen::beginConfiguration()
106+{
107+ qWarning("Not Implemented");
108+ return nullptr;
109+}
110+
111+void Screen::applyConfiguration()
112+{
113+ qWarning("Not Implemented");
114+}
tests/mocks/Unity/Screens/screens.h+84−4
@@ -19,6 +19,7 @@
1919
2020#include <QAbstractListModel>
2121#include <QScreen>
22+#include <QQmlListProperty>
2223
2324class Screen;
2425
@@ -31,7 +32,13 @@ class Screens : public QAbstractListModel
3132public:
3233 enum ItemRoles {
3334 ScreenRole = Qt::UserRole + 1,
34− OutputTypeRole
35+ OutputTypeRole,
36+ EnabledRole,
37+ NameRole,
38+ ScaleRole,
39+ FormFactorRole,
40+ GeometryRole,
41+ SizesRole
3542 };
3643
3744 enum OutputTypes {
@@ -53,6 +60,15 @@ public:
5360 };
5461 Q_ENUM(OutputTypes)
5562
63+ enum FormFactor {
64+ FormFactorUnknown,
65+ FormFactorPhone,
66+ FormFactorTablet,
67+ FormFactorMonitor,
68+ FormFactorTV,
69+ FormFactorProjector,
70+ };
71+
5672 explicit Screens(QObject *parent = 0);
5773 virtual ~Screens() noexcept;
5874
@@ -63,6 +79,9 @@ public:
6379
6480 int count() const;
6581
82+public Q_SLOTS:
83+ void activateScreen(int index);
84+
6685Q_SIGNALS:
6786 void countChanged();
6887 void screenAdded(QScreen *screen);
@@ -72,11 +91,72 @@ private:
7291 QList<Screen *> m_screenList;
7392};
7493
75−class Screen
94+class ScreenMode : public QObject
7695{
96+ Q_OBJECT
97+ Q_PROPERTY(qreal refreshRate MEMBER refreshRate CONSTANT)
98+ Q_PROPERTY(QSize size MEMBER size CONSTANT)
7799public:
78− Screens::OutputTypes outputTypes = Screens::Unknown;
79− QScreen *qScreen = nullptr;
100+ ScreenMode() {}
101+ ScreenMode(qreal refreshRate, QSize size):refreshRate(refreshRate),size(size) {}
102+ ScreenMode(const ScreenMode& other)
103+ : QObject(nullptr),
104+ refreshRate{other.refreshRate},size{other.size}
105+ {}
106+
107+ qreal refreshRate;
108+ QSize size;
80109};
81110
111+class Screen : public QObject
112+{
113+ Q_OBJECT
114+
115+ Q_PROPERTY(bool active MEMBER m_active NOTIFY activeChanged)
116+
117+ Q_PROPERTY(bool used MEMBER m_used NOTIFY usedChanged)
118+ Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged)
119+ Q_PROPERTY(Screens::OutputTypes outputType MEMBER m_outputType NOTIFY outputTypeChanged)
120+ Q_PROPERTY(float scale MEMBER m_scale NOTIFY scaleChanged)
121+ Q_PROPERTY(Screens::FormFactor formFactor MEMBER m_formFactor NOTIFY formFactorChanged)
122+ Q_PROPERTY(QPoint position MEMBER m_position NOTIFY positionChanged)
123+ Q_PROPERTY(uint currentModeIndex MEMBER m_currentModeIndex NOTIFY currentModeIndexChanged)
124+ Q_PROPERTY(QQmlListProperty<ScreenMode> availableModes READ availableModes NOTIFY availableModesChanged)
125+ Q_PROPERTY(QSizeF physicalSize MEMBER m_physicalSize NOTIFY physicalSizeChanged)
126+public:
127+ Screen(QObject* parent = 0);
128+ ~Screen();
129+
130+ QQmlListProperty<ScreenMode> availableModes();
131+
132+ Q_INVOKABLE Screen* beginConfiguration();
133+ Q_INVOKABLE void applyConfiguration();
134+
135+Q_SIGNALS:
136+ void activeChanged();
137+ void usedChanged();
138+ void nameChanged();
139+ void outputTypeChanged();
140+ void scaleChanged();
141+ void formFactorChanged();
142+ void positionChanged();
143+ void currentModeIndexChanged();
144+ void availableModesChanged();
145+ void physicalSizeChanged();
146+
147+public:
148+ bool m_active{false};
149+ bool m_used{true};
150+ QString m_name;
151+ Screens::OutputTypes m_outputType{Screens::Unknown};
152+ float m_scale{1.0};
153+ Screens::FormFactor m_formFactor{Screens::FormFactorMonitor};
154+ QPoint m_position;
155+ uint m_currentModeIndex{0};
156+ QList<ScreenMode*> m_sizes;
157+ QSizeF m_physicalSize;
158+};
159+
160+Q_DECLARE_METATYPE(ScreenMode)
161+
82162#endif // SCREENS_H
tests/mocks/Unity/Screens/screenwindow.cppnew file+35
@@ -0,0 +1,35 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify 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+
19+ScreenWindow::ScreenWindow(QWindow *parent)
20+ : QQuickWindow(parent)
21+{
22+}
23+
24+Screen *ScreenWindow::screenWrapper() const
25+{
26+ return m_screen.data();
27+}
28+
29+void ScreenWindow::setScreenWrapper(Screen *screen)
30+{
31+ if (m_screen != screen) {
32+ m_screen = screen;
33+ Q_EMIT screenWrapperChanged();
34+ }
35+}
tests/mocks/Unity/Screens/screenwindow.hnew file+42
@@ -0,0 +1,42 @@
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 SCREENWINDOW_H
18+#define SCREENWINDOW_H
19+
20+#include <QQuickWindow>
21+#include <QPointer>
22+
23+#include "screens.h"
24+
25+class ScreenWindow : public QQuickWindow
26+{
27+ Q_OBJECT
28+ Q_PROPERTY(Screen *screen READ screenWrapper WRITE setScreenWrapper NOTIFY screenWrapperChanged)
29+public:
30+ ScreenWindow(QWindow *parent = 0);
31+
32+ Screen *screenWrapper() const;
33+ void setScreenWrapper(Screen *screen);
34+
35+Q_SIGNALS:
36+ void screenWrapperChanged();
37+
38+private:
39+ QPointer<Screen> m_screen;
40+};
41+
42+#endif // SCREENWINDOW_H
tests/qmltests/Components/tst_VirtualTouchPad.qml+2−2
@@ -39,11 +39,11 @@ Rectangle {
3939
4040 SignalSpy {
4141 id: mouseEventSpy1
42− target: touchScreenPad.uinput
42+ target: UInput
4343 }
4444 SignalSpy {
4545 id: mouseEventSpy2
46− target: touchScreenPad.uinput
46+ target: UInput
4747 }
4848
4949 UnityTestCase {
tests/qmltests/Panel/tst_Panel.qml+14−12
@@ -42,6 +42,8 @@ PanelTest {
4242 value: keyboardAttached.checked
4343 }
4444
45+ readonly property alias panelState: panel.panelState
46+
4547 SurfaceManager { id: sMgr }
4648 ApplicationMenuDataLoader {
4749 id: appMenuData
@@ -148,7 +150,7 @@ PanelTest {
148150 Layout.fillWidth: true
149151 CheckBox {
150152 id: windowControlsCB
151− onClicked: PanelState.decorationsVisible = checked
153+ onClicked: panelState.decorationsVisible = checked
152154 }
153155 Label {
154156 text: "Show window decorations"
@@ -159,7 +161,7 @@ PanelTest {
159161 RowLayout {
160162 Layout.fillWidth: true
161163 CheckBox {
162− onClicked: PanelState.title = checked ? "Fake window title" : ""
164+ onClicked: panelState.title = checked ? "Fake window title" : ""
163165 }
164166 Label {
165167 text: "Show fake window title"
@@ -245,7 +247,7 @@ PanelTest {
245247
246248 SignalSpy {
247249 id: windowControlButtonsSpy
248− target: PanelState
250+ target: panelState
249251 signalName: "closeClicked"
250252 }
251253
@@ -255,8 +257,8 @@ PanelTest {
255257 panel.fullscreenMode = false;
256258 callManager.foregroundCall = null;
257259
258− PanelState.title = "";
259− PanelState.decorationsVisible = false;
260+ panelState.title = "";
261+ panelState.decorationsVisible = false;
260262
261263 // Wait for the indicators to get into position.
262264 // (switches between normal and fullscreen modes are animated)
@@ -447,7 +449,7 @@ PanelTest {
447449 }
448450
449451 function test_hint(data) {
450− PanelState.title = "Fake Title"
452+ panelState.title = "Fake Title"
451453 panel.fullscreenMode = data.fullscreen;
452454 callManager.foregroundCall = data.call;
453455
@@ -489,7 +491,7 @@ PanelTest {
489491 // menus, first by running the hint animation, then after dragging down will
490492 // expose more of the panel. Releasing the touch will complete the show.
491493 function test_drag_applicationMenu_down_shows_menu(data) {
492− PanelState.title = "Fake Title";
494+ panelState.title = "Fake Title";
493495 panel.fullscreenMode = data.fullscreen;
494496 callManager.foregroundCall = data.call;
495497
@@ -580,7 +582,7 @@ PanelTest {
580582 }
581583
582584 function test_darkenedAreaEatsAllApplicationMenuEvents() {
583− PanelState.title = "Fake Title"
585+ panelState.title = "Fake Title"
584586
585587 // The center of the area not covered by the indicators menu
586588 // Ie, the visible darkened area behind the menu
@@ -701,7 +703,7 @@ PanelTest {
701703 var windowControlArea = findChild(panel, "windowControlArea");
702704 verify(windowControlArea, "Window control area should have been created in windowed mode")
703705
704− PanelState.decorationsVisible = true;
706+ panelState.decorationsVisible = true;
705707 // click in very topleft corner and verify the close button got clicked too
706708 mouseMove(panel, 0, 0);
707709 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
@@ -762,7 +764,7 @@ PanelTest {
762764 }
763765
764766 function test_stagedApplicationMenuBarShowOnMouseHover() {
765− PanelState.title = "Fake Title";
767+ panelState.title = "Fake Title";
766768 panel.mode = "staged";
767769 mouseEmulation.checked = false;
768770
@@ -780,7 +782,7 @@ PanelTest {
780782 }
781783
782784 function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
783− PanelState.title = "Fake Title";
785+ panelState.title = "Fake Title";
784786 panel.mode = "windowed";
785787 mouseEmulation.checked = false;
786788
@@ -796,7 +798,7 @@ PanelTest {
796798 tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
797799 tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
798800
799− PanelState.decorationsVisible = true;
801+ panelState.decorationsVisible = true;
800802
801803 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
802804 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
tests/qmltests/Stage/tst_DecoratedWindow.qml+5−1
@@ -52,6 +52,10 @@ Rectangle {
5252 surfaceManager: sMgr
5353 }
5454
55+ PanelState {
56+ id: panelState
57+ }
58+
5559 Item {
5660 id: fakeShell
5761
@@ -99,7 +103,7 @@ Rectangle {
99103 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
100104
101105 Binding {
102− target: PanelState
106+ target: panelState
103107 property: "focusedPersistentSurfaceId"
104108 value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105109 }
tests/qmltests/Stage/tst_DesktopStage.qml+8−3
@@ -43,6 +43,10 @@ Item {
4343 value: false
4444 }
4545
46+ PanelState {
47+ id: panelState
48+ }
49+
4650 Component.onCompleted: {
4751 QuickUtils.keyboardAttached = true;
4852 theme.name = "Ubuntu.Components.Themes.SuruDark";
@@ -100,6 +104,7 @@ Item {
100104 topLevelSurfaceList: topSurfaceList
101105 interactive: true
102106 mode: "windowed"
107+ panelState: panelState
103108 }
104109 }
105110 }
@@ -618,19 +623,19 @@ Item {
618623 maximizeDelegate(facebookAppDelegate);
619624
620625 // verify the drop shadow is still not visible
621− verify(PanelState.dropShadow == false);
626+ verify(panelState.dropShadow == false);
622627
623628 // start a foreground app, not maximized
624629 var dialerAppDelegate = startApplication("dialer-app");
625630
626631 // verify the drop shadow becomes visible
627− verify(PanelState.dropShadow == true);
632+ verify(panelState.dropShadow == true);
628633
629634 // close the maximized app
630635 ApplicationManager.stopApplication("facebook-webapp");
631636
632637 // verify the drop shadow is gone
633− tryCompare(PanelState, "dropShadow", false);
638+ tryCompare(panelState, "dropShadow", false);
634639 }
635640
636641 function test_threeFingerTapShowsWindowControls_data() {
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−8
@@ -19,7 +19,6 @@ import QtQuick.Layouts 1.1
1919import QtTest 1.0
2020import Unity.Test 0.1
2121import ".."
22−import "../../../qml/Components/PanelState"
2322import "../../../qml/Stage"
2423import Ubuntu.Components 1.3
2524import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -31,12 +30,6 @@ Item {
3130 height: units.gu(60)
3231 width: units.gu(85)
3332
34− Binding {
35− target: PanelState
36− property: "panelHeight"
37− value: units.gu(3)
38− }
39−
4033 Component {
4134 id: fakeWindowComponent
4235
@@ -362,7 +355,7 @@ Item {
362355
363356 // Make sure it's again where we left it in normal state before destroying
364357 compare(fakeWindow.requestedX >= 0, true)
365− compare(fakeWindow.requestedY >= PanelState.panelHeight, true)
358+ compare(fakeWindow.requestedY >= 0, true)
366359 compare(fakeWindow.requestedX + fakeWindow.width <= root.width, true)
367360 compare(fakeWindow.requestedY + fakeWindow.height <= root.height, true)
368361
tests/qmltests/tst_OrientedShell.qml+24−5
@@ -30,7 +30,6 @@ import Utils 0.1
3030
3131import "../../qml"
3232import "../../qml/Components"
33−import "../../qml/Components/PanelState"
3433import "Stage"
3534
3635Rectangle {
@@ -82,6 +81,12 @@ Rectangle {
8281 deviceFilter: InputInfo.Keyboard
8382 }
8483
84+ QtObject {
85+ id: _screenWindow
86+ property bool primary: true
87+ }
88+ property alias screenWindow: _screenWindow
89+
8590 property int physicalOrientation0
8691 property int physicalOrientation90
8792 property int physicalOrientation180
@@ -174,6 +179,11 @@ Rectangle {
174179 Component.onDestruction: {
175180 orientedShellLoader.itemDestroyed = true;
176181 }
182+
183+ SurfaceManager {
184+ id: surfaceMan
185+ }
186+ surfaceManager: surfaceMan
177187 }
178188 }
179189 }
@@ -446,6 +456,15 @@ Rectangle {
446456 property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
447457 property Item shell
448458 property QtObject topLevelSurfaceList
459+ property var panelState: undefined
460+
461+ onOrientedShellChanged: {
462+ if (orientedShell) {
463+ panelState = findInvisibleChild(orientedShell, "panelState");
464+ } else {
465+ panelState = undefined;
466+ }
467+ }
449468
450469 SignalSpy { id: signalSpy }
451470 SignalSpy { id: signalSpy2 }
@@ -1509,13 +1528,13 @@ Rectangle {
15091528 print("exptectedAngle", expectedAngle, point.x, point.y)
15101529 switch (expectedAngle) {
15111530 case 0:
1512− return point.x === 0 && point.y === PanelState.panelHeight;
1531+ return point.x === 0 && point.y === panelState.panelHeight;
15131532 case 90:
1514− return point.x === orientedShell.width - PanelState.panelHeight && point.y === 0;
1533+ return point.x === orientedShell.width - panelState.panelHeight && point.y === 0;
15151534 case 180:
1516− return point.x === orientedShell.width && point.y === orientedShell.height - PanelState.panelHeight;
1535+ return point.x === orientedShell.width && point.y === orientedShell.height - panelState.panelHeight;
15171536 default: // 270
1518− return point.x === PanelState.panelHeight && point.y === orientedShell.height;
1537+ return point.x === panelState.panelHeight && point.y === orientedShell.height;
15191538 }
15201539 }
15211540
tests/qmltests/tst_Shell.qml+27−11
@@ -62,13 +62,22 @@ Rectangle {
6262 if (shell) {
6363 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
6464 appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
65+ panelState = testCase.findInvisibleChild(shell, "panelState");
6566 } else {
6667 topLevelSurfaceList = null;
6768 appMenuData.surfaceManager = null;
69+ panelState = undefined;
6870 }
6971 }
7072
7173 property var topLevelSurfaceList: null
74+ property var panelState: undefined
75+
76+ QtObject {
77+ id: _screenWindow
78+ property bool primary: false
79+ }
80+ property alias screenWindow: _screenWindow
7281
7382 Item {
7483 id: shellContainer
@@ -143,6 +152,11 @@ Rectangle {
143152 Component.onDestruction: {
144153 shellLoader.itemDestroyed = true;
145154 }
155+
156+ SurfaceManager {
157+ id: surfaceMan
158+ }
159+ surfaceManager: surfaceMan
146160 }
147161 }
148162 }
@@ -261,6 +275,7 @@ Rectangle {
261275 anchors { left: parent.left; right: parent.right }
262276 activeFocusOnPress: false
263277 model: ["phone", "tablet", "desktop"]
278+ selectedIndex: 2
264279 onSelectedIndexChanged: {
265280 shellLoader.state = model[selectedIndex];
266281 }
@@ -274,6 +289,7 @@ Rectangle {
274289 anchors { left: parent.left; right: parent.right }
275290 activeFocusOnPress: false
276291 model: ["phone", "tablet", "desktop"]
292+ selectedIndex: 2
277293 }
278294 MouseTouchEmulationCheckbox {
279295 id: mouseEmulation
@@ -678,7 +694,7 @@ Rectangle {
678694 }
679695
680696 function ensureInputMethodSurface() {
681− var surfaceManager = findInvisibleChild(shell, "surfaceManager");
697+ var surfaceManager = shell.surfaceManager;
682698 verify(surfaceManager);
683699 surfaceManager.createInputMethodSurface();
684700
@@ -1866,21 +1882,21 @@ Rectangle {
18661882 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
18671883
18681884 tryCompare(appDelegate, "state", "normal");
1869− tryCompare(PanelState, "decorationsVisible", false)
1885+ tryCompare(panelState, "decorationsVisible", false)
18701886
18711887 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
18721888 tryCompare(appDelegate, "state", "maximized");
1873− tryCompare(PanelState, "decorationsVisible", true)
1889+ tryCompare(panelState, "decorationsVisible", true)
18741890
18751891 ApplicationManager.stopApplication(application.appId);
1876− tryCompare(PanelState, "decorationsVisible", false)
1892+ tryCompare(panelState, "decorationsVisible", false)
18771893
18781894 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
18791895 // They won't be gone until those surface items are destroyed.
18801896 tryCompareFunction(function() { return application.surfaceList.count }, 0);
18811897
18821898 ApplicationManager.startApplication(application.appId);
1883− tryCompare(PanelState, "decorationsVisible", true)
1899+ tryCompare(panelState, "decorationsVisible", true)
18841900 }
18851901
18861902 function test_newAppHasValidGeometry() {
@@ -1937,7 +1953,7 @@ Rectangle {
19371953 mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
19381954 mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
19391955
1940− compare(appDelegate.y >= PanelState.panelHeight, true);
1956+ compare(appDelegate.y >= panelState.panelHeight, true);
19411957 }
19421958
19431959 function test_cantResizeWindowUnderPanel() {
@@ -1965,7 +1981,7 @@ Rectangle {
19651981 mouseMove(decoration, decoration.width/2, -units.gu(100));
19661982
19671983 // verify we don't go past the panel
1968− compare(appDelegate.y >= PanelState.panelHeight, true);
1984+ compare(appDelegate.y >= panelState.panelHeight, true);
19691985 }
19701986
19711987 function test_restoreWindowStateFixesIfUnderPanel() {
@@ -1989,7 +2005,7 @@ Rectangle {
19892005 tryCompareFunction(function () { return topLevelSurfaceList.applicationAt(0).appId; }, application.appId);
19902006
19912007 appDelegate = appRepeater.itemAt(0);
1992− compare(appDelegate.y >= PanelState.panelHeight, true);
2008+ compare(appDelegate.y >= panelState.panelHeight, true);
19932009 }
19942010
19952011 function test_lifecyclePolicyForNonTouchApp_data() {
@@ -2588,7 +2604,7 @@ Rectangle {
25882604 function test_oskDisplacesWindow_data() {
25892605 return [
25902606 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2591− {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: PanelState.panelHeight, oskEnabled: true},
2607+ {tag: "displace to top", windowHeight: units.gu(50), windowY: units.gu(10), targetDisplacement: panelState.panelHeight, oskEnabled: true},
25922608 {tag: "osk not on this screen", windowHeight: units.gu(40), windowY: units.gu(10), targetDisplacement: units.gu(10), oskEnabled: false},
25932609 ]
25942610 }
@@ -2611,8 +2627,8 @@ Rectangle {
26112627 dashAppDelegate.windowedY = data.windowY;
26122628 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, 0, 0, 0));
26132629 var initialY = dashAppDelegate.y;
2614− print("intial", initialY, "panel", PanelState.panelHeight);
2615− verify(initialY > PanelState.panelHeight);
2630+ print("intial", initialY, "panel", panelState.panelHeight);
2631+ verify(initialY > panelState.panelHeight);
26162632
26172633 topLevelSurfaceList.inputMethodSurface.setInputBounds(Qt.rect(0, root.height / 2, root.width, root.height / 2));
26182634 tryCompare(dashAppDelegate, "y", data.targetDisplacement);
tests/uqmlscene/main.cpp+2
@@ -479,6 +479,8 @@ int main(int argc, char ** argv)
479479 // TODO: as soon as the engine construction completes, the debug service is
480480 // listening for connections. But actually we aren't ready to debug anything.
481481 QQmlEngine engine;
482+ engine.rootContext()->setContextProperty("DebuggingController", new DebuggingController(&app));
483+
482484 QQmlComponent *component = new QQmlComponent(&engine);
483485 for (int i = 0; i < imports.size(); ++i)
484486 engine.addImportPath(imports.at(i));