Merge lp:~dandrader/unity8/miral into lp:unity8

Proposed by Daniel d'Andrada on 2016-11-30
Status: Merged
Approved by: Lukáš Tinkl on 2016-12-07
Approved revision: no longer in the source branch.
Merged at revision: 2744
Proposed branch: lp:~dandrader/unity8/miral
Merge into: lp:unity8
Prerequisite: lp:~dandrader/unity8/fixMakeTryApplicationWindow
Diff against target: 11323 lines (+5093/-2297)
127 files modified
CMakeLists.txt (+14/-1)
data/com.canonical.Unity8.gschema.xml (+2/-2)
debian/changelog (+33/-0)
debian/control (+5/-3)
plugins/Greeter/Unity/Launcher/CMakeLists.txt (+1/-1)
plugins/Greeter/Unity/Launcher/launcheritem.cpp (+13/-0)
plugins/Greeter/Unity/Launcher/launcheritem.h (+3/-0)
plugins/LightDM/FullLightDM/CMakeLists.txt (+5/-1)
plugins/Unity/Indicators/CMakeLists.txt (+3/-0)
plugins/Unity/Launcher/CMakeLists.txt (+4/-1)
plugins/Unity/Launcher/appdrawermodel.cpp (+62/-0)
plugins/Unity/Launcher/appdrawermodel.h (+33/-0)
plugins/Unity/Launcher/launcheritem.cpp (+13/-0)
plugins/Unity/Launcher/launcheritem.h (+4/-0)
plugins/Unity/Launcher/launchermodel.cpp (+6/-37)
plugins/Unity/Launcher/launchermodel.h (+0/-14)
plugins/Unity/Launcher/plugin.cpp (+2/-1)
plugins/Unity/Launcher/ualwrapper.cpp (+73/-0)
plugins/Unity/Launcher/ualwrapper.h (+35/-0)
plugins/Utils/CMakeLists.txt (+4/-0)
plugins/Utils/ElapsedTimer.h (+1/-1)
plugins/Utils/Timer.cpp (+5/-2)
plugins/Utils/Timer.h (+5/-6)
plugins/Utils/appdrawerproxymodel.cpp (+189/-0)
plugins/Utils/appdrawerproxymodel.h (+87/-0)
plugins/Utils/globalfunctions.cpp (+5/-0)
plugins/Utils/globalfunctions.h (+2/-0)
plugins/Utils/plugin.cpp (+2/-0)
plugins/Utils/windowstatestorage.cpp (+23/-0)
plugins/Utils/windowstatestorage.h (+5/-0)
plugins/WindowManager/CMakeLists.txt (+3/-1)
plugins/WindowManager/TopLevelSurfaceList.cpp (+0/-481)
plugins/WindowManager/TopLevelSurfaceList.h (+0/-223)
plugins/WindowManager/TopLevelWindowModel.cpp (+668/-0)
plugins/WindowManager/TopLevelWindowModel.h (+260/-0)
plugins/WindowManager/Window.cpp (+237/-0)
plugins/WindowManager/Window.h (+161/-0)
plugins/WindowManager/WindowManagerPlugin.cpp (+5/-2)
po/unity8.pot (+80/-43)
qml/Components/InputMethod.qml (+5/-4)
qml/Components/KeyboardShortcutsOverlay.qml (+13/-0)
qml/Components/KeymapSwitcher.qml (+4/-1)
qml/Components/VirtualTouchPad.qml (+269/-1)
qml/Dash/Previews/PreviewAudioPlayback.qml (+1/-0)
qml/DisabledScreenNotice.qml (+6/-58)
qml/Greeter/Greeter.qml (+1/-5)
qml/Greeter/LoginList.qml (+1/-1)
qml/Greeter/WideView.qml (+7/-2)
qml/Launcher/BackgroundBlur.qml (+81/-0)
qml/Launcher/Drawer.qml (+306/-0)
qml/Launcher/DrawerGridView.qml (+47/-0)
qml/Launcher/DrawerListView.qml (+32/-0)
qml/Launcher/Launcher.qml (+182/-34)
qml/Launcher/MoreAppsHeader.qml (+46/-0)
qml/Panel/Indicators/MenuItemFactory.qml (+22/-1)
qml/Shell.qml (+25/-41)
qml/Stage/FakeMaximizeDelegate.qml (+7/-7)
qml/Stage/MoveHandler.qml (+3/-3)
qml/Stage/Spread/WindowedRightEdgeMaths.qml (+19/-4)
qml/Stage/Stage.qml (+196/-186)
qml/Stage/StageMaths.qml (+2/-7)
qml/Stage/StagedFullscreenPolicy.qml (+4/-4)
qml/Stage/TopLevelSurfaceRepeater.qml (+0/-67)
qml/Stage/WindowControlsOverlay.qml (+1/-1)
qml/Stage/WindowResizeArea.qml (+2/-70)
qml/Stage/WindowStateSaver.qml (+63/-0)
qml/Stage/WindowedFullscreenPolicy.qml (+1/-1)
qml/Tutorial/TutorialLeftLong.qml (+1/-1)
src/ShellApplication.cpp (+1/-0)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp (+1/-1)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp (+23/-23)
tests/mocks/QtMultimedia/videooutput.h (+1/-1)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+48/-24)
tests/mocks/Unity/Application/ApplicationInfo.h (+2/-2)
tests/mocks/Unity/Application/ApplicationManager.cpp (+92/-63)
tests/mocks/Unity/Application/ApplicationManager.h (+31/-4)
tests/mocks/Unity/Application/CMakeLists.txt (+1/-1)
tests/mocks/Unity/Application/MirSurface.cpp (+86/-99)
tests/mocks/Unity/Application/MirSurface.h (+29/-27)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+4/-4)
tests/mocks/Unity/Application/MirSurfaceItem.h (+1/-2)
tests/mocks/Unity/Application/MirSurfaceListModel.cpp (+12/-34)
tests/mocks/Unity/Application/MirSurfaceListModel.h (+2/-7)
tests/mocks/Unity/Application/SurfaceManager.cpp (+172/-26)
tests/mocks/Unity/Application/SurfaceManager.h (+28/-10)
tests/mocks/Unity/Application/plugin.cpp (+4/-34)
tests/mocks/Unity/Launcher/CMakeLists.txt (+5/-1)
tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp (+75/-0)
tests/mocks/Unity/Launcher/MockAppDrawerModel.h (+32/-0)
tests/mocks/Unity/Launcher/MockLauncherItem.cpp (+13/-0)
tests/mocks/Unity/Launcher/MockLauncherItem.h (+6/-0)
tests/mocks/Unity/Launcher/MockLauncherModel.cpp (+3/-0)
tests/mocks/Unity/Launcher/plugin.cpp (+4/-0)
tests/mocks/Utils/CMakeLists.txt (+4/-0)
tests/mocks/Utils/plugin.cpp (+2/-0)
tests/mocks/Utils/windowstatestorage.cpp (+23/-0)
tests/mocks/Utils/windowstatestorage.h (+5/-0)
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt (+1/-1)
tests/plugins/Unity/Launcher/CMakeLists.txt (+2/-1)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+6/-0)
tests/plugins/Utils/WindowInputMonitorTest.cpp (+76/-9)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Components/tst_VirtualTouchPad.qml (+55/-1)
tests/qmltests/Dash/Previews/tst_Preview.qml (+63/-0)
tests/qmltests/Dash/tst_Dash.qml (+4/-3)
tests/qmltests/Dash/tst_DashShell.qml (+0/-29)
tests/qmltests/Launcher/tst_Drawer.qml (+264/-0)
tests/qmltests/Launcher/tst_Launcher.qml (+4/-4)
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml (+6/-1)
tests/qmltests/Panel/tst_ActiveCallHint.qml (+16/-2)
tests/qmltests/Panel/tst_Panel.qml (+8/-1)
tests/qmltests/Stage/ApplicationCheckBox.qml (+1/-1)
tests/qmltests/Stage/tst_ApplicationWindow.qml (+10/-10)
tests/qmltests/Stage/tst_DesktopStage.qml (+124/-80)
tests/qmltests/Stage/tst_PhoneStage.qml (+18/-38)
tests/qmltests/Stage/tst_SurfaceContainer.qml (+4/-0)
tests/qmltests/Stage/tst_TabletStage.qml (+16/-10)
tests/qmltests/Stage/tst_WindowResizeArea.qml (+1/-66)
tests/qmltests/Tutorial/tst_Tutorial.qml (+30/-8)
tests/qmltests/tst_DisabledScreenNotice.qml (+23/-9)
tests/qmltests/tst_OrientedShell.qml (+6/-13)
tests/qmltests/tst_Shell.qml (+143/-242)
tests/qmltests/tst_ShellWithPin.qml (+18/-75)
tests/uqmlscene/CMakeLists.txt (+5/-1)
tests/utils/modules/Unity/Test/StageTestCase.qml (+68/-0)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+8/-9)
tests/utils/modules/Unity/Test/qmldir (+2/-1)
To merge this branch: bzr merge lp:~dandrader/unity8/miral
Reviewer Review Type Date Requested Status
Unity8 CI Bot continuous-integration 2016-11-30 Needs Fixing on 2016-12-07
Lukáš Tinkl (community) 2016-11-30 Approve on 2016-12-07
Emanuele Antonio Faraone (community) Approve on 2016-12-05
Michael Zanetti 2016-11-30 Pending
Review via email: mp+312194@code.launchpad.net

This proposal supersedes a proposal from 2016-11-23.

Commit message

Let the model deal with some window management decisions

eg: which window to focus, whether to change surface state

unity8 requests and reacts to changes in the model instead of applying them

Description of the change

Prereq-archive: ppa:ci-train-ppa-service/2160

For the upcoming, miral-powered, qtmir.

One notable introduction is the Window object, which is a sligthly higher level abstraction of a MirSurface (and a drop-in replacement for it) to be used by Stage.qml. Window always exists in a TopLevelWindowModel entry so that QML code no longer has to if(){}else{} for the existence of a MirSurface (because of the splash screen case)

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~unity-team/unity-api/miral/+merge/309938
https://code.launchpad.net/~unity-team/qtmir/miral-qt-integration/+merge/312244

It's all in this silo: https://bileto.ubuntu.com/#/ticket/2160

* Did you perform an exploratory manual test run of your code change and any related functionality?

Yes.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable

* If you changed the UI, has there been a design review?
Not applicable

To post a comment you must log in.
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2697
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2487/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3279/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3307
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3161/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3161/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3161/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3161/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3161
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3161/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2487/rebuild

review: Needs Fixing (continuous-integration)
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

* See a bunch of inline comments

* I think we should not give up on the differntiation between claimFocus() and requestFocus(). One of them says the stage wants to actually focus something *now* while the other indicates that the app requested focus on its own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still do the raisId() call on the model. requestfocus() instead should end up in the shell, and if the shell is ok with doing so, it should call claimFocus() itself.

* IMO you're exaggerating with the debug messages. unity8.toplevelsurfacelist was already very annoying and this seems to have even more prints. Sure, you can argue that I could turn them off with the debug filter, which is what I do, but then I get none any more while the most important ones like raise() added() and removed() would still make sense.

This has been a code review for the code up until the mocks. Still needs review for tests/mocks and especially in depth testing.

review: Needs Fixing
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

Not yet sure if just an issue in the mocks or not, but this breaks:

- open an app
- click the minimize button in the window decoration
- click the app again in the launcher to restore it
- now click the minimize button again
=> button doesn't work any more

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
> * IMO you're exaggerating with the debug messages. unity8.toplevelsurfacelist was already very annoying and this seems to have even more prints. Sure, you can argue that I could turn them off with the debug filter, which is what I do, but then I get none any more while the most important ones like raise() added() and removed() would still make sense.

Reduced the logging output. Should be the way you prefer now.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

- have 3 apps (a, b and c), make them overlap a bit for better visibility
- focus a by clicking it
- focus b by clicking it
- focus c by clicking it
- minimize c
=> a will be focused

expected: after minimizing c, b should be focused

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
> * I think we should not give up on the differntiation between claimFocus() and requestFocus(). One of them says the stage wants to actually focus something*now* while the other indicates that the app requested focus on its own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still do the raisId() call on the model. requestfocus() instead should end up in the shell, and if the shell is ok with doing so, it should call claimFocus() itself.

What you are explaining effectively is that: leave things as they are
currently with regards to focus. Which means focus lives in unity8 and
miral has no say over it.

The whole point of this miral effort is exactly to move such window
management decisions down to Mir, as dictated by our high-profile
stakeholder.

On the bright side, focus requests made by unity8 should naturally
always be sensible (since it knows the situation pretty well) and
therefore miral should always swiftly comply. So in terms of end
results, those requests will work just like imperative assignments.

Focus is a central piece of window management and if miral is to do
anything useful it has to have a say in it. And we cannot have two
entities making decisions on focus (unity8 and miral) as it will
inevitably be racy and conflictive.

As for the origin of the focus change request (shell vs. application),
it doesn't matter from miral's standpoint. It will comply to either as
long the change is valid and makes sense. Eg.: it should never allow a
window blocked by a child modal dialog to be focused. So it's mostly
sanity checking. I don't see miral ignoring requests on a whim. :)

Besides, having two separate code paths would make unity8 code more
complex. Eg: Having both code that sets surface state and code that
responds to surface state changes. "If surface state change was done in
response my your own set(), do nothing, else, ponder and respond."

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2698
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2512/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3317/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3345
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3197/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3197/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3197/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3197/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3197
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3197/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2512/rebuild

review: Needs Fixing (continuous-integration)
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +int TopLevelWindowModel::nextFreeId(int candidateId)
>> >+{
>> >+ if (candidateId > m_maxId) {
>> >+ return nextFreeId(1);
>> >+ } else {
>> >+ if (indexForId(candidateId) == -1) {
>> >+ // it's indeed free
>> >+ return candidateId;
>> >+ } else {
>> >+ return nextFreeId(candidateId + 1);
>> >+ }
>> >+ }
>> >+}
> In the very unlikely event that we'd run out of free ids, this would crash with a stack overflow. Think it makes sense to write this in a non-recursive manner and perhaps add some way to actually handle failures?
>
If we run out of free ids it's because there's already a bug somewhere.
There's no way there's a valid situation where we would have literally a
million windows lying around.

But a non-recursive version is indeed lighter-weight on resources if it
ever has to traverse a long stretch of taken ids. Done.

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2699
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2513/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3318/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3346
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3198/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3198/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3198/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3198/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3198
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3198/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2513/rebuild

review: Needs Fixing (continuous-integration)
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +QString TopLevelWindowModel::toString()
>> >+{
>> >+ QString str;
>> >+ for (int i = 0; i < m_windowModel.count(); ++i) {
>> >+ auto item = m_windowModel.at(i);
>> >+
>> >+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
>> >+ .arg(i)
>> >+ .arg(item.application->appId())
>> >+ .arg((qintptr)item.window->surface(), 0, 16)
>> >+ .arg(item.window->id());
> it would be slightly more performant if you'd use QString::arg(QString, QString, QSttring, QString) instead as it has to walk the string only once with that...
>
Done.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +public Q_SLOTS:
>> >+ /**
>> >+ * @brief Returns the surface at the given index
>> >+ *
>> >+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
>> >+ * and drawn into a surface.
>> >+ *
>> >+ * Same as windowAt(index).surface()
>> >+ */
>> >+ unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the window at the given index
>> >+ *
>> >+ * Will always be valid
>> >+ */
>> >+ Window *windowAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the application at the given index
>> >+ */
>> >+ unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the unique id of the element at the given index
>> >+ */
>> >+ int idAt(int index) const;
>> >+
>> >+ /**
>> >+ * @brief Returns the index where the row with the given id is located
>> >+ *
>> >+ * Returns -1 if there's no row with the given id.
>> >+ */
>> >+ int indexForId(int id) const;
> slots with return value are weird. All the above should be public Q_INVOKABLE instead IMO.

Which uglifies the header file. Not great either.

Done.

>
>> >+
>> >+ /**
>> >+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
>> >+ */
>> >+ void raiseId(int id);
> this makes sense as slot indeed
>
But it's only a slot so it can be called from QML. Made it a Q_INVOKABLE
as well to match the others.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +Window::Window(int id)
>> >+ : QObject(nullptr)
>> >+ , m_id(id)
>> >+{
>> >+ DEBUG_MSG << "()";
>> >+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
> is there a reason why you can't use QObject's parent instead?

Laziness. Done.

>
> Would make sense in any case to parent the Window objects properly IMO
>
Yes, that's a nice safety net.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> +QString Window::toString() const
>> >+{
>> >+ if (surface()) {
>> >+ return QString("Window[0x%1, id=%2, MirSurface[0x%3,\"%4\"]]")
>> >+ .arg((quintptr)this, 0, 16)
>> >+ .arg(id())
>> >+ .arg((quintptr)surface(), 0, 16)
>> >+ .arg(surface()->name());
> same here... if this is only ever executed in certain debug modes, ok with me, but if this is executed in production code too, please use the single .arg(..., ..., ...) call for better performance.
>
Done.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
> gah I despise this overly excessive namespacing...

Me too! Nested namespaces are an eyesore.

> IMO you could just do "using namespace unity::shell::application;" and be done with this, really... It's not like we'd ever have a conflicting MirSurfaceInterface class somewhere else...
>
Not in a header file though. I think that's bad form. A .cpp file
#including this header would inadvertently get the
unity::shell::application namespace included in their main one.

I'm also scared of doing namespace manipulations in Qt macros. That
might go wrong.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + qRegisterMetaType<Window*>("Window*");
> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>

Not sure. qmlRegisterUncreatableType() documentation says: "This is
useful where the type is only intended for providing attached properties
or enum values."
Not the case here.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> - onMinimizeClicked: root.minimizeClicked();
>> >+ onMinimizeClicked: {
>> >+ if (applicationWindow.surface) {
>> >+ applicationWindow.surface.requestState(Mir.MinimizedState);
>> >+ }
>> >+ }
> this seems odd... why would you refactor DecoratedWindow to not directly operate on the data any more but instead pass everything outside with signals in a earlier branch, but now start introducing this somewhat messy behavior here again? IMO this should also be passed to outside to the Stage, and only the stage being in charge to actually execute things on the surfaces.
>
That's some old change that pre-dates that branch you mentioned.
Reverted.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> - function focusNext() {
> same as I mentioned earlier. I'm still a bit scared of dropping control over this from the qml part, but lets see how it works... At least now that the model is part of unity8 again, it seems easier to change that back or modify in yet another way if we need to.
>
This is a perfect case where it makes sense to delegate to the C++
model. Choosing the next window do be focused when the current one is
minimized is a bunch of imperative code (and WM policy) that fits better
in C++ and not mixed with animation code in QML.

Particularly now that the model knows everything (who's focused and the
position and state of all surfaces).

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2700
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2514/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3319/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3347
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3199/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3199/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3199/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3199/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3199
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3199/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2514/rebuild

review: Needs Fixing (continuous-integration)
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> if (!shown && priv.mainStageDelegate && !root.spreadShown) {
>> >- priv.mainStageDelegate.claimFocus();
>> >+ priv.mainStageDelegate.requestFocus();
> ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>
> Can you think of a way to keep that separation perhaps?
>
Heh, I actually think it is a good thing to handle them the same way (as
I said in some other comment).

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + function updateQmlFocusFromMirSurfaceFocus() {
>> >+ if (model.window.focused) {
>> > claimFocus();
>> >+ priv.focusedAppDelegate = appDelegate;
>> >+ //} else if (priv.focusedAppDelegate === appDelegate && root.state != "spread") {
>> >+ // priv.focusedAppDelegate = null;
> leftover?
>
Yes, removed.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 12:29, Michael Zanetti wrote:
>> + onMaximizeHorizontallyClicked: {
>> >+ if (appDelegate.maximizedHorizontally)
>> >+ appDelegate.requestRestore();
>> >+ else
>> >+ appDelegate.requestMaximizeHorizontally();
>> >+ }
> {} missing
>
>> >+ onMaximizeVerticallyClicked: {
>> >+ if (appDelegate.maximizedVertically)
>> >+ appDelegate.requestRestore();
>> >+ else
>> >+ appDelegate.requestMaximizeVertically();
>> >+ }
> {} missing
>

Not sure if that's in our coding style but ok, added.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 14:16, Michael Zanetti wrote:
> Review: Needs Fixing
>
> Not yet sure if just an issue in the mocks or not, but this breaks:
>
> - open an app
> - click the minimize button in the window decoration
> - click the app again in the launcher to restore it
> - now click the minimize button again
> => button doesn't work any more

Bug in the mock. Works with qtmir.

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2705
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2515/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3320/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3348
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3200/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3200/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3200/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3200/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3200
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3200/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2515/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2706
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2516/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3321/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3349
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3201/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3201/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3201/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3201/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3201
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3201/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2516/rebuild

review: Needs Fixing (continuous-integration)
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

>>>> - priv.mainStageDelegate.claimFocus();
>>>> >> >+ priv.mainStageDelegate.requestFocus();
>> > ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>> >
>> > Can you think of a way to keep that separation perhaps?
>> >
> Heh, I actually think it is a good thing to handle them the same way (as
> I said in some other comment).

How does focus stealing prevention come into play here? I believe the
plan was to only grant focus to apps that send along a ~MirCookie based
on a timestamp. I believe it's described here:

https://docs.google.com/document/d/1L85DdfDd3lDbvchYbgQ45C_lJ1IeTMG4uc7Nuq_XdAE/edit#heading=h.nlenkblnkaqq

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

On 18.11.2016 21:09, Daniel d'Andrada wrote:
> On 18/11/2016 12:29, Michael Zanetti wrote:
>>> + qRegisterMetaType<Window*>("Window*");
>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>
>
> Not sure. qmlRegisterUncreatableType() documentation says: "This is
> useful where the type is only intended for providing attached properties
> or enum values."
> Not the case here.

I'm more thinking about being able to access the Window type in QML
altogether. Not really about providing attached properties etc, but it
could make sense to do pass the Window object to somewhere in QML and
access its property there without having access to the model.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> On 18/11/2016 12:29, Michael Zanetti wrote:
> > * I think we should not give up on the differntiation between claimFocus()
> and requestFocus(). One of them says the stage wants to actually focus
> something*now* while the other indicates that the app requested focus on its
> own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still
> do the raisId() call on the model. requestfocus() instead should end up in the
> shell, and if the shell is ok with doing so, it should call claimFocus()
> itself.
>
> What you are explaining effectively is that: leave things as they are
> currently with regards to focus. Which means focus lives in unity8 and
> miral has no say over it.
>
> The whole point of this miral effort is exactly to move such window
> management decisions down to Mir, as dictated by our high-profile
> stakeholder.

There's a bunch of other stakeholders though that want to have it pixel perfect when it comes to animations and transitions. I am not saying miral should not have any say, but miral should send a requestFocus() to the shell and the shell will do it.
What I'm saying is that miral can tell unity to change the focus, and unity will follow that (unless it's in the middle of a transition or something). What you're proposing is that miral has all the say on when to change focus, and unity8 cannot do any course correction if the moment is bad.

> Focus is a central piece of window management and if miral is to do
> anything useful it has to have a say in it. And we cannot have two
> entities making decisions on focus (unity8 and miral) as it will
> inevitably be racy and conflictive.

which is exactly what we have now... unity8 thinks it is in charge, but miral can just jump in and trash it. I'm really just trying to avoid that.

>
> As for the origin of the focus change request (shell vs. application),
> it doesn't matter from miral's standpoint. It will comply to either as
> long the change is valid and makes sense. Eg.: it should never allow a
> window blocked by a child modal dialog to be focused. So it's mostly
> sanity checking. I don't see miral ignoring requests on a whim. :)
>
> Besides, having two separate code paths would make unity8 code more
> complex. Eg: Having both code that sets surface state and code that
> responds to surface state changes. "If surface state change was done in
> response my your own set(), do nothing, else, ponder and respond."

And I'm also not talking about 2 separate code paths. I'm saying the one code path should go through unity8 and not just manipulate the model at random.

Therefore I think only unity8 should manipulate the model, taking miral as consultant on how to manipulate it. This current approach is really the one that creates multiple entities fighting over the state of the model.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 18/11/2016 14:21, Michael Zanetti wrote:
> Review: Needs Fixing
>
> - have 3 apps (a, b and c), make them overlap a bit for better visibility
> - focus a by clicking it
> - focus b by clicking it
> - focus c by clicking it
> - minimize c
> => a will be focused
>
> expected: after minimizing c, b should be focused

Can't reproduce in "make tryDesktopStage". Need more specific steps
(like exact test and apps launched).

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 21/11/2016 07:27, Michael Zanetti wrote:
>
> On 18.11.2016 21:09, Daniel d'Andrada wrote:
>> On 18/11/2016 12:29, Michael Zanetti wrote:
>>>> + qRegisterMetaType<Window*>("Window*");
>>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>>
>> Not sure. qmlRegisterUncreatableType() documentation says: "This is
>> useful where the type is only intended for providing attached properties
>> or enum values."
>> Not the case here.
> I'm more thinking about being able to access the Window type in QML
> altogether. Not really about providing attached properties etc, but it
> could make sense to do pass the Window object to somewhere in QML and
> access its property there without having access to the model.
>
>
This already happens in Stage.qml:

"""
x: model.window.position.x - decoratedWindow.contentX
y: model.window.position.y - decoratedWindow.contentY
"""

Looks like qmlRegisterUncreatableType() is not needed for that. If think
you only need it for Window.foo type of constructs.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 21/11/2016 06:48, Michał Sawicz wrote:
>>>>> - priv.mainStageDelegate.claimFocus();
>>>>>>>> + priv.mainStageDelegate.requestFocus();
>>>> ok... given that requestFocus() effectively does claimFocus() now, this change totally makes sense... but we're losing the distinction between "the stage wants to focus something" and "an app requests focus by itsel (or something else than the stage)".
>>>>
>>>> Can you think of a way to keep that separation perhaps?
>>>>
>> Heh, I actually think it is a good thing to handle them the same way (as
>> I said in some other comment).
> How does focus stealing prevention come into play here? I believe the
> plan was to only grant focus to apps that send along a ~MirCookie based
> on a timestamp. I believe it's described here:
>
> https://docs.google.com/document/d/1L85DdfDd3lDbvchYbgQ45C_lJ1IeTMG4uc7Nuq_XdAE/edit#heading=h.nlenkblnkaqq
>
>
This happens at a level lower than unity8. All unity8 knows and cares
about is that some surface in the model got focus.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

On 21.11.2016 12:09, Daniel d'Andrada wrote:
> On 21/11/2016 07:27, Michael Zanetti wrote:
>>
>> On 18.11.2016 21:09, Daniel d'Andrada wrote:
>>> On 18/11/2016 12:29, Michael Zanetti wrote:
>>>>> + qRegisterMetaType<Window*>("Window*");
>>>> think it would make sense to qmlRegisterUncreatableType<Window>(...) too? Seems it would be useful API to be able to access Window from QML
>>>>
>>> Not sure. qmlRegisterUncreatableType() documentation says: "This is
>>> useful where the type is only intended for providing attached properties
>>> or enum values."
>>> Not the case here.
>> I'm more thinking about being able to access the Window type in QML
>> altogether. Not really about providing attached properties etc, but it
>> could make sense to do pass the Window object to somewhere in QML and
>> access its property there without having access to the model.
>>
>>
> This already happens in Stage.qml:
>
> """
> x: model.window.position.x - decoratedWindow.contentX
> y: model.window.position.y - decoratedWindow.contentY
> """
>
> Looks like qmlRegisterUncreatableType() is not needed for that. If think
> you only need it for Window.foo type of constructs.

Hmm, ok... fine then.

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2707
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2517/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3322/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3350
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3202/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3202/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3202/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3202/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3202
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3202/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2517/rebuild

review: Needs Fixing (continuous-integration)
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> On 18/11/2016 14:21, Michael Zanetti wrote:
> > Review: Needs Fixing
> >
> > - have 3 apps (a, b and c), make them overlap a bit for better visibility
> > - focus a by clicking it
> > - focus b by clicking it
> > - focus c by clicking it
> > - minimize c
> > => a will be focused
> >
> > expected: after minimizing c, b should be focused
>
>
> Can't reproduce in "make tryDesktopStage". Need more specific steps
> (like exact test and apps launched).

Hmm... I can't reproduce it any more either after your latest changes.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> On 18/11/2016 14:16, Michael Zanetti wrote:
> > Review: Needs Fixing
> >
> > Not yet sure if just an issue in the mocks or not, but this breaks:
> >
> > - open an app
> > - click the minimize button in the window decoration
> > - click the app again in the launcher to restore it
> > - now click the minimize button again
> > => button doesn't work any more
>
> Bug in the mock. Works with qtmir.

This is really why I think we need to move all those models to unity8, put unity8 in control of them and mock things at a lower level. All our tests regarding window management are just useless right now... they can perfectly pass while the real thing can be broken really badly.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 21/11/2016 10:08, Michael Zanetti wrote:
>> On 18/11/2016 14:16, Michael Zanetti wrote:
>>> Review: Needs Fixing
>>>
>>> Not yet sure if just an issue in the mocks or not, but this breaks:
>>>
>>> - open an app
>>> - click the minimize button in the window decoration
>>> - click the app again in the launcher to restore it
>>> - now click the minimize button again
>>> => button doesn't work any more
>> Bug in the mock. Works with qtmir.
> This is really why I think we need to move all those models to unity8, put unity8 in control of them and mock things at a lower level. All our tests regarding window management are just useless right now... they can perfectly pass while the real thing can be broken really badly.

Window management policy things like "if currently focused window gets
minimized, focus the most recently focused window that is not hidden or
minimized" will now belong to a lower level (miral) and as such they get
mock implementations in our tests.

Thus qml tests that strictly check window management policy will indeed
get redundant. Test of this nature now belong to miral's own test suite.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> On 21/11/2016 10:08, Michael Zanetti wrote:
> >> On 18/11/2016 14:16, Michael Zanetti wrote:
> >>> Review: Needs Fixing
> >>>
> >>> Not yet sure if just an issue in the mocks or not, but this breaks:
> >>>
> >>> - open an app
> >>> - click the minimize button in the window decoration
> >>> - click the app again in the launcher to restore it
> >>> - now click the minimize button again
> >>> => button doesn't work any more
> >> Bug in the mock. Works with qtmir.
> > This is really why I think we need to move all those models to unity8, put
> unity8 in control of them and mock things at a lower level. All our tests
> regarding window management are just useless right now... they can perfectly
> pass while the real thing can be broken really badly.
>
>
> Window management policy things like "if currently focused window gets
> minimized, focus the most recently focused window that is not hidden or
> minimized" will now belong to a lower level (miral) and as such they get
> mock implementations in our tests.
>
> Thus qml tests that strictly check window management policy will indeed
> get redundant. Test of this nature now belong to miral's own test suite.

Right, but we would still need tests that make sure that the QML part works together with the model as we expect it. And right now we don't have such tests... Anyhow, this is a different discussion than this particular merge proposal. It just once again popped up here. Also this isn't a new problem, we already had this since forever, but as long as we only had the one ApplicationManager model which would not even do much except moving things when unity8 said so, it was still easy enough to keep the mock close enough to the real thing. Now this seems to grow out of a manageable size.

Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal
Download full text (3.3 KiB)

> > On 18/11/2016 12:29, Michael Zanetti wrote:
> > > * I think we should not give up on the differntiation between claimFocus()
> > and requestFocus(). One of them says the stage wants to actually focus
> > something*now* while the other indicates that the app requested focus on
> its
> > own (or perhaps u-a-l did). IMO claimFocus() should not be dropped and still
> > do the raisId() call on the model. requestfocus() instead should end up in
> the
> > shell, and if the shell is ok with doing so, it should call claimFocus()
> > itself.
> >
> > What you are explaining effectively is that: leave things as they are
> > currently with regards to focus. Which means focus lives in unity8 and
> > miral has no say over it.
> >
> > The whole point of this miral effort is exactly to move such window
> > management decisions down to Mir, as dictated by our high-profile
> > stakeholder.
>
> There's a bunch of other stakeholders though that want to have it pixel
> perfect when it comes to animations and transitions. I am not saying miral
> should not have any say, but miral should send a requestFocus() to the shell
> and the shell will do it.
> What I'm saying is that miral can tell unity to change the focus, and unity
> will follow that (unless it's in the middle of a transition or something).
> What you're proposing is that miral has all the say on when to change focus,
> and unity8 cannot do any course correction if the moment is bad.
>
> > Focus is a central piece of window management and if miral is to do
> > anything useful it has to have a say in it. And we cannot have two
> > entities making decisions on focus (unity8 and miral) as it will
> > inevitably be racy and conflictive.
>
> which is exactly what we have now... unity8 thinks it is in charge, but miral
> can just jump in and trash it. I'm really just trying to avoid that.
>
> >
> > As for the origin of the focus change request (shell vs. application),
> > it doesn't matter from miral's standpoint. It will comply to either as
> > long the change is valid and makes sense. Eg.: it should never allow a
> > window blocked by a child modal dialog to be focused. So it's mostly
> > sanity checking. I don't see miral ignoring requests on a whim. :)
> >
> > Besides, having two separate code paths would make unity8 code more
> > complex. Eg: Having both code that sets surface state and code that
> > responds to surface state changes. "If surface state change was done in
> > response my your own set(), do nothing, else, ponder and respond."
>
> And I'm also not talking about 2 separate code paths. I'm saying the one code
> path should go through unity8 and not just manipulate the model at random.
>
> Therefore I think only unity8 should manipulate the model, taking miral as
> consultant on how to manipulate it. This current approach is really the one
> that creates multiple entities fighting over the state of the model.

This is a valid concern. The model must only have one owner. I think it safest that Unity8 has total ownership of the model and uses MirAL's recommendations only when it can.

MirAL has no concept of intermediary-states (like transitions/animations cause), so cannot advise Unit...

Read more...

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

Thanks a lot for the activate() thing. Makes me feel much more comfy with the whole thing.

One minor thing (see inline comments) which I missed last time.

I'll give it another test with the latest changes, but it looks like we're getting close to an approval now.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 23/11/2016 09:24, Michael Zanetti wrote:
>> readonly property Item clientAreaItem: applicationWindow
>> >
>> >+ readonly property real contentX: applicationWindow.x
>> >+ readonly property real contentY: applicationWindow.y
> afaict, you're only using that for knowing how much to add/remove for the decoration height. There is already a property "decorationHeight" you can use so this would be not needed, esp as applicationWindow.y will always be 0 at this point too.
>
Actually I can just use the existing clientAreaItem property right above
them. Fixed.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 23/11/2016 09:24, Michael Zanetti wrote:
> Qt.point(appDelegate.requestedX + decoratedWindow.decorationHeight, appDelegate.requestedY)
>
> should be enough

I would rather not assume that decorations only affect the Y coordinate.

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2715
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2558/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3369/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3397
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/3248/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3248
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3248/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3248
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3248/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3248/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3248/console

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2558/rebuild

review: Needs Fixing (continuous-integration)
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

New instances of the same app do not cascade:

0. make tryShell
1. Launch dialer
2. Click the "+" button to launch a second instance
3. The new window doesn't cascade

review: Needs Fixing
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

If you add a trust prompt to a window and launch a second instance of the same app, the newly created window is not focused

review: Needs Fixing
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

Windows are not restored properly:

1. Maximize dash
2. Minimize it
3. Click the bfb icon
4. The dash window is restored to "normal" size, not to the maximized from step 1.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 29/11/2016 15:35, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> New instances of the same app do not cascade:
>
> 0. make tryShell
> 1. Launch dialer
> 2. Click the "+" button to launch a second instance
> 3. The new window doesn't cascade

Same happens in trunk

Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

Double click to maximize breaks:

1. Double click the dash decoration to maximize the window
2. Drag it down from the panel to restore it
3. Double click again to maximize -> no longer works

review: Needs Fixing
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

In tablet mode, if you put a fullscreen app into side stage (like camera), the main app (dash usually) has its decoration cut at the top.

review: Needs Fixing
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> In tablet mode, if you put a fullscreen app into side stage (like camera), the
> main app (dash usually) has its decoration cut at the top.

This happens in trunk too

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 29/11/2016 15:37, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> If you add a trust prompt to a window and launch a second instance of the same app, the newly created window is not focused

Fixed. Bug in the Unity.Application mock.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 29/11/2016 15:39, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> Windows are not restored properly:
>
> 1. Maximize dash
> 2. Minimize it
> 3. Click the bfb icon
> 4. The dash window is restored to "normal" size, not to the maximized from step 1.

That's due to the simplicity of the Unity.Application mock. It doesn't
have a full blown implementation of the window management bits/policy
that have been now moved to a lower level, behind
Unity.Application/qtmir API. This use case works with the real
Unity.Application/qtmir.

Unfortunately this means that right now we can no longer test everything
window management related in our qmltests. For some things untiy8 just
sits back and expects to get the correct changes to the model for free,
which it will then respond to by updating the qml items and animating
the qml scene (the view).

To be more specific, in response to the click on step 3 unity8 just
calls MirSurface->activate(). qtmir/miral will take care of noticing
that this surface is minimized and will restore it to its previous state
before finally focusing and raising it.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 29/11/2016 15:50, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> Double click to maximize breaks:
>
> 1. Double click the dash decoration to maximize the window
> 2. Drag it down from the panel to restore it
> 3. Double click again to maximize -> no longer works

Fixed.

Lukáš Tinkl (lukas-kde) wrote :

Doesn't compile any more (or run), need this patch: https://pastebin.kde.org/pipwxeqjp

review: Needs Fixing
lp:~dandrader/unity8/miral updated on 2016-12-01
2726. By Launchpad Translations on behalf of unity-team on 2016-12-01

Launchpad automatic translations update.

Daniel d'Andrada (dandrader) wrote :

On 30/11/2016 18:55, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> Doesn't compile any more (or run), need this patch: https://pastebin.kde.org/pipwxeqjp

Ooops! Fixed, thanks.

Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2729
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2625/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3457
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1981
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1981
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3485
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3332/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3332/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3332/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3332/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3332/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3332
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3332/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2625/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2730
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2626/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3458
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1982
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1982
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3486
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3333/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3333/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3333/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3333/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3333/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3333
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3333/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2626/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2733
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2634/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3466
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1989
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1989
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3494
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3341/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3341/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3341/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3341/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3341/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3341
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3341/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2634/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2734
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2637/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3469
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1991
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1991
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3497
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3344/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3344/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3344/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3344/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3344/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3344
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3344/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2637/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2736
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2638/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3470
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1992
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1992
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3498
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3345/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3345/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3345/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3345/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3345/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3345
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3345/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2638/rebuild

review: Needs Fixing (continuous-integration)
review: Approve
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2737
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2641/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3473
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1995
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1995
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3501
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3348/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3348/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3348/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3348/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3348/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3348
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3348/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2641/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2738
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2643/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3475
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1997
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1997
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3503
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3350/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3350/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3350/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3350/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3350/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3350
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3350/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2643/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2742
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2646/
Executed test runs:

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2646/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2742
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2645/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3477
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/1999
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/1999
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3505
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3352/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3352/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3352/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3352/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3352/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3352
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3352/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2645/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2743
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2648/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3481
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2001
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2001
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3509
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3356/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3356/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3356/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3356/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3356/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3356
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3356/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2648/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2744
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2651/
Executed test runs:

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2651/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2744
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2650/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3483
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2003
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2003
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3511
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3358/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3358/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3358/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3358/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3358/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3358
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3358/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2650/rebuild

review: Approve (continuous-integration)
Lukáš Tinkl (lukas-kde) wrote :

Looking good except this hard to track focus related issue:

https://imgur.com/a/ZcR0A

It can happen when an application is taking a long time to start up, and you close it before it does. At that point, the focus chain is broken and the user is left with no app/window focused at all (as can be seen from the empty window title in the top panel)

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

> Looking good except this hard to track focus related issue:
>
> https://imgur.com/a/ZcR0A
>
> It can happen when an application is taking a long time to start up, and you
> close it before it does. At that point, the focus chain is broken and the user
> is left with no app/window focused at all (as can be seen from the empty
> window title in the top panel)

Fixed, thanks.

Lukáš Tinkl (lukas-kde) wrote :

In QString Window::toString() const:

I think what you're really after is this: http://doc.qt.io/qt-5/qdebug.html#writing-custom-types-to-a-stream

review: Needs Information
Daniel d'Andrada (dandrader) wrote :

On 06/12/2016 12:57, Lukáš Tinkl wrote:
> Review: Needs Information
>
> In QString Window::toString() const:
>
> I think what you're really after is this: http://doc.qt.io/qt-5/qdebug.html#writing-custom-types-to-a-stream

Already have it. It uses toString() in its implementation.

Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2746
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2659/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3493
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2010
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2010
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3521
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3368/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3368/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3368/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3368/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3368/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3368
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3368/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2659/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2746
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2663/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/3497/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3525
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3372
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3372/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3372
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3372/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3372
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3372/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3372
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3372/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3372
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3372/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3372/console

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2663/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2746
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2665/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3499
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2015
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2015
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3527
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3374/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3374/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3374/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3374/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3374/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3374
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3374/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2665/rebuild

review: Approve (continuous-integration)
Lukáš Tinkl (lukas-kde) wrote :

* Did you perform an exploratory manual test run of the code change and any related functionality?

Yes

* Did CI run pass? If not, please explain why.

Yes (they will again once the silo gets rebuilt)

review: Approve
lp:~dandrader/unity8/miral updated on 2016-12-12
2727. By Lukáš Tinkl on 2016-12-07

Fix the Super key not invoking the dash scope home (LP: #1607427)

Approved by: Daniel d'Andrada, Unity8 CI Bot

2728. By Albert Astals Cid on 2016-12-07

Add the Wsuggest-override flag to gcc

While at it mark system includes as such so we don't get warnings we can not fix

Approved by: Michael Zanetti, Unity8 CI Bot

2729. By Albert Astals Cid on 2016-12-07

Add support for compiler sanitizers via ECM

2730. By Albert Astals Cid on 2016-12-07

Use timeStep as delay time

Passing iterations / speed didn't make much sense since that parameter is a delay in milliseconds and the default parameters would give a value of 5 / units.gu(10) that is smaller than 1 millisecond.

Qt 5.7 calculation for velocity was very unhappy if we moved things so fast in less than 1ms and ignored the movements, so this also makes tests pass on Qt 5.7 (LP: #1642919)

Approved by: Josh Arenson, Unity8 CI Bot

2731. By Michael Zanetti on 2016-12-07

Add the ApplicationDrawer

Approved by: Lukáš Tinkl, Unity8 CI Bot

2732. By Michael Zanetti on 2016-12-07

tune right edge push

make it less intrusive when accidentally hitting the edge with the mouse
tweak visuals for the mouse case (LP: #1646094)

Approved by: Unity8 CI Bot

2733. By Michael Zanetti on 2016-12-07

improve close button visiblity when hovering with the mouse

Approved by: Albert Astals Cid, Unity8 CI Bot

2734. By Albert Astals Cid on 2016-12-07

Bring back fix for 1517830

Now with autotest \o/ (LP: #1517830)

Approved by: Andrea Cimitan, Unity8 CI Bot

2735. By Daniel d'Andrada on 2016-12-07

Fix "make tryApplicationWindow"

No surface was showing up on the screen
Also remove outdated button (feature is no longer there)

Approved by: Albert Astals Cid, Unity8 CI Bot

2736. By Albert Astals Cid on 2016-12-07

Fix compile warnings in mocks

Approved by: Daniel d'Andrada, Unity8 CI Bot

2737. By Josh Arenson on 2016-12-07

Enable the greeter to remember which session the user last logged into

This also fixes a small issue with how the default session was handled. (LP: #1631365)

Approved by: Albert Astals Cid, Unity8 CI Bot

2738. By Daniel d'Andrada on 2016-12-07

Take save/restore functions out of WindowResizeArea

They've no relationship with resizing whatsoever.

Approved by: Lukáš Tinkl, Unity8 CI Bot

2739. By Michael Zanetti on 2016-12-07

Update virtual touchpad visuals and add a tutorial. (LP: #1585220)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2740. By Albert Astals Cid on 2016-12-07

Do not hide panel when launching an application if the mouse is on the panel

Need Functions.itemUnderMouse because MouseArea.containsMouse returns true when tapping (i.e. no mouse used) on it.

Unfortunately the QML testlib do not set the proper value when issueing a mouseMove so i can't add a test that proofs it works, i'll try to propose something upstream and then add the test at a later MR (LP: #1591311)

Approved by: Lukáš Tinkl, Unity8 CI Bot

2741. By Pete Woods on 2016-12-07

MenuItemFactory: Add subtitle support to SwitchItem widget

Approved by: Marco Trevisan (Treviño), Michał Sawicz, Unity8 CI Bot

2742. By CI Train Bot Account on 2016-12-07

Releasing 8.15+17.04.20161207.1-0ubuntu1

2743. By Daniel d'Andrada on 2016-12-12

Let the model deal with some window management decisions

eg: which window to focus, whether to change surface state

unit8 requests and reacts to changes in the model instead of applying them

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-10-24 11:34:08 +0000
3+++ CMakeLists.txt 2016-12-12 16:49:02 +0000
4@@ -40,6 +40,19 @@
5 ENABLE_COVERAGE_REPORT(TARGETS ${SHELL_APP} FILTER /usr/include ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/*)
6 endif()
7
8+find_package (ECM 1.7.0 QUIET NO_MODULE)
9+if (ECM_FOUND)
10+ # Provides us with -DECM_ENABLE_SANITIZERS='X'
11+ # Where X can be address, thread, memory, leak, undefined
12+ include("${ECM_MODULE_DIR}/ECMEnableSanitizers.cmake")
13+endif()
14+
15+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
16+ if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0.0")
17+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" )
18+ endif()
19+endif()
20+
21 # Make sure we have all the needed symbols
22 set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,defs")
23 set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,defs")
24@@ -57,7 +70,7 @@
25 find_package(Qt5Concurrent 5.4 REQUIRED)
26 find_package(Qt5Sql 5.4 REQUIRED)
27
28-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
29+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
30 pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
31 pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
32 pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
33
34=== modified file 'data/com.canonical.Unity8.gschema.xml'
35--- data/com.canonical.Unity8.gschema.xml 2016-11-16 05:54:50 +0000
36+++ data/com.canonical.Unity8.gschema.xml 2016-12-12 16:49:02 +0000
37@@ -12,7 +12,7 @@
38 <description>The usage mode chosen will affect the Window Management behaviour.</description>
39 </key>
40 <key type="y" name="edge-barrier-sensitivity">
41- <default>35</default>
42+ <default>50</default>
43 <range min="1" max="100"/>
44 <summary>Sensitivity of screen edge barriers for the mouse pointer.</summary>
45 <description>Some UI actions like revealing the launcher or the applications spread are triggered by pushing the mouse pointer against a screen edge. This key defines how much you have to push in order to trigger the associated action.</description>
46@@ -23,7 +23,7 @@
47 <description>How much you have to push (in grid units)the mouse against an edge barrier when sensibility is 100.</description>
48 </key>
49 <key type="u" name="edge-barrier-max-push">
50- <default>60</default>
51+ <default>120</default>
52 <summary>Maximum push needed to overcome edge barrier</summary>
53 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
54 </key>
55
56=== modified file 'debian/changelog'
57--- debian/changelog 2016-11-29 09:43:33 +0000
58+++ debian/changelog 2016-12-12 16:49:02 +0000
59@@ -1,3 +1,36 @@
60+unity8 (8.15+17.04.20161207.1-0ubuntu1) zesty; urgency=medium
61+
62+ [ Albert Astals Cid ]
63+ * Add the Wsuggest-override flag to gcc
64+ * Add support for compiler sanitizers via ECM
65+ * Use timeStep as delay time (LP: #1642919)
66+ * Bring back fix for 1517830 (LP: #1517830)
67+ * Fix compile warnings in mocks
68+ * Do not hide panel when launching an application if the mouse is on
69+ the panel (LP: #1591311)
70+
71+ [ Daniel d'Andrada ]
72+ * Fix "make tryApplicationWindow"
73+ * Take save/restore functions out of WindowResizeArea
74+
75+ [ Josh Arenson ]
76+ * Enable the greeter to remember which session the user last logged
77+ into (LP: #1631365)
78+
79+ [ Lukáš Tinkl ]
80+ * Fix the Super key not invoking the dash scope home (LP: #1607427)
81+
82+ [ Michael Zanetti ]
83+ * Add the ApplicationDrawer
84+ * tune right edge push (LP: #1646094)
85+ * improve close button visiblity when hovering with the mouse
86+ * Update virtual touchpad visuals and add a tutorial. (LP: #1585220)
87+
88+ [ Pete Woods ]
89+ * MenuItemFactory: Add subtitle support to SwitchItem widget
90+
91+ -- Michał Sawicz <michal.sawicz@canonical.com> Wed, 07 Dec 2016 13:49:24 +0000
92+
93 unity8 (8.15+17.04.20161129-0ubuntu1) zesty; urgency=medium
94
95 [ Albert Astals Cid ]
96
97=== modified file 'debian/control'
98--- debian/control 2016-11-29 09:40:16 +0000
99+++ debian/control 2016-12-12 16:49:02 +0000
100@@ -38,7 +38,7 @@
101 libubuntugestures5-private-dev (>= 1.3.2030),
102 libudev-dev,
103 libudm-common-dev,
104- libunity-api-dev (>= 7.120),
105+ libunity-api-dev (>= 8.0),
106 libusermetricsoutput1-dev,
107 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
108 libx11-dev[!arm64 !armhf],
109@@ -49,6 +49,7 @@
110 python3-all:any,
111 python3-setuptools,
112 qml-module-qt-labs-folderlistmodel,
113+ qml-module-qt-labs-settings,
114 qml-module-qtqml-statemachine,
115 qml-module-qtmultimedia (>= 5.4.1-1ubuntu19~overlay2),
116 qml-module-qtquick-layouts,
117@@ -122,6 +123,7 @@
118 qmenumodel-qml (>= 0.2.10),
119 qml-module-biometryd,
120 qml-module-qt-labs-folderlistmodel,
121+ qml-module-qt-labs-settings,
122 qml-module-qtqml-statemachine,
123 qml-module-qtquick-xmllistmodel,
124 qml-module-qtsysteminfo,
125@@ -160,7 +162,7 @@
126 qttranslations5-l10n,
127 ubuntu-thumbnailer-impl-0,
128 ubuntu-wallpapers,
129- unity-application-impl-22,
130+ unity-application-impl-23,
131 unity-notifications-impl-3,
132 unity-plugin-scopes | unity-scopes-impl,
133 unity-scopes-impl-12,
134@@ -206,7 +208,7 @@
135 Depends: ${misc:Depends},
136 ${shlibs:Depends},
137 Provides: unity-application-impl,
138- unity-application-impl-22,
139+ unity-application-impl-23,
140 Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
141 Description: Fake environment for running Unity 8 shell
142 Provides fake implementations of some QML modules used by Unity 8 shell
143
144=== modified file 'plugins/Greeter/Unity/Launcher/CMakeLists.txt'
145--- plugins/Greeter/Unity/Launcher/CMakeLists.txt 2016-10-28 12:08:59 +0000
146+++ plugins/Greeter/Unity/Launcher/CMakeLists.txt 2016-12-12 16:49:02 +0000
147@@ -1,4 +1,4 @@
148-pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
149+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
150 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
151
152 add_definitions(-DSM_BUSNAME=systemBus)
153
154=== modified file 'plugins/Greeter/Unity/Launcher/launcheritem.cpp'
155--- plugins/Greeter/Unity/Launcher/launcheritem.cpp 2016-05-10 16:23:34 +0000
156+++ plugins/Greeter/Unity/Launcher/launcheritem.cpp 2016-12-12 16:49:02 +0000
157@@ -76,6 +76,19 @@
158 }
159 }
160
161+QStringList LauncherItem::keywords() const
162+{
163+ return m_keywords;
164+}
165+
166+void LauncherItem::setKeywords(const QStringList &keywords)
167+{
168+ if (m_keywords != keywords) {
169+ m_keywords = keywords;
170+ Q_EMIT keywordsChanged(keywords);
171+ }
172+}
173+
174 bool LauncherItem::pinned() const
175 {
176 return m_pinned;
177
178=== modified file 'plugins/Greeter/Unity/Launcher/launcheritem.h'
179--- plugins/Greeter/Unity/Launcher/launcheritem.h 2016-05-10 15:51:00 +0000
180+++ plugins/Greeter/Unity/Launcher/launcheritem.h 2016-12-12 16:49:02 +0000
181@@ -34,6 +34,7 @@
182 QString appId() const override;
183 QString name() const override;
184 QString icon() const override;
185+ QStringList keywords() const override;
186 bool pinned() const override;
187 bool running() const override;
188 bool recent() const override;
189@@ -49,6 +50,7 @@
190 private:
191 void setName(const QString &name);
192 void setIcon(const QString &icon);
193+ void setKeywords(const QStringList &keywords);
194 void setPinned(bool pinned);
195 void setRunning(bool running);
196 void setRecent(bool recent);
197@@ -63,6 +65,7 @@
198 QString m_appId;
199 QString m_name;
200 QString m_icon;
201+ QStringList m_keywords;
202 bool m_pinned;
203 bool m_running;
204 bool m_recent;
205
206=== modified file 'plugins/LightDM/FullLightDM/CMakeLists.txt'
207--- plugins/LightDM/FullLightDM/CMakeLists.txt 2016-07-08 15:44:53 +0000
208+++ plugins/LightDM/FullLightDM/CMakeLists.txt 2016-12-12 16:49:02 +0000
209@@ -6,8 +6,12 @@
210
211 include_directories(
212 ../
213+ ${CMAKE_CURRENT_BINARY_DIR}
214+ )
215+
216+include_directories(
217+ SYSTEM
218 ${LIBLIGHTDM_INCLUDE_DIRS}
219- ${CMAKE_CURRENT_BINARY_DIR}
220 )
221
222 foreach(source_file ${QMLPLUGIN_SRC})
223
224=== modified file 'plugins/Unity/Indicators/CMakeLists.txt'
225--- plugins/Unity/Indicators/CMakeLists.txt 2016-06-02 09:32:33 +0000
226+++ plugins/Unity/Indicators/CMakeLists.txt 2016-12-12 16:49:02 +0000
227@@ -11,6 +11,9 @@
228 include_directories(
229 SYSTEM
230 ${GLIB_INCLUDE_DIRS}
231+)
232+include_directories(
233+ SYSTEM
234 ${GIO_INCLUDE_DIRS}
235 ${QMENUMODEL_INCLUDE_DIRS}
236 )
237
238=== modified file 'plugins/Unity/Launcher/CMakeLists.txt'
239--- plugins/Unity/Launcher/CMakeLists.txt 2016-10-28 12:08:59 +0000
240+++ plugins/Unity/Launcher/CMakeLists.txt 2016-12-12 16:49:02 +0000
241@@ -1,4 +1,4 @@
242-pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=10)
243+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
244 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
245
246 add_definitions(-DSM_BUSNAME=systemBus)
247@@ -25,6 +25,8 @@
248 dbusinterface.cpp
249 gsettings.cpp
250 asadapter.cpp
251+ appdrawermodel.cpp
252+ ualwrapper.cpp
253 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
254 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
255 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
256@@ -32,6 +34,7 @@
257 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
258 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
259 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
260+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
261 )
262
263 add_library(UnityLauncher-qml MODULE
264
265=== added file 'plugins/Unity/Launcher/appdrawermodel.cpp'
266--- plugins/Unity/Launcher/appdrawermodel.cpp 1970-01-01 00:00:00 +0000
267+++ plugins/Unity/Launcher/appdrawermodel.cpp 2016-12-12 16:49:02 +0000
268@@ -0,0 +1,62 @@
269+/*
270+ * Copyright (C) 2016 Canonical, Ltd.
271+ *
272+ * This program is free software; you can redistribute it and/or modify
273+ * it under the terms of the GNU General Public License as published by
274+ * the Free Software Foundation; version 3.
275+ *
276+ * This program is distributed in the hope that it will be useful,
277+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
278+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
279+ * GNU General Public License for more details.
280+ *
281+ * You should have received a copy of the GNU General Public License
282+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
283+ */
284+
285+#include "appdrawermodel.h"
286+#include "ualwrapper.h"
287+
288+#include <QDebug>
289+#include <QDateTime>
290+
291+AppDrawerModel::AppDrawerModel(QObject *parent):
292+ AppDrawerModelInterface(parent)
293+{
294+ Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
295+ UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
296+ if (!info.valid) {
297+ qWarning() << "Failed to get app info for app" << appId;
298+ continue;
299+ }
300+ m_list.append(new LauncherItem(appId, info.name, info.icon, this));
301+ m_list.last()->setKeywords(info.keywords);
302+ }
303+ qsrand(QDateTime::currentMSecsSinceEpoch() / 100);
304+}
305+
306+int AppDrawerModel::rowCount(const QModelIndex &parent) const
307+{
308+ Q_UNUSED(parent)
309+ return m_list.count();
310+}
311+
312+QVariant AppDrawerModel::data(const QModelIndex &index, int role) const
313+{
314+ switch (role) {
315+ case RoleAppId:
316+ return m_list.at(index.row())->appId();
317+ case RoleName:
318+ return m_list.at(index.row())->name();
319+ case RoleIcon:
320+ return m_list.at(index.row())->icon();
321+ case RoleKeywords:
322+ return m_list.at(index.row())->keywords();
323+ case RoleUsage:
324+ // FIXME: u-a-l needs to provide API for usage stats.
325+ // don't forget to drop the qsrand() call in the ctor when dropping this.
326+ return qrand();
327+ }
328+
329+ return QVariant();
330+}
331
332=== added file 'plugins/Unity/Launcher/appdrawermodel.h'
333--- plugins/Unity/Launcher/appdrawermodel.h 1970-01-01 00:00:00 +0000
334+++ plugins/Unity/Launcher/appdrawermodel.h 2016-12-12 16:49:02 +0000
335@@ -0,0 +1,33 @@
336+/*
337+ * Copyright (C) 2016 Canonical, Ltd.
338+ *
339+ * This program is free software; you can redistribute it and/or modify
340+ * it under the terms of the GNU General Public License as published by
341+ * the Free Software Foundation; version 3.
342+ *
343+ * This program is distributed in the hope that it will be useful,
344+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
345+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
346+ * GNU General Public License for more details.
347+ *
348+ * You should have received a copy of the GNU General Public License
349+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
350+ */
351+
352+
353+#include <unity/shell/launcher/AppDrawerModelInterface.h>
354+
355+#include "launcheritem.h"
356+
357+class AppDrawerModel: public AppDrawerModelInterface
358+{
359+ Q_OBJECT
360+public:
361+ AppDrawerModel(QObject* parent = nullptr);
362+
363+ int rowCount(const QModelIndex &parent) const override;
364+ QVariant data(const QModelIndex &index, int role) const override;
365+
366+private:
367+ QList<LauncherItem*> m_list;
368+};
369
370=== modified file 'plugins/Unity/Launcher/launcheritem.cpp'
371--- plugins/Unity/Launcher/launcheritem.cpp 2016-10-28 11:54:16 +0000
372+++ plugins/Unity/Launcher/launcheritem.cpp 2016-12-12 16:49:02 +0000
373@@ -90,6 +90,19 @@
374 }
375 }
376
377+QStringList LauncherItem::keywords() const
378+{
379+ return m_keywords;
380+}
381+
382+void LauncherItem::setKeywords(const QStringList &keywords)
383+{
384+ if (m_keywords != keywords) {
385+ m_keywords = keywords;
386+ Q_EMIT keywordsChanged(keywords);
387+ }
388+}
389+
390 bool LauncherItem::pinned() const
391 {
392 return m_pinned;
393
394=== modified file 'plugins/Unity/Launcher/launcheritem.h'
395--- plugins/Unity/Launcher/launcheritem.h 2016-05-10 15:51:00 +0000
396+++ plugins/Unity/Launcher/launcheritem.h 2016-12-12 16:49:02 +0000
397@@ -37,6 +37,7 @@
398 QString appId() const override;
399 QString name() const override;
400 QString icon() const override;
401+ QStringList keywords() const override;
402 bool pinned() const override;
403 bool running() const override;
404 bool recent() const override;
405@@ -52,6 +53,7 @@
406 private:
407 void setName(const QString &name);
408 void setIcon(const QString &icon);
409+ void setKeywords(const QStringList &keywords);
410 void setPinned(bool pinned);
411 void setRunning(bool running);
412 void setRecent(bool recent);
413@@ -66,6 +68,7 @@
414 QString m_appId;
415 QString m_name;
416 QString m_icon;
417+ QStringList m_keywords;
418 bool m_pinned;
419 bool m_running;
420 bool m_recent;
421@@ -79,6 +82,7 @@
422 QuickListEntry m_quitAction;
423
424 friend class LauncherModel;
425+ friend class AppDrawerModel;
426 };
427
428 #endif // LAUNCHERITEM_H
429
430=== modified file 'plugins/Unity/Launcher/launchermodel.cpp'
431--- plugins/Unity/Launcher/launchermodel.cpp 2016-09-23 15:10:26 +0000
432+++ plugins/Unity/Launcher/launchermodel.cpp 2016-12-12 16:49:02 +0000
433@@ -19,18 +19,14 @@
434 #include "gsettings.h"
435 #include "dbusinterface.h"
436 #include "asadapter.h"
437+#include "ualwrapper.h"
438
439-#include <ubuntu-app-launch/appid.h>
440-#include <ubuntu-app-launch/application.h>
441-#include <ubuntu-app-launch/registry.h>
442 #include <unity/shell/application/ApplicationInfoInterface.h>
443 #include <unity/shell/application/MirSurfaceListInterface.h>
444
445 #include <QDesktopServices>
446 #include <QDebug>
447
448-namespace ual = ubuntu::app_launch;
449-
450 using namespace unity::shell::application;
451
452 LauncherModel::LauncherModel(QObject *parent):
453@@ -38,8 +34,7 @@
454 m_settings(new GSettings(this)),
455 m_dbusIface(new DBusInterface(this)),
456 m_asAdapter(new ASAdapter()),
457- m_appManager(nullptr),
458- m_ualRegistry(std::make_shared<ual::Registry>())
459+ m_appManager(nullptr)
460 {
461 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
462 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
463@@ -159,7 +154,7 @@
464 index = m_list.count();
465 }
466
467- auto appInfo = getApplicationInfo(appId);
468+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(appId);
469 if (!appInfo.valid) {
470 qWarning() << "Can't pin application, appId not found:" << appId;
471 return;
472@@ -335,32 +330,6 @@
473 return -1;
474 }
475
476-LauncherModel::AppInfo LauncherModel::getApplicationInfo(const QString &appId)
477-{
478- AppInfo info;
479-
480- ual::AppID ualAppId = ual::AppID::find(m_ualRegistry, appId.toStdString());
481- if (ualAppId.empty()) {
482- return info;
483- }
484-
485- std::shared_ptr<ual::Application> ualApp;
486- try
487- {
488- ualApp = ual::Application::create(ualAppId, m_ualRegistry);
489- }
490- catch (std::runtime_error &e)
491- {
492- qWarning() << "Couldn't find application info for" << appId << "-" << e.what();
493- return info;
494- }
495-
496- info.valid = true;
497- info.name = QString::fromStdString(ualApp->info()->name());
498- info.icon = QString::fromStdString(ualApp->info()->iconPath());
499- return info;
500-}
501-
502 void LauncherModel::progressChanged(const QString &appId, int progress)
503 {
504 const int idx = findApplication(appId);
505@@ -408,7 +377,7 @@
506 }
507 } else {
508 // Need to create a new LauncherItem and show the highlight
509- auto appInfo = getApplicationInfo(appId);
510+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(appId);
511 if (countVisible && appInfo.valid) {
512 LauncherItem *item = new LauncherItem(appId,
513 appInfo.name,
514@@ -428,7 +397,7 @@
515 // First walk through all the existing items and see if we need to remove something
516 QList<LauncherItem*> toBeRemoved;
517 Q_FOREACH (LauncherItem* item, m_list) {
518- auto appInfo = getApplicationInfo(item->appId());
519+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(item->appId());
520 if (!appInfo.valid) {
521 // Application no longer available => drop it!
522 toBeRemoved << item;
523@@ -480,7 +449,7 @@
524 if (itemIndex == -1) {
525 // Need to add it. Just add it into the addedIndex to keep same ordering as the list
526 // in the settings.
527- auto appInfo = getApplicationInfo(entry);
528+ UalWrapper::AppInfo appInfo = UalWrapper::getApplicationInfo(entry);
529 if (!appInfo.valid) {
530 continue;
531 }
532
533=== modified file 'plugins/Unity/Launcher/launchermodel.h'
534--- plugins/Unity/Launcher/launchermodel.h 2016-09-23 14:44:26 +0000
535+++ plugins/Unity/Launcher/launchermodel.h 2016-12-12 16:49:02 +0000
536@@ -27,12 +27,6 @@
537 class DBusInterface;
538 class ASAdapter;
539
540-namespace ubuntu {
541- namespace app_launch {
542- class Registry;
543- }
544-}
545-
546 using namespace unity::shell::launcher;
547 using namespace unity::shell::application;
548
549@@ -73,13 +67,6 @@
550
551 void unpin(const QString &appId);
552
553- struct AppInfo {
554- bool valid = false;
555- QString name;
556- QString icon;
557- };
558- AppInfo getApplicationInfo(const QString &appId);
559-
560 private Q_SLOTS:
561 void countChanged(const QString &appId, int count);
562 void countVisibleChanged(const QString &appId, bool count);
563@@ -98,7 +85,6 @@
564 ASAdapter *m_asAdapter;
565
566 ApplicationManagerInterface *m_appManager;
567- std::shared_ptr<ubuntu::app_launch::Registry> m_ualRegistry;
568
569 friend class LauncherModelTest;
570 };
571
572=== modified file 'plugins/Unity/Launcher/plugin.cpp'
573--- plugins/Unity/Launcher/plugin.cpp 2015-09-14 09:11:08 +0000
574+++ plugins/Unity/Launcher/plugin.cpp 2016-12-12 16:49:02 +0000
575@@ -26,7 +26,7 @@
576 // local
577 #include "launchermodel.h"
578 #include "launcheritem.h"
579-
580+#include "appdrawermodel.h"
581
582 using namespace unity::shell::launcher;
583
584@@ -46,4 +46,5 @@
585 qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
586 qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", QStringLiteral("Can't create new Launcher Items in QML. Get them from the LauncherModel."));
587 qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", QStringLiteral("Can't create a QuickListModel in QML. Get them from the LauncherItems."));
588+ qmlRegisterType<AppDrawerModel>(uri, 0, 1, "AppDrawerModel");
589 }
590
591=== added file 'plugins/Unity/Launcher/ualwrapper.cpp'
592--- plugins/Unity/Launcher/ualwrapper.cpp 1970-01-01 00:00:00 +0000
593+++ plugins/Unity/Launcher/ualwrapper.cpp 2016-12-12 16:49:02 +0000
594@@ -0,0 +1,73 @@
595+/*
596+ * Copyright (C) 2016 Canonical, Ltd.
597+ *
598+ * This program is free software; you can redistribute it and/or modify
599+ * it under the terms of the GNU General Public License as published by
600+ * the Free Software Foundation; version 3.
601+ *
602+ * This program is distributed in the hope that it will be useful,
603+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
604+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
605+ * GNU General Public License for more details.
606+ *
607+ * You should have received a copy of the GNU General Public License
608+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
609+ */
610+
611+#include <QDebug>
612+
613+#include "ualwrapper.h"
614+
615+#include <ubuntu-app-launch/registry.h>
616+using namespace ubuntu::app_launch;
617+
618+UalWrapper::UalWrapper(QObject *parent):
619+ QObject(parent)
620+{
621+
622+}
623+
624+QStringList UalWrapper::installedApps()
625+{
626+ QStringList appIds;
627+ try {
628+ for (const std::shared_ptr<Application> &app : Registry::installedApps()) {
629+ if (!app->appId().package.value().empty()) {
630+ appIds << QString::fromStdString(app->appId().package.value() + "_" + app->appId().appname.value());
631+ } else {
632+ appIds << QString::fromStdString(app->appId().appname);
633+ }
634+ }
635+ } catch (const std::runtime_error &e) {
636+ qWarning() << "ubuntu-all-launch threw an exception listing apps:" << e.what();
637+ }
638+
639+ return appIds;
640+}
641+
642+UalWrapper::AppInfo UalWrapper::getApplicationInfo(const QString &appId)
643+{
644+ AppInfo info;
645+
646+ try {
647+ AppID ualAppId = AppID::find(appId.toStdString());
648+ if (ualAppId.empty()) {
649+ qWarning() << "Empty ualAppId result for" << appId;
650+ return info;
651+ }
652+
653+ std::shared_ptr<Application> ualApp;
654+ ualApp = Application::create(ualAppId, Registry::getDefault());
655+
656+ info.name = QString::fromStdString(ualApp->info()->name());
657+ info.icon = QString::fromStdString(ualApp->info()->iconPath());
658+ for (const std::string &keyword : ualApp->info()->keywords().value()) {
659+ info.keywords << QString::fromStdString(keyword);
660+ }
661+ info.valid = true;
662+ } catch (const std::runtime_error &e) {
663+ qWarning() << "ubuntu-app-launch threw an exception getting app info for appId:" << appId << ":" << e.what();
664+ }
665+
666+ return info;
667+}
668
669=== added file 'plugins/Unity/Launcher/ualwrapper.h'
670--- plugins/Unity/Launcher/ualwrapper.h 1970-01-01 00:00:00 +0000
671+++ plugins/Unity/Launcher/ualwrapper.h 2016-12-12 16:49:02 +0000
672@@ -0,0 +1,35 @@
673+/*
674+ * Copyright (C) 2016 Canonical, Ltd.
675+ *
676+ * This program is free software; you can redistribute it and/or modify
677+ * it under the terms of the GNU General Public License as published by
678+ * the Free Software Foundation; version 3.
679+ *
680+ * This program is distributed in the hope that it will be useful,
681+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
682+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
683+ * GNU General Public License for more details.
684+ *
685+ * You should have received a copy of the GNU General Public License
686+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
687+ */
688+
689+#include <QObject>
690+
691+class UalWrapper: public QObject
692+{
693+ Q_OBJECT
694+public:
695+ struct AppInfo {
696+ bool valid = false;
697+ QString name;
698+ QString icon;
699+ QStringList keywords;
700+ };
701+
702+ UalWrapper(QObject* parent = nullptr);
703+
704+ static QStringList installedApps();
705+ static AppInfo getApplicationInfo(const QString &appId);
706+
707+};
708
709=== modified file 'plugins/Utils/CMakeLists.txt'
710--- plugins/Utils/CMakeLists.txt 2016-06-29 18:05:44 +0000
711+++ plugins/Utils/CMakeLists.txt 2016-12-12 16:49:02 +0000
712@@ -15,6 +15,10 @@
713 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
714 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
715 applicationsfiltermodel.cpp
716+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
717+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
718+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
719+ appdrawerproxymodel.cpp
720 constants.cpp
721 WindowInputMonitor.cpp
722 inputwatcher.cpp
723
724=== modified file 'plugins/Utils/ElapsedTimer.h'
725--- plugins/Utils/ElapsedTimer.h 2015-04-22 10:40:54 +0000
726+++ plugins/Utils/ElapsedTimer.h 2016-12-12 16:49:02 +0000
727@@ -41,7 +41,7 @@
728 public:
729 void start() override { m_timer.start(); }
730 qint64 msecsSinceReference() const override { return m_timer.msecsSinceReference(); }
731- qint64 elapsed() const override { return m_timer.elapsed(); }
732+ qint64 elapsed() const override { return m_timer.isValid() ? m_timer.elapsed() : 0; }
733 private:
734 QElapsedTimer m_timer;
735 };
736
737=== modified file 'plugins/Utils/Timer.cpp'
738--- plugins/Utils/Timer.cpp 2015-04-22 10:40:54 +0000
739+++ plugins/Utils/Timer.cpp 2016-12-12 16:49:02 +0000
740@@ -37,13 +37,16 @@
741 void Timer::start()
742 {
743 m_timer.start();
744- AbstractTimer::start();
745 }
746
747 void Timer::stop()
748 {
749 m_timer.stop();
750- AbstractTimer::stop();
751+}
752+
753+bool Timer::isRunning() const
754+{
755+ return m_timer.isActive();
756 }
757
758 bool Timer::isSingleShot() const
759
760=== modified file 'plugins/Utils/Timer.h'
761--- plugins/Utils/Timer.h 2015-04-22 10:40:54 +0000
762+++ plugins/Utils/Timer.h 2016-12-12 16:49:02 +0000
763@@ -30,18 +30,16 @@
764 {
765 Q_OBJECT
766 public:
767- AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
768+ AbstractTimer(QObject *parent) : QObject(parent) {}
769 virtual int interval() const = 0;
770 virtual void setInterval(int msecs) = 0;
771- virtual void start() { m_isRunning = true; }
772- virtual void stop() { m_isRunning = false; }
773- bool isRunning() const { return m_isRunning; }
774+ virtual void start() = 0;
775+ virtual void stop() = 0;
776+ virtual bool isRunning() const = 0;
777 virtual bool isSingleShot() const = 0;
778 virtual void setSingleShot(bool value) = 0;
779 Q_SIGNALS:
780 void timeout();
781-private:
782- bool m_isRunning;
783 };
784
785 /** A QTimer wrapper */
786@@ -55,6 +53,7 @@
787 void setInterval(int msecs) override;
788 void start() override;
789 void stop() override;
790+ bool isRunning() const override;
791 bool isSingleShot() const override;
792 void setSingleShot(bool value) override;
793 private:
794
795=== added file 'plugins/Utils/appdrawerproxymodel.cpp'
796--- plugins/Utils/appdrawerproxymodel.cpp 1970-01-01 00:00:00 +0000
797+++ plugins/Utils/appdrawerproxymodel.cpp 2016-12-12 16:49:02 +0000
798@@ -0,0 +1,189 @@
799+/*
800+ * Copyright (C) 2016 Canonical, Ltd.
801+ *
802+ * This program is free software; you can redistribute it and/or modify
803+ * it under the terms of the GNU General Public License as published by
804+ * the Free Software Foundation; version 3.
805+ *
806+ * This program is distributed in the hope that it will be useful,
807+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
808+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
809+ * GNU General Public License for more details.
810+ *
811+ * You should have received a copy of the GNU General Public License
812+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
813+ */
814+
815+#include "appdrawerproxymodel.h"
816+
817+#include <unity/shell/launcher/LauncherItemInterface.h>
818+
819+#include <QDebug>
820+
821+AppDrawerProxyModel::AppDrawerProxyModel(QObject *parent):
822+ QSortFilterProxyModel(parent)
823+{
824+ setSortRole(AppDrawerModelInterface::RoleName);
825+ setSortLocaleAware(true);
826+ sort(0);
827+
828+ connect(this, &QAbstractListModel::rowsInserted, this, &AppDrawerProxyModel::countChanged);
829+ connect(this, &QAbstractListModel::rowsRemoved, this, &AppDrawerProxyModel::countChanged);
830+ connect(this, &QAbstractListModel::layoutChanged, this, &AppDrawerProxyModel::countChanged);
831+}
832+
833+QAbstractItemModel *AppDrawerProxyModel::source() const
834+{
835+ return m_source;
836+}
837+
838+void AppDrawerProxyModel::setSource(QAbstractItemModel *source)
839+{
840+ if (m_source != source) {
841+ m_source = source;
842+ setSourceModel(m_source);
843+ setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
844+ connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidateFilter);
845+ connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidateFilter);
846+ Q_EMIT sourceChanged();
847+ }
848+}
849+
850+AppDrawerProxyModel::GroupBy AppDrawerProxyModel::group() const
851+{
852+ return m_group;
853+}
854+
855+void AppDrawerProxyModel::setGroup(AppDrawerProxyModel::GroupBy group)
856+{
857+ if (m_group != group) {
858+ m_group = group;
859+ Q_EMIT groupChanged();
860+ invalidateFilter();
861+ }
862+}
863+
864+QString AppDrawerProxyModel::filterLetter() const
865+{
866+ return m_filterLetter;
867+}
868+
869+void AppDrawerProxyModel::setFilterLetter(const QString &filterLetter)
870+{
871+ if (m_filterLetter != filterLetter) {
872+ m_filterLetter = filterLetter;
873+ Q_EMIT filterLetterChanged();
874+ invalidateFilter();
875+ }
876+}
877+
878+QString AppDrawerProxyModel::filterString() const
879+{
880+ return m_filterString;
881+}
882+
883+void AppDrawerProxyModel::setFilterString(const QString &filterString)
884+{
885+ if (m_filterString != filterString) {
886+ m_filterString = filterString;
887+ Q_EMIT filterStringChanged();
888+ invalidateFilter();
889+ }
890+}
891+
892+AppDrawerProxyModel::SortBy AppDrawerProxyModel::sortBy() const
893+{
894+ return m_sortBy;
895+}
896+
897+void AppDrawerProxyModel::setSortBy(AppDrawerProxyModel::SortBy sortBy)
898+{
899+ if (m_sortBy != sortBy) {
900+ m_sortBy = sortBy;
901+ Q_EMIT sortByChanged();
902+ setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
903+ sort(0);
904+ }
905+}
906+
907+int AppDrawerProxyModel::count() const
908+{
909+ return rowCount();
910+}
911+
912+QVariant AppDrawerProxyModel::data(const QModelIndex &index, int role) const
913+{
914+ QModelIndex idx = mapToSource(index);
915+ if (role == Qt::UserRole) {
916+ QString name = m_source->data(idx, AppDrawerModelInterface::RoleName).toString();
917+ return name.length() > 0 ? QString(name.at(0)).toUpper() : QChar();
918+ }
919+ return m_source->data(idx, role);
920+}
921+
922+QHash<int, QByteArray> AppDrawerProxyModel::roleNames() const
923+{
924+ if (m_source) {
925+ QHash<int, QByteArray> roles = m_source->roleNames();
926+ roles.insert(Qt::UserRole, "letter");
927+ return roles;
928+ }
929+ return QHash<int, QByteArray>();
930+}
931+
932+bool AppDrawerProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
933+{
934+ Q_UNUSED(source_parent)
935+
936+ if (m_group == GroupByAToZ && source_row > 0) {
937+ QString currentName = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString();
938+ QChar currentLetter = currentName.length() > 0 ? currentName.at(0) : QChar();
939+ QString previousName = m_source->data(m_source->index(source_row - 1,0 ), AppDrawerModelInterface::RoleName).toString();
940+ QChar previousLetter = previousName.length() > 0 ? previousName.at(0) : QChar();
941+ if (currentLetter.toLower() == previousLetter.toLower()) {
942+ return false;
943+ }
944+ } else if(m_group == GroupByAll && source_row > 0) {
945+ return false;
946+ }
947+ if (!m_filterLetter.isEmpty()) {
948+ QString currentName = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString();
949+ QString currentLetter = currentName.length() > 0 ? QString(currentName.at(0)) : QString();
950+ if (currentLetter.toLower() != m_filterLetter.toLower()) {
951+ return false;
952+ }
953+ }
954+ if (!m_filterString.isEmpty()) {
955+ QStringList allWords = m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleKeywords).toStringList();
956+ allWords.prepend(m_source->data(m_source->index(source_row, 0), AppDrawerModelInterface::RoleName).toString());
957+ bool found = false;
958+ Q_FOREACH (const QString currentWord, allWords) {
959+ if (currentWord.toLower().startsWith(m_filterString.toLower())) {
960+ found = true;
961+ break;
962+ }
963+ }
964+ if (!found) {
965+ return false;
966+ }
967+ }
968+ return true;
969+}
970+
971+QString AppDrawerProxyModel::appId(int index) const
972+{
973+ if (index >= 0 && index < rowCount()) {
974+ QModelIndex sourceIndex = mapToSource(this->index(index, 0));
975+
976+ AppDrawerModelInterface* adm = dynamic_cast<AppDrawerModelInterface*>(m_source);
977+ if (adm) {
978+ return adm->data(sourceIndex, AppDrawerModelInterface::RoleAppId).toString();
979+ }
980+
981+ AppDrawerProxyModel* adpm = qobject_cast<AppDrawerProxyModel*>(m_source);
982+ if (adpm) {
983+ return adpm->appId(sourceIndex.row());
984+ }
985+ }
986+ return nullptr;
987+}
988
989=== added file 'plugins/Utils/appdrawerproxymodel.h'
990--- plugins/Utils/appdrawerproxymodel.h 1970-01-01 00:00:00 +0000
991+++ plugins/Utils/appdrawerproxymodel.h 2016-12-12 16:49:02 +0000
992@@ -0,0 +1,87 @@
993+/*
994+ * Copyright (C) 2016 Canonical, Ltd.
995+ *
996+ * This program is free software; you can redistribute it and/or modify
997+ * it under the terms of the GNU General Public License as published by
998+ * the Free Software Foundation; version 3.
999+ *
1000+ * This program is distributed in the hope that it will be useful,
1001+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1002+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1003+ * GNU General Public License for more details.
1004+ *
1005+ * You should have received a copy of the GNU General Public License
1006+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1007+ */
1008+
1009+#include <QSortFilterProxyModel>
1010+
1011+#include <unity/shell/launcher/AppDrawerModelInterface.h>
1012+
1013+using namespace unity::shell::launcher;
1014+
1015+class AppDrawerProxyModel: public QSortFilterProxyModel
1016+{
1017+ Q_OBJECT
1018+ Q_ENUMS(GroupBy)
1019+ Q_ENUMS(SortBy)
1020+ Q_PROPERTY(QAbstractItemModel* source READ source WRITE setSource NOTIFY sourceChanged)
1021+ Q_PROPERTY(GroupBy group READ group WRITE setGroup NOTIFY groupChanged)
1022+ Q_PROPERTY(QString filterLetter READ filterLetter WRITE setFilterLetter NOTIFY filterLetterChanged)
1023+ Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)
1024+ Q_PROPERTY(SortBy sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged)
1025+ Q_PROPERTY(int count READ count NOTIFY countChanged)
1026+
1027+public:
1028+ enum GroupBy {
1029+ GroupByNone,
1030+ GroupByAll,
1031+ GroupByAToZ
1032+ };
1033+ enum SortBy {
1034+ SortByAToZ,
1035+ SortByUsage
1036+ };
1037+
1038+ AppDrawerProxyModel(QObject* parent = nullptr);
1039+
1040+ QAbstractItemModel* source() const;
1041+ void setSource(QAbstractItemModel* source);
1042+
1043+ GroupBy group() const;
1044+ void setGroup(GroupBy group);
1045+
1046+ QString filterLetter() const;
1047+ void setFilterLetter(const QString &filterLetter);
1048+
1049+ QString filterString() const;
1050+ void setFilterString(const QString &filterString);
1051+
1052+ SortBy sortBy() const;
1053+ void setSortBy(SortBy sortBy);
1054+
1055+ int count() const;
1056+
1057+ QVariant data(const QModelIndex &index, int role) const override;
1058+ QHash<int, QByteArray> roleNames() const override;
1059+
1060+ Q_INVOKABLE QString appId(int index) const;
1061+
1062+protected:
1063+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
1064+
1065+Q_SIGNALS:
1066+ void sourceChanged();
1067+ void groupChanged();
1068+ void filterLetterChanged();
1069+ void filterStringChanged();
1070+ void sortByChanged();
1071+ void countChanged();
1072+
1073+private:
1074+ QAbstractItemModel* m_source = nullptr;
1075+ GroupBy m_group = GroupByNone;
1076+ QString m_filterLetter;
1077+ QString m_filterString;
1078+ SortBy m_sortBy = SortByAToZ;
1079+};
1080
1081=== modified file 'plugins/Utils/globalfunctions.cpp'
1082--- plugins/Utils/globalfunctions.cpp 2016-06-02 09:32:33 +0000
1083+++ plugins/Utils/globalfunctions.cpp 2016-12-12 16:49:02 +0000
1084@@ -51,3 +51,8 @@
1085 }
1086 return nullptr;
1087 }
1088+
1089+bool GlobalFunctions::itemUnderMouse(QQuickItem* item)
1090+{
1091+ return item && item->isUnderMouse();
1092+}
1093
1094=== modified file 'plugins/Utils/globalfunctions.h'
1095--- plugins/Utils/globalfunctions.h 2016-03-11 20:18:12 +0000
1096+++ plugins/Utils/globalfunctions.h 2016-12-12 16:49:02 +0000
1097@@ -37,6 +37,8 @@
1098 int x,
1099 int y,
1100 QJSValue matcher);
1101+
1102+ static Q_INVOKABLE bool itemUnderMouse(QQuickItem* item);
1103 };
1104
1105 #endif // GLOBALFUNCTIONS_H
1106
1107=== modified file 'plugins/Utils/plugin.cpp'
1108--- plugins/Utils/plugin.cpp 2016-06-28 20:38:00 +0000
1109+++ plugins/Utils/plugin.cpp 2016-12-12 16:49:02 +0000
1110@@ -39,6 +39,7 @@
1111 #include "deviceconfigparser.h"
1112 #include "globalfunctions.h"
1113 #include "URLDispatcher.h"
1114+#include "appdrawerproxymodel.h"
1115
1116 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
1117 {
1118@@ -82,4 +83,5 @@
1119 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
1120 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
1121 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
1122+ qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
1123 }
1124
1125=== modified file 'plugins/Utils/windowstatestorage.cpp'
1126--- plugins/Utils/windowstatestorage.cpp 2016-08-23 13:53:07 +0000
1127+++ plugins/Utils/windowstatestorage.cpp 2016-12-12 16:49:02 +0000
1128@@ -189,3 +189,26 @@
1129 }
1130 return query;
1131 }
1132+
1133+Mir::State WindowStateStorage::toMirState(WindowState state) const
1134+{
1135+ // assumes a single state (not an OR of several)
1136+ switch (state) {
1137+ case WindowStateMaximized: return Mir::MaximizedState;
1138+ case WindowStateMinimized: return Mir::MinimizedState;
1139+ case WindowStateFullscreen: return Mir::FullscreenState;
1140+ case WindowStateMaximizedLeft: return Mir::MaximizedLeftState;
1141+ case WindowStateMaximizedRight: return Mir::MaximizedRightState;
1142+ case WindowStateMaximizedHorizontally: return Mir::HorizMaximizedState;
1143+ case WindowStateMaximizedVertically: return Mir::VertMaximizedState;
1144+ case WindowStateMaximizedTopLeft: return Mir::MaximizedTopLeftState;
1145+ case WindowStateMaximizedTopRight: return Mir::MaximizedTopRightState;
1146+ case WindowStateMaximizedBottomLeft: return Mir::MaximizedBottomLeftState;
1147+ case WindowStateMaximizedBottomRight: return Mir::MaximizedBottomRightState;
1148+
1149+ case WindowStateNormal:
1150+ case WindowStateRestored:
1151+ default:
1152+ return Mir::RestoredState;
1153+ }
1154+}
1155
1156=== modified file 'plugins/Utils/windowstatestorage.h'
1157--- plugins/Utils/windowstatestorage.h 2016-08-23 13:53:07 +0000
1158+++ plugins/Utils/windowstatestorage.h 2016-12-12 16:49:02 +0000
1159@@ -19,6 +19,9 @@
1160 #include <QMutex>
1161 #include <QFuture>
1162
1163+// unity-api
1164+#include <unity/shell/application/Mir.h>
1165+
1166 class WindowStateStorage: public QObject
1167 {
1168 Q_OBJECT
1169@@ -56,6 +59,8 @@
1170 Q_INVOKABLE void saveStage(const QString &appId, int stage);
1171 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
1172
1173+ Q_INVOKABLE Mir::State toMirState(WindowState state) const;
1174+
1175 private:
1176 void initdb();
1177
1178
1179=== modified file 'plugins/WindowManager/CMakeLists.txt'
1180--- plugins/WindowManager/CMakeLists.txt 2016-06-20 09:43:38 +0000
1181+++ plugins/WindowManager/CMakeLists.txt 2016-12-12 16:49:02 +0000
1182@@ -1,10 +1,12 @@
1183 set(WINDOWMANAGER_SRC
1184- TopLevelSurfaceList.cpp
1185+ TopLevelWindowModel.cpp
1186+ Window.cpp
1187 WindowManagerPlugin.cpp
1188 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
1189 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
1190 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
1191 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceListInterface.h
1192+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/SurfaceManagerInterface.h
1193 )
1194
1195 add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
1196
1197=== removed file 'plugins/WindowManager/TopLevelSurfaceList.cpp'
1198--- plugins/WindowManager/TopLevelSurfaceList.cpp 2016-09-14 15:02:01 +0000
1199+++ plugins/WindowManager/TopLevelSurfaceList.cpp 1970-01-01 00:00:00 +0000
1200@@ -1,481 +0,0 @@
1201-/*
1202- * Copyright (C) 2016 Canonical, Ltd.
1203- *
1204- * This program is free software; you can redistribute it and/or modify
1205- * it under the terms of the GNU General Public License as published by
1206- * the Free Software Foundation; version 3.
1207- *
1208- * This program is distributed in the hope that it will be useful,
1209- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1210- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1211- * GNU General Public License for more details.
1212- *
1213- * You should have received a copy of the GNU General Public License
1214- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1215- */
1216-
1217-#include "TopLevelSurfaceList.h"
1218-
1219-// unity-api
1220-#include <unity/shell/application/ApplicationInfoInterface.h>
1221-#include <unity/shell/application/MirSurfaceInterface.h>
1222-#include <unity/shell/application/MirSurfaceListInterface.h>
1223-
1224-#include <QMetaObject>
1225-
1226-Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
1227-
1228-#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
1229-
1230-using namespace unity::shell::application;
1231-
1232-TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
1233- QAbstractListModel(parent)
1234-{
1235- DEBUG_MSG << "()";
1236-}
1237-
1238-TopLevelSurfaceList::~TopLevelSurfaceList()
1239-{
1240- DEBUG_MSG << "()";
1241-}
1242-
1243-int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
1244-{
1245- return !parent.isValid() ? m_surfaceList.size() : 0;
1246-}
1247-
1248-QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
1249-{
1250- if (index.row() < 0 || index.row() >= m_surfaceList.size())
1251- return QVariant();
1252-
1253- if (role == SurfaceRole) {
1254- MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
1255- return QVariant::fromValue(surface);
1256- } else if (role == ApplicationRole) {
1257- return QVariant::fromValue(m_surfaceList.at(index.row()).application);
1258- } else if (role == IdRole) {
1259- return QVariant::fromValue(m_surfaceList.at(index.row()).id);
1260- } else {
1261- return QVariant();
1262- }
1263-}
1264-
1265-void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
1266-{
1267- if (!surface)
1268- return;
1269-
1270- DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
1271-
1272- int i = indexOf(surface);
1273- if (i != -1) {
1274- raiseId(m_surfaceList.at(i).id);
1275- }
1276-}
1277-
1278-void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
1279-{
1280- DEBUG_MSG << "(" << application->appId() << ")";
1281-
1282- appendSurfaceHelper(nullptr, application);
1283-}
1284-
1285-void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
1286-{
1287- Q_ASSERT(surface != nullptr);
1288-
1289- bool filledPlaceholder = false;
1290- for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
1291- ModelEntry &entry = m_surfaceList[i];
1292- if (entry.application == application && entry.surface == nullptr) {
1293- entry.surface = surface;
1294- connectSurface(surface);
1295- DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
1296- << ", filling out placeholder. after: " << toString();
1297- Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
1298- filledPlaceholder = true;
1299- }
1300- }
1301-
1302- if (!filledPlaceholder) {
1303- DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
1304- appendSurfaceHelper(surface, application);
1305- }
1306-}
1307-
1308-void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
1309-{
1310- if (m_modelState == IdleState) {
1311- m_modelState = InsertingState;
1312- beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
1313- } else {
1314- Q_ASSERT(m_modelState == ResettingState);
1315- // No point in signaling anything if we're resetting the whole model
1316- }
1317-
1318- int id = generateId();
1319- m_surfaceList.append(ModelEntry(surface, application, id));
1320- if (surface) {
1321- connectSurface(surface);
1322- }
1323-
1324- if (m_modelState == InsertingState) {
1325- endInsertRows();
1326- Q_EMIT countChanged();
1327- Q_EMIT listChanged();
1328- m_modelState = IdleState;
1329- }
1330-
1331- DEBUG_MSG << " after " << toString();
1332-}
1333-
1334-void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
1335-{
1336- connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
1337- if (!live) {
1338- onSurfaceDied(surface);
1339- }
1340- });
1341- connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
1342-}
1343-
1344-void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
1345-{
1346- int i = indexOf(surface);
1347- if (i == -1) {
1348- return;
1349- }
1350-
1351- auto application = m_surfaceList[i].application;
1352-
1353- // can't be starting if it already has a surface
1354- Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
1355-
1356- if (application->state() == ApplicationInfoInterface::Running) {
1357- m_surfaceList[i].removeOnceSurfaceDestroyed = true;
1358- } else {
1359- // assume it got killed by the out-of-memory daemon.
1360- //
1361- // So leave entry in the model and only remove its surface, so shell can display a screenshot
1362- // in its place.
1363- m_surfaceList[i].removeOnceSurfaceDestroyed = false;
1364- }
1365-}
1366-
1367-void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
1368-{
1369- int i = indexOf(surface);
1370- if (i == -1) {
1371- return;
1372- }
1373-
1374- if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
1375- removeAt(i);
1376- } else {
1377- m_surfaceList[i].surface = nullptr;
1378- Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
1379- DEBUG_MSG << " Removed surface from entry. After: " << toString();
1380- }
1381-}
1382-
1383-void TopLevelSurfaceList::removeAt(int index)
1384-{
1385- if (m_modelState == IdleState) {
1386- beginRemoveRows(QModelIndex(), index, index);
1387- m_modelState = RemovingState;
1388- } else {
1389- Q_ASSERT(m_modelState == ResettingState);
1390- // No point in signaling anything if we're resetting the whole model
1391- }
1392-
1393- m_surfaceList.removeAt(index);
1394-
1395- if (m_modelState == RemovingState) {
1396- endRemoveRows();
1397- Q_EMIT countChanged();
1398- Q_EMIT listChanged();
1399- m_modelState = IdleState;
1400- }
1401-
1402- DEBUG_MSG << " after " << toString();
1403-}
1404-
1405-int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
1406-{
1407- for (int i = 0; i < m_surfaceList.count(); ++i) {
1408- if (m_surfaceList.at(i).surface == surface) {
1409- return i;
1410- }
1411- }
1412- return -1;
1413-}
1414-
1415-void TopLevelSurfaceList::move(int from, int to)
1416-{
1417- if (from == to) return;
1418- DEBUG_MSG << " from=" << from << " to=" << to;
1419-
1420- if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
1421- QModelIndex parent;
1422- /* When moving an item down, the destination index needs to be incremented
1423- by one, as explained in the documentation:
1424- http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
1425-
1426- Q_ASSERT(m_modelState == IdleState);
1427- m_modelState = MovingState;
1428-
1429- beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
1430- m_surfaceList.move(from, to);
1431- endMoveRows();
1432- Q_EMIT listChanged();
1433-
1434- m_modelState = IdleState;
1435-
1436- DEBUG_MSG << " after " << toString();
1437- }
1438-}
1439-
1440-MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
1441-{
1442- if (index >=0 && index < m_surfaceList.count()) {
1443- return m_surfaceList[index].surface;
1444- } else {
1445- return nullptr;
1446- }
1447-}
1448-
1449-ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
1450-{
1451- if (index >=0 && index < m_surfaceList.count()) {
1452- return m_surfaceList[index].application;
1453- } else {
1454- return nullptr;
1455- }
1456-}
1457-
1458-int TopLevelSurfaceList::idAt(int index) const
1459-{
1460- if (index >=0 && index < m_surfaceList.count()) {
1461- return m_surfaceList[index].id;
1462- } else {
1463- return 0;
1464- }
1465-}
1466-
1467-int TopLevelSurfaceList::indexForId(int id) const
1468-{
1469- for (int i = 0; i < m_surfaceList.count(); ++i) {
1470- if (m_surfaceList[i].id == id) {
1471- return i;
1472- }
1473- }
1474- return -1;
1475-}
1476-
1477-void TopLevelSurfaceList::doRaiseId(int id)
1478-{
1479- int fromIndex = indexForId(id);
1480- if (fromIndex != -1) {
1481- move(fromIndex, 0 /* toIndex */);
1482- }
1483-}
1484-
1485-void TopLevelSurfaceList::raiseId(int id)
1486-{
1487- if (m_modelState == IdleState) {
1488- DEBUG_MSG << "(id=" << id << ") - do it now.";
1489- doRaiseId(id);
1490- } else {
1491- DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
1492- // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
1493- // if we perform yet another model change straight away.
1494- //
1495- // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
1496- // the index is definitely within bounds.
1497- QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
1498- }
1499-}
1500-
1501-int TopLevelSurfaceList::generateId()
1502-{
1503- int id = m_nextId;
1504- m_nextId = nextFreeId(m_nextId + 1);
1505- Q_EMIT nextIdChanged();
1506- return id;
1507-}
1508-
1509-int TopLevelSurfaceList::nextFreeId(int candidateId)
1510-{
1511- if (candidateId > m_maxId) {
1512- return nextFreeId(1);
1513- } else {
1514- if (indexForId(candidateId) == -1) {
1515- // it's indeed free
1516- return candidateId;
1517- } else {
1518- return nextFreeId(candidateId + 1);
1519- }
1520- }
1521-}
1522-
1523-QString TopLevelSurfaceList::toString()
1524-{
1525- QString str;
1526- for (int i = 0; i < m_surfaceList.count(); ++i) {
1527- auto item = m_surfaceList.at(i);
1528-
1529- QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
1530- .arg(i)
1531- .arg(item.application->appId())
1532- .arg((qintptr)item.surface, 0, 16)
1533- .arg(item.id);
1534-
1535- if (i > 0) {
1536- str.append(",");
1537- }
1538- str.append(itemStr);
1539- }
1540- return str;
1541-}
1542-
1543-void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
1544-{
1545- DEBUG_MSG << "(" << application->appId() << ")";
1546- Q_ASSERT(!m_applications.contains(application));
1547- m_applications.append(application);
1548-
1549- MirSurfaceListInterface *surfaceList = application->surfaceList();
1550-
1551- if (application->state() != ApplicationInfoInterface::Stopped) {
1552- if (surfaceList->count() == 0) {
1553- appendPlaceholder(application);
1554- } else {
1555- for (int i = 0; i < surfaceList->count(); ++i) {
1556- appendSurface(surfaceList->get(i), application);
1557- }
1558- }
1559- }
1560-
1561- connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
1562- [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
1563- {
1564- for (int i = last; i >= first; --i) {
1565- this->appendSurface(surfaceList->get(i), application);
1566- }
1567- });
1568-}
1569-
1570-void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
1571-{
1572- DEBUG_MSG << "(" << application->appId() << ")";
1573- Q_ASSERT(m_applications.contains(application));
1574-
1575- MirSurfaceListInterface *surfaceList = application->surfaceList();
1576-
1577- disconnect(surfaceList, 0, this, 0);
1578-
1579- Q_ASSERT(m_modelState == IdleState);
1580- m_modelState = RemovingState;
1581-
1582- int i = 0;
1583- while (i < m_surfaceList.count()) {
1584- if (m_surfaceList.at(i).application == application) {
1585- beginRemoveRows(QModelIndex(), i, i);
1586- m_surfaceList.removeAt(i);
1587- endRemoveRows();
1588- Q_EMIT countChanged();
1589- Q_EMIT listChanged();
1590- } else {
1591- ++i;
1592- }
1593- }
1594-
1595- m_modelState = IdleState;
1596-
1597- DEBUG_MSG << " after " << toString();
1598-
1599- m_applications.removeAll(application);
1600-}
1601-
1602-QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
1603-{
1604- return m_applicationsModel;
1605-}
1606-
1607-void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
1608-{
1609- if (m_applicationsModel == value) {
1610- return;
1611- }
1612-
1613- DEBUG_MSG << "(" << value << ")";
1614-
1615- Q_ASSERT(m_modelState == IdleState);
1616- m_modelState = ResettingState;
1617-
1618- beginResetModel();
1619-
1620- if (m_applicationsModel) {
1621- m_surfaceList.clear();
1622- m_applications.clear();
1623- disconnect(m_applicationsModel, 0, this, 0);
1624- }
1625-
1626- m_applicationsModel = value;
1627-
1628- if (m_applicationsModel) {
1629- findApplicationRole();
1630-
1631- connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
1632- this, [this](const QModelIndex &/*parent*/, int first, int last) {
1633- for (int i = first; i <= last; ++i) {
1634- auto application = getApplicationFromModelAt(i);
1635- addApplication(application);
1636- }
1637- });
1638-
1639- connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
1640- this, [this](const QModelIndex &/*parent*/, int first, int last) {
1641- for (int i = first; i <= last; ++i) {
1642- auto application = getApplicationFromModelAt(i);
1643- removeApplication(application);
1644- }
1645- });
1646-
1647- for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
1648- auto application = getApplicationFromModelAt(i);
1649- addApplication(application);
1650- }
1651- }
1652-
1653- endResetModel();
1654- m_modelState = IdleState;
1655-}
1656-
1657-ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
1658-{
1659- QModelIndex modelIndex = m_applicationsModel->index(index);
1660-
1661- QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
1662-
1663- // variant.value<ApplicationInfoInterface*>() returns null for some reason.
1664- return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
1665-}
1666-
1667-void TopLevelSurfaceList::findApplicationRole()
1668-{
1669- QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
1670-
1671- m_applicationRole = -1;
1672- for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
1673- if (i.value() == "application") {
1674- m_applicationRole = i.key();
1675- }
1676- }
1677-
1678- if (m_applicationRole == -1) {
1679- qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
1680- }
1681-}
1682
1683=== removed file 'plugins/WindowManager/TopLevelSurfaceList.h'
1684--- plugins/WindowManager/TopLevelSurfaceList.h 2016-04-04 13:39:44 +0000
1685+++ plugins/WindowManager/TopLevelSurfaceList.h 1970-01-01 00:00:00 +0000
1686@@ -1,223 +0,0 @@
1687-/*
1688- * Copyright (C) 2016 Canonical, Ltd.
1689- *
1690- * This program is free software; you can redistribute it and/or modify
1691- * it under the terms of the GNU General Public License as published by
1692- * the Free Software Foundation; version 3.
1693- *
1694- * This program is distributed in the hope that it will be useful,
1695- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1696- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1697- * GNU General Public License for more details.
1698- *
1699- * You should have received a copy of the GNU General Public License
1700- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1701- */
1702-
1703-#ifndef TOPLEVELSURFACELIST_H
1704-#define TOPLEVELSURFACELIST_H
1705-
1706-#include <QAbstractListModel>
1707-#include <QList>
1708-#include <QLoggingCategory>
1709-
1710-Q_DECLARE_LOGGING_CATEGORY(UNITY_TOPSURFACELIST)
1711-
1712-namespace unity {
1713- namespace shell {
1714- namespace application {
1715- class ApplicationInfoInterface;
1716- class MirSurfaceInterface;
1717- }
1718- }
1719-}
1720-
1721-/**
1722- * @brief A model of top-level surfaces
1723- *
1724- * It's an abstraction of top-level application windows.
1725- *
1726- * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
1727- * still starting up. A shell should then display a splash screen or saved screenshot of the application
1728- * until its surface comes up.
1729- *
1730- * As applications can have multiple surfaces and you can also have entries without surfaces at all,
1731- * the only way to unambiguously refer to an entry in this model is through its id.
1732- */
1733-class TopLevelSurfaceList : public QAbstractListModel
1734-{
1735-
1736- Q_OBJECT
1737-
1738- /**
1739- * @brief A list model of applications.
1740- *
1741- * It's expected to have a role called "application" which returns a ApplicationInfoInterface
1742- */
1743- Q_PROPERTY(QAbstractListModel* applicationsModel READ applicationsModel
1744- WRITE setApplicationsModel
1745- NOTIFY applicationsModelChanged)
1746-
1747- /**
1748- * @brief Number of top-level surfaces in this model
1749- *
1750- * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
1751- */
1752- Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
1753-
1754- /**
1755- The id to be used on the next entry created
1756- Useful for tests
1757- */
1758- Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
1759-public:
1760-
1761- /**
1762- * @brief The Roles supported by the model
1763- *
1764- * SurfaceRole - A MirSurfaceInterface. It will be null if the application is still starting up
1765- * ApplicationRole - An ApplicationInfoInterface
1766- * IdRole - A unique identifier for this entry. Useful to unambiguosly track elements as they move around in the list
1767- */
1768- enum Roles {
1769- SurfaceRole = Qt::UserRole,
1770- ApplicationRole = Qt::UserRole + 1,
1771- IdRole = Qt::UserRole + 2,
1772- };
1773-
1774- explicit TopLevelSurfaceList(QObject *parent = nullptr);
1775- virtual ~TopLevelSurfaceList();
1776-
1777- // QAbstractItemModel methods
1778- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
1779- QVariant data(const QModelIndex& index, int role) const override;
1780- QHash<int, QByteArray> roleNames() const override {
1781- QHash<int, QByteArray> roleNames { {SurfaceRole, "surface"},
1782- {ApplicationRole, "application"},
1783- {IdRole, "id"} };
1784- return roleNames;
1785- }
1786-
1787- int nextId() const { return m_nextId; }
1788-
1789- QAbstractListModel *applicationsModel() const;
1790- void setApplicationsModel(QAbstractListModel*);
1791-
1792-public Q_SLOTS:
1793- /**
1794- * @brief Returns the surface at the given index
1795- *
1796- * It will be a nullptr if the application is still starting up and thus hasn't yet created
1797- * and drawn into a surface.
1798- */
1799- unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
1800-
1801- /**
1802- * @brief Returns the application at the given index
1803- */
1804- unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
1805-
1806- /**
1807- * @brief Returns the unique id of the element at the given index
1808- */
1809- int idAt(int index) const;
1810-
1811- /**
1812- * @brief Returns the index where the row with the given id is located
1813- *
1814- * Returns -1 if there's no row with the given id.
1815- */
1816- int indexForId(int id) const;
1817-
1818- /**
1819- * @brief Raises the row with the given id to index 0
1820- */
1821- void raiseId(int id);
1822-
1823- void doRaiseId(int id);
1824-
1825-Q_SIGNALS:
1826- void countChanged();
1827-
1828- /**
1829- * @brief Emitted when the list changes
1830- *
1831- * Emitted when model gains an element, loses an element or when elements exchange positions.
1832- */
1833- void listChanged();
1834-
1835- void nextIdChanged();
1836-
1837- void applicationsModelChanged();
1838-
1839-private:
1840- void addApplication(unity::shell::application::ApplicationInfoInterface *application);
1841- void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
1842-
1843- int indexOf(unity::shell::application::MirSurfaceInterface *surface);
1844- void raise(unity::shell::application::MirSurfaceInterface *surface);
1845- void move(int from, int to);
1846- void appendSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
1847- unity::shell::application::ApplicationInfoInterface *application);
1848- void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
1849- int generateId();
1850- int nextFreeId(int candidateId);
1851- QString toString();
1852- void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
1853- void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
1854- void removeAt(int index);
1855- void findApplicationRole();
1856-
1857- unity::shell::application::ApplicationInfoInterface *getApplicationFromModelAt(int index);
1858-
1859- /*
1860- Placeholder for a future surface from a starting or running application.
1861- Enables shell to give immediate feedback to the user by showing, eg,
1862- a splash screen.
1863-
1864- It's a model row containing a null surface and the given application.
1865- */
1866- void appendPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
1867-
1868- /*
1869- Adds a model row with the given surface and application
1870-
1871- Alternatively, if a placeholder exists for the given application it's
1872- filled with the given surface instead.
1873- */
1874- void appendSurface(unity::shell::application::MirSurfaceInterface *surface,
1875- unity::shell::application::ApplicationInfoInterface *application);
1876-
1877- struct ModelEntry {
1878- ModelEntry(unity::shell::application::MirSurfaceInterface *surface, unity::shell::application::ApplicationInfoInterface *application, int id)
1879- : surface(surface), application(application), id(id) {}
1880- unity::shell::application::MirSurfaceInterface *surface;
1881- unity::shell::application::ApplicationInfoInterface *application;
1882- int id;
1883- bool removeOnceSurfaceDestroyed{false};
1884- };
1885-
1886- QList<ModelEntry> m_surfaceList;
1887- int m_nextId{1};
1888- static const int m_maxId{1000000};
1889-
1890- // applications that are being monitored
1891- QList<unity::shell::application::ApplicationInfoInterface *> m_applications;
1892-
1893- QAbstractListModel* m_applicationsModel{nullptr};
1894- int m_applicationRole{-1};
1895-
1896- enum ModelState {
1897- IdleState,
1898- InsertingState,
1899- RemovingState,
1900- MovingState,
1901- ResettingState
1902- };
1903- ModelState m_modelState{IdleState};
1904-};
1905-
1906-Q_DECLARE_METATYPE(TopLevelSurfaceList*)
1907-Q_DECLARE_METATYPE(QAbstractListModel*)
1908-
1909-#endif // TOPLEVELSURFACELIST_H
1910
1911=== added file 'plugins/WindowManager/TopLevelWindowModel.cpp'
1912--- plugins/WindowManager/TopLevelWindowModel.cpp 1970-01-01 00:00:00 +0000
1913+++ plugins/WindowManager/TopLevelWindowModel.cpp 2016-12-12 16:49:02 +0000
1914@@ -0,0 +1,668 @@
1915+/*
1916+ * Copyright (C) 2016 Canonical, Ltd.
1917+ *
1918+ * This program is free software: you can redistribute it and/or modify it under
1919+ * the terms of the GNU Lesser General Public License version 3, as published by
1920+ * the Free Software Foundation.
1921+ *
1922+ * This program is distributed in the hope that it will be useful, but WITHOUT
1923+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1924+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1925+ * Lesser General Public License for more details.
1926+ *
1927+ * You should have received a copy of the GNU Lesser General Public License
1928+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1929+ */
1930+
1931+#include "TopLevelWindowModel.h"
1932+
1933+// unity-api
1934+#include <unity/shell/application/ApplicationInfoInterface.h>
1935+#include <unity/shell/application/ApplicationManagerInterface.h>
1936+#include <unity/shell/application/MirSurfaceInterface.h>
1937+#include <unity/shell/application/MirSurfaceListInterface.h>
1938+#include <unity/shell/application/SurfaceManagerInterface.h>
1939+
1940+// Qt
1941+#include <QGuiApplication>
1942+#include <QDebug>
1943+
1944+// local
1945+#include "Window.h"
1946+
1947+Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
1948+
1949+#define DEBUG_MSG qCDebug(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
1950+#define INFO_MSG qCInfo(TOPLEVELWINDOWMODEL).nospace().noquote() << __func__
1951+
1952+namespace unityapi = unity::shell::application;
1953+
1954+TopLevelWindowModel::TopLevelWindowModel()
1955+{
1956+}
1957+
1958+void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
1959+{
1960+ if (m_applicationManager == value) {
1961+ return;
1962+ }
1963+
1964+ DEBUG_MSG << "(" << value << ")";
1965+
1966+ Q_ASSERT(m_modelState == IdleState);
1967+ m_modelState = ResettingState;
1968+
1969+ beginResetModel();
1970+
1971+ if (m_applicationManager) {
1972+ m_windowModel.clear();
1973+ disconnect(m_applicationManager, 0, this, 0);
1974+ }
1975+
1976+ m_applicationManager = value;
1977+
1978+ if (m_applicationManager) {
1979+ connect(m_applicationManager, &QAbstractItemModel::rowsInserted,
1980+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
1981+ for (int i = first; i <= last; ++i) {
1982+ auto application = m_applicationManager->get(i);
1983+ addApplication(application);
1984+ }
1985+ });
1986+
1987+ connect(m_applicationManager, &QAbstractItemModel::rowsAboutToBeRemoved,
1988+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
1989+ for (int i = first; i <= last; ++i) {
1990+ auto application = m_applicationManager->get(i);
1991+ removeApplication(application);
1992+ }
1993+ });
1994+
1995+ for (int i = 0; i < m_applicationManager->rowCount(); ++i) {
1996+ auto application = m_applicationManager->get(i);
1997+ addApplication(application);
1998+ }
1999+ }
2000+
2001+ endResetModel();
2002+ m_modelState = IdleState;
2003+}
2004+
2005+void TopLevelWindowModel::setSurfaceManager(unityapi::SurfaceManagerInterface *surfaceManager)
2006+{
2007+ if (surfaceManager == m_surfaceManager) {
2008+ return;
2009+ }
2010+
2011+ DEBUG_MSG << "(" << surfaceManager << ")";
2012+
2013+ if (m_surfaceManager) {
2014+ disconnect(m_surfaceManager, 0, this, 0);
2015+ }
2016+
2017+ m_surfaceManager = surfaceManager;
2018+
2019+ if (m_surfaceManager) {
2020+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfaceCreated, this, &TopLevelWindowModel::onSurfaceCreated);
2021+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::surfacesRaised, this, &TopLevelWindowModel::onSurfacesRaised);
2022+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsStarted, this, &TopLevelWindowModel::onModificationsStarted);
2023+ connect(m_surfaceManager, &unityapi::SurfaceManagerInterface::modificationsEnded, this, &TopLevelWindowModel::onModificationsEnded);
2024+ }
2025+
2026+ Q_EMIT surfaceManagerChanged(m_surfaceManager);
2027+}
2028+
2029+void TopLevelWindowModel::addApplication(unityapi::ApplicationInfoInterface *application)
2030+{
2031+ DEBUG_MSG << "(" << application->appId() << ")";
2032+
2033+ if (application->state() != unityapi::ApplicationInfoInterface::Stopped && application->surfaceList()->count() == 0) {
2034+ prependPlaceholder(application);
2035+ }
2036+}
2037+
2038+void TopLevelWindowModel::removeApplication(unityapi::ApplicationInfoInterface *application)
2039+{
2040+ DEBUG_MSG << "(" << application->appId() << ")";
2041+
2042+ Q_ASSERT(m_modelState == IdleState);
2043+
2044+ int i = 0;
2045+ while (i < m_windowModel.count()) {
2046+ if (m_windowModel.at(i).application == application) {
2047+ removeAt(i);
2048+ } else {
2049+ ++i;
2050+ }
2051+ }
2052+}
2053+
2054+void TopLevelWindowModel::prependPlaceholder(unityapi::ApplicationInfoInterface *application)
2055+{
2056+ INFO_MSG << "(" << application->appId() << ")";
2057+
2058+ prependSurfaceHelper(nullptr, application);
2059+}
2060+
2061+void TopLevelWindowModel::prependSurface(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
2062+{
2063+ Q_ASSERT(surface != nullptr);
2064+
2065+ bool filledPlaceholder = false;
2066+ for (int i = 0; i < m_windowModel.count() && !filledPlaceholder; ++i) {
2067+ ModelEntry &entry = m_windowModel[i];
2068+ if (entry.application == application && entry.window->surface() == nullptr) {
2069+ entry.window->setSurface(surface);
2070+ connectSurface(surface);
2071+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface
2072+ << ", filling out placeholder. after: " << toString();
2073+ filledPlaceholder = true;
2074+ }
2075+ }
2076+
2077+ if (!filledPlaceholder) {
2078+ INFO_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
2079+ prependSurfaceHelper(surface, application);
2080+ }
2081+}
2082+
2083+void TopLevelWindowModel::prependSurfaceHelper(unityapi::MirSurfaceInterface *surface, unityapi::ApplicationInfoInterface *application)
2084+{
2085+ if (m_modelState == IdleState) {
2086+ m_modelState = InsertingState;
2087+ beginInsertRows(QModelIndex(), 0 /*first*/, 0 /*last*/);
2088+ } else {
2089+ Q_ASSERT(m_modelState == ResettingState);
2090+ // No point in signaling anything if we're resetting the whole model
2091+ }
2092+
2093+ int id = generateId();
2094+ Window *window = new Window(id, this);
2095+ if (surface) {
2096+ window->setSurface(surface);
2097+ }
2098+ m_windowModel.prepend(ModelEntry(window, application));
2099+ if (surface) {
2100+ connectSurface(surface);
2101+ }
2102+
2103+ connectWindow(window);
2104+
2105+ if (m_modelState == InsertingState) {
2106+ endInsertRows();
2107+ Q_EMIT countChanged();
2108+ Q_EMIT listChanged();
2109+ m_modelState = IdleState;
2110+ }
2111+
2112+ if (!surface) {
2113+ activateEmptyWindow(window);
2114+ }
2115+
2116+ INFO_MSG << " after " << toString();
2117+}
2118+
2119+void TopLevelWindowModel::connectWindow(Window *window)
2120+{
2121+ connect(window, &Window::focusRequested, this, [this, window]() {
2122+ if (!window->surface()) {
2123+ activateEmptyWindow(window);
2124+ }
2125+ });
2126+
2127+ connect(window, &Window::focusedChanged, this, [this, window](bool focused) {
2128+ if (window->surface()) {
2129+ // Condense changes to the focused window
2130+ // eg: Do focusedWindow=A to focusedWindow=B instead of
2131+ // focusedWindow=A to focusedWindow=null to focusedWindow=B
2132+ if (focused) {
2133+ Q_ASSERT(m_newlyFocusedWindow == nullptr);
2134+ m_focusedWindowChanged = true;
2135+ m_newlyFocusedWindow = window;
2136+ } else if (m_focusedWindow == window) {
2137+ m_focusedWindowChanged = true;
2138+ } else {
2139+ // don't clear the focused window if you were not there in the first place
2140+ // happens when a filled window gets replaced with an empty one (no surface) as the focused window.
2141+ }
2142+ }
2143+ });
2144+
2145+ connect(window, &Window::closeRequested, this, [this, window]() {
2146+ if (!window->surface()) {
2147+ // do things ourselves as miral doesn't know about this window
2148+ int id = window->id();
2149+ int index = indexForId(id);
2150+ bool focusOther = false;
2151+ Q_ASSERT(index >= 0);
2152+ if (window->focused()) {
2153+ focusOther = true;
2154+ }
2155+ m_windowModel[index].application->close();
2156+ if (focusOther) {
2157+ activateTopMostWindowWithoutId(id);
2158+ }
2159+ }
2160+ });
2161+
2162+ connect(window, &Window::emptyWindowActivated, this, [this, window]() {
2163+ activateEmptyWindow(window);
2164+ });
2165+}
2166+
2167+void TopLevelWindowModel::activateEmptyWindow(Window *window)
2168+{
2169+ Q_ASSERT(!window->surface());
2170+ DEBUG_MSG << "(" << window << ")";
2171+
2172+ // miral doesn't know about empty windows (ie, windows that are not backed up by MirSurfaces)
2173+ // So we have to activate them ourselves (instead of asking SurfaceManager to do it for us).
2174+
2175+ window->setFocused(true);
2176+ raiseId(window->id());
2177+ Window *previousWindow = m_focusedWindow;
2178+ setFocusedWindow(window);
2179+ if (previousWindow && previousWindow->surface() && previousWindow->surface()->focused()) {
2180+ m_surfaceManager->activate(nullptr);
2181+ }
2182+}
2183+
2184+void TopLevelWindowModel::connectSurface(unityapi::MirSurfaceInterface *surface)
2185+{
2186+ connect(surface, &unityapi::MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
2187+ if (!live) {
2188+ onSurfaceDied(surface);
2189+ }
2190+ });
2191+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
2192+}
2193+
2194+void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
2195+{
2196+ if (surface->type() == Mir::InputMethodType) {
2197+ removeInputMethodWindow();
2198+ return;
2199+ }
2200+
2201+ int i = indexOf(surface);
2202+ if (i == -1) {
2203+ return;
2204+ }
2205+
2206+ auto application = m_windowModel[i].application;
2207+
2208+ // can't be starting if it already has a surface
2209+ Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
2210+
2211+ if (application->state() == unityapi::ApplicationInfoInterface::Running) {
2212+ m_windowModel[i].removeOnceSurfaceDestroyed = true;
2213+ } else {
2214+ // assume it got killed by the out-of-memory daemon.
2215+ //
2216+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
2217+ // in its place.
2218+ m_windowModel[i].removeOnceSurfaceDestroyed = false;
2219+ }
2220+}
2221+
2222+void TopLevelWindowModel::onSurfaceDestroyed(unityapi::MirSurfaceInterface *surface)
2223+{
2224+ int i = indexOf(surface);
2225+ if (i == -1) {
2226+ return;
2227+ }
2228+
2229+ if (m_windowModel[i].removeOnceSurfaceDestroyed) {
2230+ removeAt(i);
2231+ } else {
2232+ auto window = m_windowModel[i].window;
2233+ window->setSurface(nullptr);
2234+ window->setFocused(false);
2235+ INFO_MSG << " Removed surface from entry. After: " << toString();
2236+ }
2237+}
2238+
2239+void TopLevelWindowModel::onSurfaceCreated(unityapi::MirSurfaceInterface *surface)
2240+{
2241+ DEBUG_MSG << "(" << surface << ")";
2242+ if (surface->type() == Mir::InputMethodType) {
2243+ int id = generateId();
2244+ Window *qmlWindow = new Window(id, this);
2245+ connectWindow(qmlWindow);
2246+ qmlWindow->setSurface(surface);
2247+ setInputMethodWindow(qmlWindow);
2248+ } else {
2249+ auto application = m_applicationManager->findApplicationWithSurface(surface);
2250+ if (application) {
2251+ prependSurface(surface, application);
2252+ } else {
2253+ // Must be a prompt session. No need to do add it as a prompt surface is not top-level.
2254+ // It will show up in the ApplicationInfoInterface::promptSurfaceList of some application.
2255+ // Still wrap it in a Window though, so that we keep focusedWindow() up to date.
2256+ int id = generateId();
2257+ Window *promptWindow = new Window(id, this);
2258+ connectWindow(promptWindow);
2259+ promptWindow->setSurface(surface);
2260+ connect(surface, &QObject::destroyed, promptWindow, [=](){
2261+ promptWindow->setSurface(nullptr);
2262+ promptWindow->deleteLater();
2263+ });
2264+ }
2265+ }
2266+ // TODO: handle surfaces that are neither top-level windows nor input method. eg: child dialogs, popups, menus
2267+}
2268+
2269+void TopLevelWindowModel::removeAt(int index)
2270+{
2271+ if (m_modelState == IdleState) {
2272+ beginRemoveRows(QModelIndex(), index, index);
2273+ m_modelState = RemovingState;
2274+ } else {
2275+ Q_ASSERT(m_modelState == ResettingState);
2276+ // No point in signaling anything if we're resetting the whole model
2277+ }
2278+
2279+ auto window = m_windowModel[index].window;
2280+
2281+ window->setSurface(nullptr);
2282+ window->setFocused(false);
2283+
2284+ m_windowModel.removeAt(index);
2285+
2286+ if (m_modelState == RemovingState) {
2287+ endRemoveRows();
2288+ Q_EMIT countChanged();
2289+ Q_EMIT listChanged();
2290+ m_modelState = IdleState;
2291+ }
2292+
2293+ disconnect(window, 0, this, 0);
2294+ if (m_focusedWindow == window) {
2295+ setFocusedWindow(nullptr);
2296+ }
2297+ delete window;
2298+
2299+ INFO_MSG << " after " << toString();
2300+}
2301+
2302+void TopLevelWindowModel::setInputMethodWindow(Window *window)
2303+{
2304+ if (m_inputMethodWindow) {
2305+ qWarning("Multiple Input Method Surfaces created, removing the old one!");
2306+ delete m_inputMethodWindow;
2307+ }
2308+ m_inputMethodWindow = window;
2309+ Q_EMIT inputMethodSurfaceChanged(m_inputMethodWindow->surface());
2310+}
2311+
2312+void TopLevelWindowModel::removeInputMethodWindow()
2313+{
2314+ if (m_inputMethodWindow) {
2315+ delete m_inputMethodWindow;
2316+ m_inputMethodWindow = nullptr;
2317+ Q_EMIT inputMethodSurfaceChanged(nullptr);
2318+ }
2319+}
2320+
2321+void TopLevelWindowModel::onSurfacesRaised(const QVector<unityapi::MirSurfaceInterface*> &surfaces)
2322+{
2323+ DEBUG_MSG << "(" << surfaces << ")";
2324+ const int raiseCount = surfaces.size();
2325+ for (int i = 0; i < raiseCount; i++) {
2326+ int fromIndex = findIndexOf(surfaces[i]);
2327+ if (fromIndex != -1) {
2328+ move(fromIndex, 0);
2329+ }
2330+ }
2331+}
2332+
2333+int TopLevelWindowModel::rowCount(const QModelIndex &/*parent*/) const
2334+{
2335+ return m_windowModel.count();
2336+}
2337+
2338+QVariant TopLevelWindowModel::data(const QModelIndex& index, int role) const
2339+{
2340+ if (index.row() < 0 || index.row() >= m_windowModel.size())
2341+ return QVariant();
2342+
2343+ if (role == WindowRole) {
2344+ Window *window = m_windowModel.at(index.row()).window;
2345+ return QVariant::fromValue(window);
2346+ } else if (role == ApplicationRole) {
2347+ return QVariant::fromValue(m_windowModel.at(index.row()).application);
2348+ } else {
2349+ return QVariant();
2350+ }
2351+}
2352+
2353+int TopLevelWindowModel::findIndexOf(const unityapi::MirSurfaceInterface *surface) const
2354+{
2355+ for (int i=0; i<m_windowModel.count(); i++) {
2356+ if (m_windowModel[i].window->surface() == surface) {
2357+ return i;
2358+ }
2359+ }
2360+ return -1;
2361+}
2362+
2363+int TopLevelWindowModel::generateId()
2364+{
2365+ int id = m_nextId;
2366+ m_nextId = nextFreeId(nextId(id), id);
2367+ return id;
2368+}
2369+
2370+int TopLevelWindowModel::nextId(int id) const
2371+{
2372+ if (id == m_maxId) {
2373+ return id = 1;
2374+ } else {
2375+ return id + 1;
2376+ }
2377+}
2378+
2379+int TopLevelWindowModel::nextFreeId(int candidateId, const int latestId)
2380+{
2381+ int firstCandidateId = candidateId;
2382+
2383+ while (indexForId(candidateId) != -1 || candidateId == latestId) {
2384+ candidateId = nextId(candidateId);
2385+
2386+ if (candidateId == firstCandidateId) {
2387+ qFatal("TopLevelWindowModel: run out of window ids.");
2388+ }
2389+ }
2390+
2391+ return candidateId;
2392+}
2393+
2394+QString TopLevelWindowModel::toString()
2395+{
2396+ QString str;
2397+ for (int i = 0; i < m_windowModel.count(); ++i) {
2398+ auto item = m_windowModel.at(i);
2399+
2400+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
2401+ .arg(QString::number(i),
2402+ item.application->appId(),
2403+ QString::number((qintptr)item.window->surface(), 16),
2404+ QString::number(item.window->id()));
2405+
2406+ if (i > 0) {
2407+ str.append(",");
2408+ }
2409+ str.append(itemStr);
2410+ }
2411+ return str;
2412+}
2413+
2414+int TopLevelWindowModel::indexOf(unityapi::MirSurfaceInterface *surface)
2415+{
2416+ for (int i = 0; i < m_windowModel.count(); ++i) {
2417+ if (m_windowModel.at(i).window->surface() == surface) {
2418+ return i;
2419+ }
2420+ }
2421+ return -1;
2422+}
2423+
2424+int TopLevelWindowModel::indexForId(int id) const
2425+{
2426+ for (int i = 0; i < m_windowModel.count(); ++i) {
2427+ if (m_windowModel[i].window->id() == id) {
2428+ return i;
2429+ }
2430+ }
2431+ return -1;
2432+}
2433+
2434+Window *TopLevelWindowModel::windowAt(int index) const
2435+{
2436+ if (index >=0 && index < m_windowModel.count()) {
2437+ return m_windowModel[index].window;
2438+ } else {
2439+ return nullptr;
2440+ }
2441+}
2442+
2443+unityapi::MirSurfaceInterface *TopLevelWindowModel::surfaceAt(int index) const
2444+{
2445+ if (index >=0 && index < m_windowModel.count()) {
2446+ return m_windowModel[index].window->surface();
2447+ } else {
2448+ return nullptr;
2449+ }
2450+}
2451+
2452+unityapi::ApplicationInfoInterface *TopLevelWindowModel::applicationAt(int index) const
2453+{
2454+ if (index >=0 && index < m_windowModel.count()) {
2455+ return m_windowModel[index].application;
2456+ } else {
2457+ return nullptr;
2458+ }
2459+}
2460+
2461+int TopLevelWindowModel::idAt(int index) const
2462+{
2463+ if (index >=0 && index < m_windowModel.count()) {
2464+ return m_windowModel[index].window->id();
2465+ } else {
2466+ return 0;
2467+ }
2468+}
2469+
2470+void TopLevelWindowModel::raiseId(int id)
2471+{
2472+ if (m_modelState == IdleState) {
2473+ DEBUG_MSG << "(id=" << id << ") - do it now.";
2474+ doRaiseId(id);
2475+ } else {
2476+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
2477+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
2478+ // if we perform yet another model change straight away.
2479+ //
2480+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
2481+ // the index is definitely within bounds.
2482+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
2483+ }
2484+}
2485+
2486+void TopLevelWindowModel::doRaiseId(int id)
2487+{
2488+ int fromIndex = indexForId(id);
2489+ // can't raise something that doesn't exist or that it's already on top
2490+ if (fromIndex != -1 && fromIndex != 0) {
2491+ auto surface = m_windowModel[fromIndex].window->surface();
2492+ if (surface) {
2493+ m_surfaceManager->raise(surface);
2494+ } else {
2495+ // move it ourselves. Since there's no mir::scene::Surface/miral::Window, there's nothing
2496+ // miral can do about it.
2497+ move(fromIndex, 0);
2498+ }
2499+ }
2500+}
2501+
2502+void TopLevelWindowModel::setFocusedWindow(Window *window)
2503+{
2504+ if (window != m_focusedWindow) {
2505+ INFO_MSG << "(" << window << ")";
2506+
2507+ Window* previousWindow = m_focusedWindow;
2508+
2509+ m_focusedWindow = window;
2510+ Q_EMIT focusedWindowChanged(m_focusedWindow);
2511+
2512+ if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
2513+ // do it ourselves. miral doesn't know about this window
2514+ previousWindow->setFocused(false);
2515+ }
2516+ }
2517+}
2518+
2519+unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
2520+{
2521+ return m_inputMethodWindow ? m_inputMethodWindow->surface() : nullptr;
2522+}
2523+
2524+Window* TopLevelWindowModel::focusedWindow() const
2525+{
2526+ return m_focusedWindow;
2527+}
2528+
2529+void TopLevelWindowModel::move(int from, int to)
2530+{
2531+ if (from == to) return;
2532+ DEBUG_MSG << " from=" << from << " to=" << to;
2533+
2534+ if (from >= 0 && from < m_windowModel.size() && to >= 0 && to < m_windowModel.size()) {
2535+ QModelIndex parent;
2536+ /* When moving an item down, the destination index needs to be incremented
2537+ by one, as explained in the documentation:
2538+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
2539+
2540+ Q_ASSERT(m_modelState == IdleState);
2541+ m_modelState = MovingState;
2542+
2543+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
2544+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
2545+ const auto &window = m_windowModel.takeAt(from);
2546+ m_windowModel.insert(to, window);
2547+#else
2548+ m_windowModel.move(from, to);
2549+#endif
2550+ endMoveRows();
2551+
2552+ Q_EMIT listChanged();
2553+ m_modelState = IdleState;
2554+
2555+ INFO_MSG << " after " << toString();
2556+ }
2557+}
2558+void TopLevelWindowModel::onModificationsStarted()
2559+{
2560+}
2561+
2562+void TopLevelWindowModel::onModificationsEnded()
2563+{
2564+ if (m_focusedWindowChanged) {
2565+ setFocusedWindow(m_newlyFocusedWindow);
2566+ }
2567+ // reset
2568+ m_focusedWindowChanged = false;
2569+ m_newlyFocusedWindow = nullptr;
2570+}
2571+
2572+void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
2573+{
2574+ DEBUG_MSG << "(" << forbiddenId << ")";
2575+
2576+ for (int i = 0; i < m_windowModel.count(); ++i) {
2577+ Window *window = m_windowModel[i].window;
2578+ if (window->id() != forbiddenId) {
2579+ window->activate();
2580+ }
2581+ }
2582+}
2583
2584=== added file 'plugins/WindowManager/TopLevelWindowModel.h'
2585--- plugins/WindowManager/TopLevelWindowModel.h 1970-01-01 00:00:00 +0000
2586+++ plugins/WindowManager/TopLevelWindowModel.h 2016-12-12 16:49:02 +0000
2587@@ -0,0 +1,260 @@
2588+/*
2589+ * Copyright (C) 2016 Canonical, Ltd.
2590+ *
2591+ * This program is free software: you can redistribute it and/or modify it under
2592+ * the terms of the GNU Lesser General Public License version 3, as published by
2593+ * the Free Software Foundation.
2594+ *
2595+ * This program is distributed in the hope that it will be useful, but WITHOUT
2596+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2597+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2598+ * Lesser General Public License for more details.
2599+ *
2600+ * You should have received a copy of the GNU Lesser General Public License
2601+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2602+ */
2603+
2604+#ifndef TOPLEVELWINDOWMODEL_H
2605+#define TOPLEVELWINDOWMODEL_H
2606+
2607+#include <QAbstractListModel>
2608+#include <QLoggingCategory>
2609+
2610+Q_DECLARE_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL)
2611+
2612+class Window;
2613+
2614+namespace unity {
2615+ namespace shell {
2616+ namespace application {
2617+ class ApplicationInfoInterface;
2618+ class ApplicationManagerInterface;
2619+ class MirSurfaceInterface;
2620+ class SurfaceManagerInterface;
2621+ }
2622+ }
2623+}
2624+
2625+/**
2626+ * @brief A model of top-level surfaces
2627+ *
2628+ * It's an abstraction of top-level application windows.
2629+ *
2630+ * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
2631+ * still starting up. A shell should then display a splash screen or saved screenshot of the application
2632+ * until its surface comes up.
2633+ *
2634+ * As applications can have multiple surfaces and you can also have entries without surfaces at all,
2635+ * the only way to unambiguously refer to an entry in this model is through its id.
2636+ */
2637+class TopLevelWindowModel : public QAbstractListModel
2638+{
2639+ Q_OBJECT
2640+
2641+ /**
2642+ * @brief Number of top-level surfaces in this model
2643+ *
2644+ * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
2645+ */
2646+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
2647+
2648+ /**
2649+ * @brief The input method surface, if any
2650+ *
2651+ * The surface of a onscreen keyboard (akak "virtual keyboard") would be kept here and not in the model itself.
2652+ */
2653+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
2654+
2655+ /**
2656+ * @brief The currently focused window, if any
2657+ */
2658+ Q_PROPERTY(Window* focusedWindow READ focusedWindow NOTIFY focusedWindowChanged)
2659+
2660+ Q_PROPERTY(unity::shell::application::SurfaceManagerInterface* surfaceManager
2661+ READ surfaceManager
2662+ WRITE setSurfaceManager
2663+ NOTIFY surfaceManagerChanged)
2664+
2665+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationManager
2666+ READ applicationManager
2667+ WRITE setApplicationManager
2668+ NOTIFY applicationManagerChanged)
2669+
2670+ /**
2671+ The id to be used on the next entry created
2672+ Useful for tests
2673+ */
2674+ Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
2675+
2676+public:
2677+ /**
2678+ * @brief The Roles supported by the model
2679+ *
2680+ * WindowRole - A Window.
2681+ * ApplicationRole - An ApplicationInfoInterface
2682+ */
2683+ enum Roles {
2684+ WindowRole = Qt::UserRole,
2685+ ApplicationRole = Qt::UserRole + 1,
2686+ };
2687+
2688+ TopLevelWindowModel();
2689+
2690+ // From QAbstractItemModel
2691+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
2692+ QVariant data(const QModelIndex& index, int role) const override;
2693+ QHash<int, QByteArray> roleNames() const override {
2694+ QHash<int, QByteArray> roleNames { {WindowRole, "window"},
2695+ {ApplicationRole, "application"} };
2696+ return roleNames;
2697+ }
2698+
2699+ // Own API
2700+
2701+ unity::shell::application::MirSurfaceInterface* inputMethodSurface() const;
2702+ Window* focusedWindow() const;
2703+
2704+ unity::shell::application::ApplicationManagerInterface *applicationManager() const { return m_applicationManager; }
2705+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface*);
2706+
2707+ unity::shell::application::SurfaceManagerInterface *surfaceManager() const { return m_surfaceManager; }
2708+ void setSurfaceManager(unity::shell::application::SurfaceManagerInterface*);
2709+
2710+ int nextId() const { return m_nextId; }
2711+
2712+public:
2713+ /**
2714+ * @brief Returns the surface at the given index
2715+ *
2716+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
2717+ * and drawn into a surface.
2718+ *
2719+ * Same as windowAt(index).surface()
2720+ */
2721+ Q_INVOKABLE unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
2722+
2723+ /**
2724+ * @brief Returns the window at the given index
2725+ *
2726+ * Will always be valid
2727+ */
2728+ Q_INVOKABLE Window *windowAt(int index) const;
2729+
2730+ /**
2731+ * @brief Returns the application at the given index
2732+ */
2733+ Q_INVOKABLE unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
2734+
2735+ /**
2736+ * @brief Returns the unique id of the element at the given index
2737+ */
2738+ Q_INVOKABLE int idAt(int index) const;
2739+
2740+ /**
2741+ * @brief Returns the index where the row with the given id is located
2742+ *
2743+ * Returns -1 if there's no row with the given id.
2744+ */
2745+ Q_INVOKABLE int indexForId(int id) const;
2746+
2747+ /**
2748+ * @brief Raises the row with the given id to the top of the window stack (index == count-1)
2749+ */
2750+ Q_INVOKABLE void raiseId(int id);
2751+
2752+Q_SIGNALS:
2753+ void countChanged();
2754+ void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
2755+ void focusedWindowChanged(Window *focusedWindow);
2756+ void applicationManagerChanged(unity::shell::application::ApplicationManagerInterface*);
2757+ void surfaceManagerChanged(unity::shell::application::SurfaceManagerInterface*);
2758+
2759+ /**
2760+ * @brief Emitted when the list changes
2761+ *
2762+ * Emitted when model gains an element, loses an element or when elements exchange positions.
2763+ */
2764+ void listChanged();
2765+
2766+ void nextIdChanged();
2767+
2768+private Q_SLOTS:
2769+ void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
2770+ void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
2771+
2772+ void onModificationsStarted();
2773+ void onModificationsEnded();
2774+
2775+private:
2776+ void doRaiseId(int id);
2777+ int generateId();
2778+ int nextFreeId(int candidateId, const int latestId);
2779+ int nextId(int id) const;
2780+ QString toString();
2781+ int indexOf(unity::shell::application::MirSurfaceInterface *surface);
2782+
2783+ void setInputMethodWindow(Window *window);
2784+ void setFocusedWindow(Window *window);
2785+ void removeInputMethodWindow();
2786+ int findIndexOf(const unity::shell::application::MirSurfaceInterface *surface) const;
2787+ void removeAt(int index);
2788+
2789+ void addApplication(unity::shell::application::ApplicationInfoInterface *application);
2790+ void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
2791+
2792+ void prependPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
2793+ void prependSurface(unity::shell::application::MirSurfaceInterface *surface,
2794+ unity::shell::application::ApplicationInfoInterface *application);
2795+ void prependSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
2796+ unity::shell::application::ApplicationInfoInterface *application);
2797+
2798+ void connectWindow(Window *window);
2799+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
2800+
2801+ void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
2802+ void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
2803+
2804+ void move(int from, int to);
2805+
2806+ void activateEmptyWindow(Window *window);
2807+
2808+ void activateTopMostWindowWithoutId(int forbiddenId);
2809+
2810+ struct ModelEntry {
2811+ ModelEntry() {}
2812+ ModelEntry(Window *window,
2813+ unity::shell::application::ApplicationInfoInterface *application)
2814+ : window(window), application(application) {}
2815+ Window *window{nullptr};
2816+ unity::shell::application::ApplicationInfoInterface *application{nullptr};
2817+ bool removeOnceSurfaceDestroyed{false};
2818+ };
2819+
2820+ QVector<ModelEntry> m_windowModel;
2821+ Window* m_inputMethodWindow{nullptr};
2822+ Window* m_focusedWindow{nullptr};
2823+
2824+ int m_nextId{1};
2825+ // Just something big enough that we don't risk running out of unused id numbers.
2826+ // Not sure if QML int type supports something close to std::numeric_limits<int>::max() and
2827+ // there's no reason to try out its limits.
2828+ static const int m_maxId{1000000};
2829+
2830+ unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
2831+ unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
2832+
2833+ enum ModelState {
2834+ IdleState,
2835+ InsertingState,
2836+ RemovingState,
2837+ MovingState,
2838+ ResettingState
2839+ };
2840+ ModelState m_modelState{IdleState};
2841+
2842+ // Valid between modificationsStarted and modificationsEnded
2843+ bool m_focusedWindowChanged{false};
2844+ Window *m_newlyFocusedWindow{nullptr};
2845+};
2846+
2847+#endif // TOPLEVELWINDOWMODEL_H
2848
2849=== added file 'plugins/WindowManager/Window.cpp'
2850--- plugins/WindowManager/Window.cpp 1970-01-01 00:00:00 +0000
2851+++ plugins/WindowManager/Window.cpp 2016-12-12 16:49:02 +0000
2852@@ -0,0 +1,237 @@
2853+/*
2854+ * Copyright (C) 2016 Canonical, Ltd.
2855+ *
2856+ * This program is free software; you can redistribute it and/or modify
2857+ * it under the terms of the GNU General Public License as published by
2858+ * the Free Software Foundation; version 3.
2859+ *
2860+ * This program is distributed in the hope that it will be useful,
2861+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2862+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2863+ * GNU General Public License for more details.
2864+ *
2865+ * You should have received a copy of the GNU General Public License
2866+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2867+ */
2868+
2869+#include "Window.h"
2870+
2871+// unity-api
2872+#include <unity/shell/application/MirSurfaceInterface.h>
2873+
2874+#include <QQmlEngine>
2875+#include <QTextStream>
2876+
2877+namespace unityapi = unity::shell::application;
2878+
2879+Q_LOGGING_CATEGORY(UNITY_WINDOW, "unity.window", QtWarningMsg)
2880+
2881+#define DEBUG_MSG qCDebug(UNITY_WINDOW).nospace() << qPrintable(toString()) << "::" << __func__
2882+
2883+Window::Window(int id, QObject *parent)
2884+ : QObject(parent)
2885+ , m_id(id)
2886+{
2887+ DEBUG_MSG << "()";
2888+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
2889+}
2890+
2891+Window::~Window()
2892+{
2893+ DEBUG_MSG << "()";
2894+}
2895+
2896+QPoint Window::position() const
2897+{
2898+ return m_position;
2899+}
2900+
2901+QPoint Window::requestedPosition() const
2902+{
2903+ return m_requestedPosition;
2904+}
2905+
2906+void Window::setRequestedPosition(const QPoint &value)
2907+{
2908+ m_positionRequested = true;
2909+ if (value != m_requestedPosition) {
2910+ m_requestedPosition = value;
2911+ Q_EMIT requestedPositionChanged(m_requestedPosition);
2912+ if (m_surface) {
2913+ m_surface->setRequestedPosition(value);
2914+ } else {
2915+ // fake-miral: always comply
2916+ m_position = m_requestedPosition;
2917+ Q_EMIT positionChanged(m_position);
2918+ }
2919+ }
2920+}
2921+
2922+Mir::State Window::state() const
2923+{
2924+ return m_state;
2925+}
2926+
2927+bool Window::focused() const
2928+{
2929+ return m_focused;
2930+}
2931+
2932+bool Window::confinesMousePointer() const
2933+{
2934+ if (m_surface) {
2935+ return m_surface->confinesMousePointer();
2936+ } else {
2937+ return false;
2938+ }
2939+}
2940+
2941+int Window::id() const
2942+{
2943+ return m_id;
2944+}
2945+
2946+unityapi::MirSurfaceInterface* Window::surface() const
2947+{
2948+ return m_surface;
2949+}
2950+
2951+void Window::requestState(Mir::State state)
2952+{
2953+ m_stateRequested = true;
2954+ if (m_surface) {
2955+ m_surface->requestState(state);
2956+ } else if (m_state != state) {
2957+ m_state = state;
2958+ Q_EMIT stateChanged(m_state);
2959+ }
2960+}
2961+
2962+void Window::close()
2963+{
2964+ if (m_surface) {
2965+ m_surface->close();
2966+ } else {
2967+ Q_EMIT closeRequested();
2968+ }
2969+}
2970+
2971+void Window::activate()
2972+{
2973+ DEBUG_MSG << "()";
2974+ if (m_surface) {
2975+ m_surface->activate();
2976+ } else {
2977+ Q_EMIT emptyWindowActivated();
2978+ }
2979+}
2980+
2981+void Window::setSurface(unityapi::MirSurfaceInterface *surface)
2982+{
2983+ DEBUG_MSG << "(" << surface << ")";
2984+ if (m_surface) {
2985+ disconnect(m_surface, 0, this, 0);
2986+ }
2987+
2988+ m_surface = surface;
2989+
2990+ if (m_surface) {
2991+ connect(surface, &unityapi::MirSurfaceInterface::focusRequested, this, [this]() {
2992+ Q_EMIT focusRequested();
2993+ });
2994+
2995+ connect(surface, &unityapi::MirSurfaceInterface::closeRequested, this, &Window::closeRequested);
2996+
2997+ connect(surface, &unityapi::MirSurfaceInterface::positionChanged, this, [this]() {
2998+ updatePosition();
2999+ });
3000+
3001+ connect(surface, &unityapi::MirSurfaceInterface::stateChanged, this, [this]() {
3002+ updateState();
3003+ });
3004+
3005+ connect(surface, &unityapi::MirSurfaceInterface::focusedChanged, this, [this]() {
3006+ updateFocused();
3007+ });
3008+
3009+ // bring it up to speed
3010+ if (m_positionRequested) {
3011+ m_surface->setRequestedPosition(m_requestedPosition);
3012+ }
3013+ if (m_stateRequested && m_surface->state() == Mir::RestoredState) {
3014+ m_surface->requestState(m_state);
3015+ }
3016+
3017+ // and sync with surface
3018+ updatePosition();
3019+ updateState();
3020+ updateFocused();
3021+ }
3022+
3023+ Q_EMIT surfaceChanged(surface);
3024+}
3025+
3026+void Window::updatePosition()
3027+{
3028+ if (m_surface->position() != m_position) {
3029+ m_position = m_surface->position();
3030+ Q_EMIT positionChanged(m_position);
3031+ }
3032+}
3033+
3034+void Window::updateState()
3035+{
3036+ if (m_surface->state() != m_state) {
3037+ m_state = m_surface->state();
3038+ Q_EMIT stateChanged(m_state);
3039+ }
3040+}
3041+
3042+void Window::updateFocused()
3043+{
3044+ if (m_surface->focused() != m_focused) {
3045+ m_focused = m_surface->focused();
3046+ Q_EMIT focusedChanged(m_focused);
3047+ }
3048+}
3049+
3050+void Window::setFocused(bool value)
3051+{
3052+ if (value != m_focused) {
3053+ DEBUG_MSG << "(" << value << ")";
3054+ m_focused = value;
3055+ Q_EMIT focusedChanged(m_focused);
3056+ // when we have a surface we get focus changes from updateFocused() instead
3057+ Q_ASSERT(!m_surface);
3058+ }
3059+}
3060+
3061+QString Window::toString() const
3062+{
3063+ QString result;
3064+ {
3065+ QTextStream stream(&result);
3066+ stream << "Window["<<(void*)this<<", id="<<id()<<", ";
3067+ if (surface()) {
3068+ stream << "MirSurface["<<(void*)surface()<<",\""<<surface()->name()<<"\"]";
3069+ } else {
3070+ stream << "null";
3071+ }
3072+ stream << "]";
3073+ }
3074+ return result;
3075+}
3076+
3077+QDebug operator<<(QDebug dbg, const Window *window)
3078+{
3079+ QDebugStateSaver saver(dbg);
3080+ dbg.nospace();
3081+
3082+ if (window) {
3083+ dbg << qPrintable(window->toString());
3084+ } else {
3085+ dbg << (void*)(window);
3086+ }
3087+
3088+ return dbg;
3089+}
3090
3091=== added file 'plugins/WindowManager/Window.h'
3092--- plugins/WindowManager/Window.h 1970-01-01 00:00:00 +0000
3093+++ plugins/WindowManager/Window.h 2016-12-12 16:49:02 +0000
3094@@ -0,0 +1,161 @@
3095+/*
3096+ * Copyright (C) 2016 Canonical, Ltd.
3097+ *
3098+ * This program is free software; you can redistribute it and/or modify
3099+ * it under the terms of the GNU General Public License as published by
3100+ * the Free Software Foundation; version 3.
3101+ *
3102+ * This program is distributed in the hope that it will be useful,
3103+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3104+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3105+ * GNU General Public License for more details.
3106+ *
3107+ * You should have received a copy of the GNU General Public License
3108+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3109+ */
3110+
3111+#ifndef UNITY_WINDOW_H
3112+#define UNITY_WINDOW_H
3113+
3114+#include <QLoggingCategory>
3115+#include <QObject>
3116+#include <QPoint>
3117+
3118+// Unity API
3119+#include <unity/shell/application/Mir.h>
3120+
3121+namespace unity {
3122+ namespace shell {
3123+ namespace application {
3124+ class MirSurfaceInterface;
3125+ }
3126+ }
3127+}
3128+
3129+
3130+Q_DECLARE_LOGGING_CATEGORY(UNITY_WINDOW)
3131+
3132+/**
3133+ @brief A slightly higher concept than MirSurface
3134+
3135+ A Window exists before its MirSurface gets created (for splashscreen purposes)
3136+ and might also hang around after the backing surface is gone (In case the application
3137+ was killed to free up memory, as it should still remain in the window list since the user
3138+ did not explicitly close it).
3139+ */
3140+class Window : public QObject
3141+{
3142+ Q_OBJECT
3143+
3144+ /**
3145+ * @brief Position of the current surface buffer, in pixels.
3146+ */
3147+ Q_PROPERTY(QPoint position READ position NOTIFY positionChanged)
3148+
3149+ /**
3150+ * @brief Requested position of the current surface buffer, in pixels.
3151+ */
3152+ Q_PROPERTY(QPoint requestedPosition READ requestedPosition WRITE setRequestedPosition NOTIFY requestedPositionChanged)
3153+
3154+ /**
3155+ * @brief State of the surface
3156+ */
3157+ Q_PROPERTY(Mir::State state READ state NOTIFY stateChanged)
3158+
3159+ /**
3160+ * @brief Whether the surface is focused
3161+ *
3162+ * It will be true if this surface is MirFocusControllerInterface::focusedSurface
3163+ */
3164+ Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged)
3165+
3166+ /**
3167+ * @brief Whether the surface wants to confine the mouse pointer within its boundaries
3168+ *
3169+ * If true, the surface doesn't want the mouse pointer to leave its boundaries while it's focused.
3170+ */
3171+ Q_PROPERTY(bool confinesMousePointer READ confinesMousePointer NOTIFY confinesMousePointerChanged)
3172+
3173+ /**
3174+ * @brief A unique identifier for this window.
3175+ * Useful for telling windows apart in a list model as they get moved around
3176+ */
3177+ Q_PROPERTY(int id READ id CONSTANT)
3178+
3179+ /**
3180+ * @brief Surface backing up this window
3181+ * It might be null if a surface hasn't been created yet (application is starting up) or if
3182+ * the corresponding application has been killed (but can still get restarted to continue from
3183+ * where it left)
3184+ */
3185+ Q_PROPERTY(unity::shell::application::MirSurfaceInterface* surface READ surface NOTIFY surfaceChanged)
3186+
3187+public:
3188+ Window(int id, QObject *parent = nullptr);
3189+ virtual ~Window();
3190+ QPoint position() const;
3191+ QPoint requestedPosition() const;
3192+ void setRequestedPosition(const QPoint &);
3193+ Mir::State state() const;
3194+ bool focused() const;
3195+ bool confinesMousePointer() const;
3196+ int id() const;
3197+ unity::shell::application::MirSurfaceInterface* surface() const;
3198+
3199+ void setSurface(unity::shell::application::MirSurfaceInterface *surface);
3200+ void setFocused(bool value);
3201+
3202+ QString toString() const;
3203+
3204+public Q_SLOTS:
3205+ /**
3206+ * @brief Requests a change to the specified state
3207+ */
3208+ void requestState(Mir::State state);
3209+
3210+ /**
3211+ * @brief Sends a close request
3212+ *
3213+ */
3214+ void close();
3215+
3216+ /**
3217+ * @brief Focuses and raises the window
3218+ */
3219+ void activate();
3220+
3221+Q_SIGNALS:
3222+ void closeRequested();
3223+ void emptyWindowActivated();
3224+
3225+ void positionChanged(QPoint position);
3226+ void requestedPositionChanged(QPoint position);
3227+ void stateChanged(Mir::State value);
3228+ void focusedChanged(bool value);
3229+ void confinesMousePointerChanged(bool value);
3230+ void surfaceChanged(unity::shell::application::MirSurfaceInterface *surface);
3231+
3232+ /**
3233+ * @brief Emitted when focus for this window is requested by an external party
3234+ */
3235+ void focusRequested();
3236+
3237+private:
3238+ void updatePosition();
3239+ void updateState();
3240+ void updateFocused();
3241+
3242+ QPoint m_position;
3243+ QPoint m_requestedPosition;
3244+ bool m_positionRequested{false};
3245+ bool m_focused{false};
3246+ int m_id;
3247+ Mir::State m_state{Mir::RestoredState};
3248+ bool m_stateRequested{false};
3249+ unity::shell::application::MirSurfaceInterface *m_surface{nullptr};
3250+};
3251+
3252+QDebug operator<<(QDebug dbg, const Window *window);
3253+
3254+Q_DECLARE_METATYPE(Window*)
3255+#endif // UNITY_WINDOW_H
3256
3257=== modified file 'plugins/WindowManager/WindowManagerPlugin.cpp'
3258--- plugins/WindowManager/WindowManagerPlugin.cpp 2016-04-04 13:37:49 +0000
3259+++ plugins/WindowManager/WindowManagerPlugin.cpp 2016-12-12 16:49:02 +0000
3260@@ -16,13 +16,16 @@
3261
3262 #include "WindowManagerPlugin.h"
3263
3264-#include "TopLevelSurfaceList.h"
3265+#include "TopLevelWindowModel.h"
3266+#include "Window.h"
3267
3268 #include <QtQml>
3269
3270 void WindowManagerPlugin::registerTypes(const char *uri)
3271 {
3272- qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
3273+ qmlRegisterType<TopLevelWindowModel>(uri, 1, 0, "TopLevelWindowModel");
3274+
3275+ qRegisterMetaType<Window*>("Window*");
3276
3277 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
3278 }
3279
3280=== modified file 'po/unity8.pot'
3281--- po/unity8.pot 2016-11-29 09:43:33 +0000
3282+++ po/unity8.pot 2016-12-12 16:49:02 +0000
3283@@ -8,7 +8,7 @@
3284 msgstr ""
3285 "Project-Id-Version: unity8\n"
3286 "Report-Msgid-Bugs-To: \n"
3287-"POT-Creation-Date: 2016-11-29 09:43+0000\n"
3288+"POT-Creation-Date: 2016-12-07 13:49+0000\n"
3289 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
3290 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
3291 "Language-Team: LANGUAGE <LL@li.org>\n"
3292@@ -23,7 +23,7 @@
3293 msgstr ""
3294
3295 #: plugins/Unity/Launcher/launcheritem.cpp:50
3296-#: plugins/Unity/Launcher/launcheritem.cpp:109
3297+#: plugins/Unity/Launcher/launcheritem.cpp:122
3298 msgid "Pin shortcut"
3299 msgstr ""
3300
3301@@ -31,7 +31,7 @@
3302 msgid "Quit"
3303 msgstr ""
3304
3305-#: plugins/Unity/Launcher/launcheritem.cpp:109
3306+#: plugins/Unity/Launcher/launcheritem.cpp:122
3307 msgid "Unpin shortcut"
3308 msgstr ""
3309
3310@@ -175,87 +175,95 @@
3311 msgid "Same as clicking on a launcher icon."
3312 msgstr ""
3313
3314-#: qml/Components/KeyboardShortcutsOverlay.qml:181
3315+#: qml/Components/KeyboardShortcutsOverlay.qml:177
3316+msgid "Super + A"
3317+msgstr ""
3318+
3319+#: qml/Components/KeyboardShortcutsOverlay.qml:182
3320+msgid "Opens the Application Drawer."
3321+msgstr ""
3322+
3323+#: qml/Components/KeyboardShortcutsOverlay.qml:194
3324 msgid "Scopes"
3325 msgstr ""
3326
3327-#: qml/Components/KeyboardShortcutsOverlay.qml:188
3328+#: qml/Components/KeyboardShortcutsOverlay.qml:201
3329 msgid "Super (Tap)"
3330 msgstr ""
3331
3332-#: qml/Components/KeyboardShortcutsOverlay.qml:193
3333+#: qml/Components/KeyboardShortcutsOverlay.qml:206
3334 msgid "Opens the Scopes home."
3335 msgstr ""
3336
3337-#: qml/Components/KeyboardShortcutsOverlay.qml:209
3338+#: qml/Components/KeyboardShortcutsOverlay.qml:222
3339 msgid "Switching"
3340 msgstr ""
3341
3342-#: qml/Components/KeyboardShortcutsOverlay.qml:216
3343+#: qml/Components/KeyboardShortcutsOverlay.qml:229
3344 msgid "Alt + Tab"
3345 msgstr ""
3346
3347-#: qml/Components/KeyboardShortcutsOverlay.qml:221
3348+#: qml/Components/KeyboardShortcutsOverlay.qml:234
3349 msgid "Switches between applications."
3350 msgstr ""
3351
3352-#: qml/Components/KeyboardShortcutsOverlay.qml:229
3353+#: qml/Components/KeyboardShortcutsOverlay.qml:242
3354 msgid "Super + W"
3355 msgstr ""
3356
3357-#: qml/Components/KeyboardShortcutsOverlay.qml:234
3358+#: qml/Components/KeyboardShortcutsOverlay.qml:247
3359 msgid "Opens the desktop spread."
3360 msgstr ""
3361
3362-#: qml/Components/KeyboardShortcutsOverlay.qml:242
3363+#: qml/Components/KeyboardShortcutsOverlay.qml:255
3364 msgid "Cursor Left or Right"
3365 msgstr ""
3366
3367-#: qml/Components/KeyboardShortcutsOverlay.qml:247
3368+#: qml/Components/KeyboardShortcutsOverlay.qml:260
3369 msgid "Moves the focus."
3370 msgstr ""
3371
3372-#: qml/Components/KeyboardShortcutsOverlay.qml:259
3373+#: qml/Components/KeyboardShortcutsOverlay.qml:272
3374 msgid "Windows"
3375 msgstr ""
3376
3377-#: qml/Components/KeyboardShortcutsOverlay.qml:266
3378+#: qml/Components/KeyboardShortcutsOverlay.qml:279
3379 msgid "Ctrl + Super + D"
3380 msgstr ""
3381
3382-#: qml/Components/KeyboardShortcutsOverlay.qml:271
3383+#: qml/Components/KeyboardShortcutsOverlay.qml:284
3384 msgid "Minimizes all windows."
3385 msgstr ""
3386
3387-#: qml/Components/KeyboardShortcutsOverlay.qml:279
3388+#: qml/Components/KeyboardShortcutsOverlay.qml:292
3389 msgid "Ctrl + Super + Up"
3390 msgstr ""
3391
3392-#: qml/Components/KeyboardShortcutsOverlay.qml:284
3393+#: qml/Components/KeyboardShortcutsOverlay.qml:297
3394 msgid "Maximizes the current window."
3395 msgstr ""
3396
3397-#: qml/Components/KeyboardShortcutsOverlay.qml:292
3398+#: qml/Components/KeyboardShortcutsOverlay.qml:305
3399 msgid "Ctrl + Super + Down"
3400 msgstr ""
3401
3402-#: qml/Components/KeyboardShortcutsOverlay.qml:297
3403+#: qml/Components/KeyboardShortcutsOverlay.qml:310
3404 msgid "Minimizes or restores the current window."
3405 msgstr ""
3406
3407-#: qml/Components/KeyboardShortcutsOverlay.qml:305
3408+#: qml/Components/KeyboardShortcutsOverlay.qml:318
3409 msgid "Ctrl + Super + Left or Right"
3410 msgstr ""
3411
3412-#: qml/Components/KeyboardShortcutsOverlay.qml:310
3413+#: qml/Components/KeyboardShortcutsOverlay.qml:323
3414 msgid "Semi-maximizes the current window."
3415 msgstr ""
3416
3417-#: qml/Components/KeyboardShortcutsOverlay.qml:318
3418+#: qml/Components/KeyboardShortcutsOverlay.qml:331
3419 msgid "Alt + F4"
3420 msgstr ""
3421
3422-#: qml/Components/KeyboardShortcutsOverlay.qml:323
3423+#: qml/Components/KeyboardShortcutsOverlay.qml:336
3424 msgid "Closes the current window."
3425 msgstr ""
3426
3427@@ -311,6 +319,28 @@
3428 msgid "Preview Share Item"
3429 msgstr ""
3430
3431+#: qml/Components/VirtualTouchPad.qml:317
3432+msgid ""
3433+"Your device is now connected to an external display. Use this screen as a "
3434+"touch pad to interact with the pointer."
3435+msgstr ""
3436+
3437+#: qml/Components/VirtualTouchPad.qml:327
3438+msgid "Tap left button to click."
3439+msgstr ""
3440+
3441+#: qml/Components/VirtualTouchPad.qml:339
3442+msgid "Tap right button to right click."
3443+msgstr ""
3444+
3445+#: qml/Components/VirtualTouchPad.qml:351
3446+msgid "Swipe with two fingers to scroll."
3447+msgstr ""
3448+
3449+#: qml/Components/VirtualTouchPad.qml:396
3450+msgid "Find more settings in the system settings."
3451+msgstr ""
3452+
3453 #: qml/Dash/DashPageHeader.qml:361
3454 msgctxt "Button: Open the Ubuntu Store"
3455 msgid "Store"
3456@@ -408,12 +438,6 @@
3457 msgid "Also installed"
3458 msgstr ""
3459
3460-#: qml/DisabledScreenNotice.qml:109
3461-msgid ""
3462-"Your device is now connected to an external display. Use this screen as a "
3463-"touch pad to interact with the pointer."
3464-msgstr ""
3465-
3466 #: qml/Greeter/CoverPage.qml:127
3467 msgid "Unlock"
3468 msgstr ""
3469@@ -437,7 +461,7 @@
3470 msgstr[0] ""
3471 msgstr[1] ""
3472
3473-#: qml/Greeter/Greeter.qml:596
3474+#: qml/Greeter/Greeter.qml:592
3475 msgid "Try again"
3476 msgstr ""
3477
3478@@ -465,6 +489,19 @@
3479 msgid "Select desktop environment"
3480 msgstr ""
3481
3482+#: qml/Launcher/Drawer.qml:80
3483+msgid "Search…"
3484+msgstr ""
3485+
3486+#: qml/Launcher/Drawer.qml:101
3487+msgctxt "Apps sorted alphabetically"
3488+msgid "A-Z"
3489+msgstr ""
3490+
3491+#: qml/Launcher/MoreAppsHeader.qml:32
3492+msgid "More apps in the store"
3493+msgstr ""
3494+
3495 #: qml/Notifications/NotificationMenuItemFactory.qml:124
3496 msgid "Show password"
3497 msgstr ""
3498@@ -477,62 +514,62 @@
3499 msgid "Conference"
3500 msgstr ""
3501
3502-#: qml/Panel/Indicators/MenuItemFactory.qml:847
3503+#: qml/Panel/Indicators/MenuItemFactory.qml:868
3504 msgid "Nothing is playing"
3505 msgstr ""
3506
3507-#: qml/Panel/Indicators/MenuItemFactory.qml:975
3508+#: qml/Panel/Indicators/MenuItemFactory.qml:996
3509 #, qt-format
3510 msgid "%1 hour"
3511 msgid_plural "%1 hours"
3512 msgstr[0] ""
3513 msgstr[1] ""
3514
3515-#: qml/Panel/Indicators/MenuItemFactory.qml:979
3516+#: qml/Panel/Indicators/MenuItemFactory.qml:1000
3517 #, qt-format
3518 msgid "%1 minute"
3519 msgid_plural "%1 minutes"
3520 msgstr[0] ""
3521 msgstr[1] ""
3522
3523-#: qml/Panel/Indicators/MenuItemFactory.qml:984
3524+#: qml/Panel/Indicators/MenuItemFactory.qml:1005
3525 #, qt-format
3526 msgid "%1 second"
3527 msgid_plural "%1 seconds"
3528 msgstr[0] ""
3529 msgstr[1] ""
3530
3531-#: qml/Panel/Indicators/MenuItemFactory.qml:987
3532+#: qml/Panel/Indicators/MenuItemFactory.qml:1008
3533 msgid "0 seconds"
3534 msgstr ""
3535
3536 #. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
3537-#: qml/Panel/Indicators/MenuItemFactory.qml:989
3538+#: qml/Panel/Indicators/MenuItemFactory.qml:1010
3539 #, qt-format
3540 msgid "%1 remaining"
3541 msgstr ""
3542
3543-#: qml/Panel/Indicators/MenuItemFactory.qml:995
3544+#: qml/Panel/Indicators/MenuItemFactory.qml:1016
3545 msgid "In queue…"
3546 msgstr ""
3547
3548-#: qml/Panel/Indicators/MenuItemFactory.qml:999
3549+#: qml/Panel/Indicators/MenuItemFactory.qml:1020
3550 msgid "Downloading"
3551 msgstr ""
3552
3553-#: qml/Panel/Indicators/MenuItemFactory.qml:1001
3554+#: qml/Panel/Indicators/MenuItemFactory.qml:1022
3555 msgid "Paused, tap to resume"
3556 msgstr ""
3557
3558-#: qml/Panel/Indicators/MenuItemFactory.qml:1003
3559+#: qml/Panel/Indicators/MenuItemFactory.qml:1024
3560 msgid "Canceled"
3561 msgstr ""
3562
3563-#: qml/Panel/Indicators/MenuItemFactory.qml:1005
3564+#: qml/Panel/Indicators/MenuItemFactory.qml:1026
3565 msgid "Finished"
3566 msgstr ""
3567
3568-#: qml/Panel/Indicators/MenuItemFactory.qml:1007
3569+#: qml/Panel/Indicators/MenuItemFactory.qml:1028
3570 msgid "Failed, tap to retry"
3571 msgstr ""
3572
3573
3574=== modified file 'qml/Components/InputMethod.qml'
3575--- qml/Components/InputMethod.qml 2016-10-06 13:06:31 +0000
3576+++ qml/Components/InputMethod.qml 2016-12-12 16:49:02 +0000
3577@@ -23,6 +23,8 @@
3578
3579 readonly property rect visibleRect: surfaceItem.surface && visible ? surfaceItem.surface.inputBounds : Qt.rect(0, 0, 0, 0)
3580
3581+ property var surface
3582+
3583 MirSurfaceItem {
3584 id: surfaceItem
3585 anchors.fill: parent
3586@@ -31,7 +33,7 @@
3587
3588 surfaceWidth: root.enabled ? width : -1
3589 surfaceHeight: root.enabled ? height : -1
3590- surface: SurfaceManager.inputMethodSurface
3591+ surface: root.surface
3592
3593 onLiveChanged: {
3594 if (surface !== null && !live) {
3595@@ -50,7 +52,6 @@
3596 }
3597
3598 visible: surfaceItem.surface &&
3599- surfaceItem.surfaceState != Mir.HiddenState &&
3600- surfaceItem.surfaceState != Mir.MinimizedState &&
3601- root.enabled
3602+ surfaceItem.surface.visible &&
3603+ root.enabled
3604 }
3605
3606=== modified file 'qml/Components/KeyboardShortcutsOverlay.qml'
3607--- qml/Components/KeyboardShortcutsOverlay.qml 2016-05-11 11:09:42 +0000
3608+++ qml/Components/KeyboardShortcutsOverlay.qml 2016-12-12 16:49:02 +0000
3609@@ -173,6 +173,19 @@
3610 Layout.maximumWidth: maxTextSize
3611 }
3612
3613+ Label {
3614+ text: i18n.tr("Super + A")
3615+ fontSize: "small"
3616+ font.weight: Font.Medium
3617+ }
3618+ Label {
3619+ text: i18n.tr("Opens the Application Drawer.")
3620+ fontSize: "small"
3621+ font.weight: Font.Light
3622+ wrapMode: Text.Wrap
3623+ Layout.maximumWidth: maxTextSize
3624+ }
3625+
3626
3627 // Scopes section
3628 Item { Layout.columnSpan: 2; height: units.gu(2) }
3629
3630=== modified file 'qml/Components/KeymapSwitcher.qml'
3631--- qml/Components/KeymapSwitcher.qml 2016-07-11 14:40:56 +0000
3632+++ qml/Components/KeymapSwitcher.qml 2016-12-12 16:49:02 +0000
3633@@ -23,6 +23,9 @@
3634 QtObject {
3635 id: root
3636
3637+ // to be set from outside
3638+ property var focusedSurface: null
3639+
3640 property GlobalShortcut shortcutNext: GlobalShortcut {
3641 shortcut: Qt.MetaModifier|Qt.Key_Space
3642 onTriggered: root.nextKeymap()
3643@@ -60,7 +63,7 @@
3644 }
3645
3646 property Binding surfaceKeymapBinding: Binding {
3647- target: MirFocusController.focusedSurface
3648+ target: root.focusedSurface
3649 property: "keymap"
3650 value: root.currentKeymap
3651 }
3652
3653=== modified file 'qml/Components/VirtualTouchPad.qml'
3654--- qml/Components/VirtualTouchPad.qml 2016-02-18 17:21:40 +0000
3655+++ qml/Components/VirtualTouchPad.qml 2016-12-12 16:49:02 +0000
3656@@ -15,20 +15,58 @@
3657 */
3658
3659 import QtQuick 2.4
3660+import QtQuick.Layouts 1.1
3661 import Ubuntu.Components 1.3
3662+import Qt.labs.settings 1.0
3663+import Unity.Screens 0.1
3664 import UInput 0.1
3665+import "../Components"
3666
3667 Item {
3668+ id: root
3669 property var uinput: UInput {
3670 Component.onCompleted: createMouse();
3671 Component.onDestruction: removeMouse();
3672 }
3673
3674- readonly property bool pressed: point1.pressed || point2.pressed
3675+ Component.onCompleted: {
3676+ if (!settings.touchpadTutorialHasRun) {
3677+ root.runTutorial()
3678+ }
3679+ }
3680+
3681+ function runTutorial() {
3682+ // If the tutorial animation is started too early, e.g. in Component.onCompleted,
3683+ // root width & height might be reported as 0x0 still. As animations read their
3684+ // values at startup and won't update them, lets make sure to only start once
3685+ // we have some actual size.
3686+ if (root.width > 0 && root.height > 0) {
3687+ tutorial.start();
3688+ } else {
3689+ tutorialTimer.start();
3690+ }
3691+ }
3692+
3693+ Timer {
3694+ id: tutorialTimer
3695+ interval: 50
3696+ repeat: false
3697+ running: false
3698+ onTriggered: root.runTutorial();
3699+ }
3700+
3701+ readonly property bool pressed: point1.pressed || point2.pressed || leftButton.pressed || rightButton.pressed
3702+
3703+ property var settings: Settings {
3704+ objectName: "virtualTouchPadSettings"
3705+ property bool touchpadTutorialHasRun: false
3706+ property bool oskEnabled: true
3707+ }
3708
3709 MultiPointTouchArea {
3710 objectName: "touchPadArea"
3711 anchors.fill: parent
3712+ enabled: !tutorial.running || tutorial.paused
3713
3714 // FIXME: Once we have Qt DPR support, this should be Qt.styleHints.startDragDistance
3715 readonly property int clickThreshold: internalGu * 1.5
3716@@ -37,6 +75,11 @@
3717 property bool isDrag: false
3718
3719 onPressed: {
3720+ if (tutorial.paused) {
3721+ tutorial.resume();
3722+ return;
3723+ }
3724+
3725 // If double-tapping *really* fast, it could happen that we end up having only point2 pressed
3726 // Make sure we check for both combos, only point1 or only point2
3727 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
3728@@ -135,4 +178,229 @@
3729 }
3730 ]
3731 }
3732+
3733+ RowLayout {
3734+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom; margins: -internalGu * 1 }
3735+ height: internalGu * 10
3736+ spacing: internalGu * 1
3737+
3738+ MouseArea {
3739+ id: leftButton
3740+ objectName: "leftButton"
3741+ Layout.fillWidth: true
3742+ Layout.fillHeight: true
3743+ onPressed: uinput.pressMouse(UInput.ButtonLeft);
3744+ onReleased: uinput.releaseMouse(UInput.ButtonLeft);
3745+ property bool highlight: false
3746+ UbuntuShape {
3747+ anchors.fill: parent
3748+ backgroundColor: leftButton.highlight || leftButton.pressed ? UbuntuColors.ash : UbuntuColors.inkstone
3749+ Behavior on backgroundColor { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
3750+ }
3751+ }
3752+
3753+ MouseArea {
3754+ id: rightButton
3755+ objectName: "rightButton"
3756+ Layout.fillWidth: true
3757+ Layout.fillHeight: true
3758+ onPressed: uinput.pressMouse(UInput.ButtonRight);
3759+ onReleased: uinput.releaseMouse(UInput.ButtonRight);
3760+ property bool highlight: false
3761+ UbuntuShape {
3762+ anchors.fill: parent
3763+ backgroundColor: rightButton.highlight || rightButton.pressed ? UbuntuColors.ash : UbuntuColors.inkstone
3764+ Behavior on backgroundColor { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
3765+ }
3766+ }
3767+ }
3768+
3769+ AbstractButton {
3770+ id: oskButton
3771+ objectName: "oskButton"
3772+ anchors { right: parent.right; top: parent.top; margins: internalGu * 2 }
3773+ height: internalGu * 6
3774+ width: height
3775+
3776+ onClicked: {
3777+ settings.oskEnabled = !settings.oskEnabled
3778+ }
3779+
3780+ Rectangle {
3781+ anchors.fill: parent
3782+ radius: width / 2
3783+ color: UbuntuColors.inkstone
3784+ }
3785+
3786+ Icon {
3787+ anchors.fill: parent
3788+ anchors.margins: internalGu * 1.5
3789+ name: "input-keyboard-symbolic"
3790+ }
3791+ }
3792+
3793+ Screens {
3794+ id: screens
3795+ }
3796+
3797+ InputMethod {
3798+ id: inputMethod
3799+ // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
3800+ enabled: screens.count > 1 && settings.oskEnabled && !tutorial.running
3801+ objectName: "inputMethod"
3802+ anchors.fill: parent
3803+ }
3804+
3805+ Label {
3806+ id: tutorialLabel
3807+ objectName: "tutorialLabel"
3808+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: internalGu * 4; topMargin: internalGu * 10 }
3809+ opacity: 0
3810+ visible: opacity > 0
3811+ font.pixelSize: 2 * internalGu
3812+ color: "white"
3813+ wrapMode: Text.WordWrap
3814+ }
3815+
3816+ Icon {
3817+ id: tutorialImage
3818+ objectName: "tutorialImage"
3819+ height: internalGu * 8
3820+ width: height
3821+ name: "input-touchpad-symbolic"
3822+ color: "white"
3823+ opacity: 0
3824+ visible: opacity > 0
3825+ anchors { top: tutorialLabel.bottom; horizontalCenter: parent.horizontalCenter; margins: internalGu * 2 }
3826+ }
3827+
3828+ Item {
3829+ id: tutorialFinger1
3830+ objectName: "tutorialFinger1"
3831+ width: internalGu * 5
3832+ height: width
3833+ property real scale: 1
3834+ opacity: 0
3835+ visible: opacity > 0
3836+ Rectangle {
3837+ width: parent.width * parent.scale
3838+ height: width
3839+ anchors.centerIn: parent
3840+ radius: width / 2
3841+ color: UbuntuColors.inkstone
3842+ }
3843+ }
3844+
3845+ Item {
3846+ id: tutorialFinger2
3847+ objectName: "tutorialFinger2"
3848+ width: internalGu * 5
3849+ height: width
3850+ property real scale: 1
3851+ opacity: 0
3852+ visible: opacity > 0
3853+ Rectangle {
3854+ width: parent.width * parent.scale
3855+ height: width
3856+ anchors.centerIn: parent
3857+ radius: width / 2
3858+ color: UbuntuColors.inkstone
3859+ }
3860+ }
3861+
3862+ SequentialAnimation {
3863+ id: tutorial
3864+ objectName: "tutorialAnimation"
3865+
3866+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: false }
3867+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "opacity"; value: 0 }
3868+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.") }
3869+ UbuntuNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3870+ PropertyAction { target: tutorial; property: "paused"; value: true }
3871+ PauseAnimation { duration: 500 } // it takes a bit until pausing actually takes effect
3872+ UbuntuNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3873+
3874+ UbuntuNumberAnimation { target: leftButton; property: "opacity"; to: 1 }
3875+ UbuntuNumberAnimation { target: rightButton; property: "opacity"; to: 1 }
3876+
3877+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3878+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap left button to click.") }
3879+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3880+ SequentialAnimation {
3881+ loops: 2
3882+ PropertyAction { target: leftButton; property: "highlight"; value: true }
3883+ PauseAnimation { duration: UbuntuAnimation.FastDuration }
3884+ PropertyAction { target: leftButton; property: "highlight"; value: false }
3885+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3886+ }
3887+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3888+
3889+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3890+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap right button to right click.") }
3891+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3892+ SequentialAnimation {
3893+ loops: 2
3894+ PropertyAction { target: rightButton; property: "highlight"; value: true }
3895+ PauseAnimation { duration: UbuntuAnimation.FastDuration }
3896+ PropertyAction { target: rightButton; property: "highlight"; value: false }
3897+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3898+ }
3899+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3900+
3901+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3902+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Swipe with two fingers to scroll.") }
3903+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3904+ PropertyAction { target: tutorialFinger1; property: "x"; value: root.width / 2 - tutorialFinger1.width - internalGu * 1 }
3905+ PropertyAction { target: tutorialFinger2; property: "x"; value: root.width / 2 + tutorialFinger1.width + internalGu * 1 - tutorialFinger2.width }
3906+ PropertyAction { target: tutorialFinger1; property: "y"; value: root.height / 2 - internalGu * 10 }
3907+ PropertyAction { target: tutorialFinger2; property: "y"; value: root.height / 2 - internalGu * 10 }
3908+ SequentialAnimation {
3909+ ParallelAnimation {
3910+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3911+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3912+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
3913+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
3914+ }
3915+ ParallelAnimation {
3916+ UbuntuNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 + internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
3917+ UbuntuNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 + internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
3918+ }
3919+ ParallelAnimation {
3920+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3921+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3922+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
3923+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
3924+ }
3925+ PauseAnimation { duration: UbuntuAnimation.SlowDuration }
3926+ ParallelAnimation {
3927+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3928+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3929+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
3930+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: UbuntuAnimation.FastDuration }
3931+ }
3932+ ParallelAnimation {
3933+ UbuntuNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 - internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
3934+ UbuntuNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 - internalGu * 10; duration: UbuntuAnimation.SleepyDuration }
3935+ }
3936+ ParallelAnimation {
3937+ UbuntuNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3938+ UbuntuNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3939+ UbuntuNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
3940+ UbuntuNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: UbuntuAnimation.FastDuration }
3941+ }
3942+ PauseAnimation { duration: UbuntuAnimation.SlowDuration }
3943+ }
3944+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3945+
3946+ PauseAnimation { duration: UbuntuAnimation.SleepyDuration }
3947+ PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Find more settings in the system settings.") }
3948+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: UbuntuAnimation.FastDuration }
3949+ PauseAnimation { duration: 2000 }
3950+ UbuntuNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: UbuntuAnimation.FastDuration }
3951+
3952+ UbuntuNumberAnimation { target: oskButton; property: "opacity"; to: 1 }
3953+ PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: true }
3954+
3955+ PropertyAction { target: settings; property: "touchpadTutorialHasRun"; value: true }
3956+ }
3957 }
3958
3959=== modified file 'qml/Dash/Previews/PreviewAudioPlayback.qml'
3960--- qml/Dash/Previews/PreviewAudioPlayback.qml 2016-11-03 12:06:32 +0000
3961+++ qml/Dash/Previews/PreviewAudioPlayback.qml 2016-12-12 16:49:02 +0000
3962@@ -70,6 +70,7 @@
3963 objectName: "playButton"
3964 width: trackRow.column1Width
3965 height: width
3966+ activeFocusOnPress: false
3967
3968 onClicked: {
3969 if (trackItem.isPlayingItem) {
3970
3971=== modified file 'qml/DisabledScreenNotice.qml'
3972--- qml/DisabledScreenNotice.qml 2016-05-06 14:29:25 +0000
3973+++ qml/DisabledScreenNotice.qml 2016-12-12 16:49:02 +0000
3974@@ -18,15 +18,12 @@
3975 import QtQuick.Layouts 1.1
3976 import Ubuntu.Components 1.3
3977 import Unity.Session 0.1
3978-import Unity.Screens 0.1
3979 import QtQuick.Window 2.2
3980 import "Components"
3981
3982 Item {
3983 id: root
3984
3985- property bool infoNoteDisplayed: true
3986-
3987 // For testing
3988 property var screen: Screen
3989 property var orientationLock: OrientationLock
3990@@ -36,10 +33,6 @@
3991 name: applicationArguments.deviceName
3992 }
3993
3994- Screens {
3995- id: screens
3996- }
3997-
3998 Item {
3999 id: contentContainer
4000 objectName: "contentContainer"
4001@@ -75,58 +68,13 @@
4002 }
4003 transformOrigin: Item.Center
4004
4005+ Rectangle {
4006+ anchors.fill: parent
4007+ color: UbuntuColors.jet
4008+ }
4009+
4010 VirtualTouchPad {
4011- anchors.fill: parent
4012-
4013- onPressedChanged: {
4014- if (pressed && infoNoteDisplayed) {
4015- infoNoteDisplayed = false;
4016- }
4017- }
4018- }
4019-
4020- Rectangle {
4021- anchors.fill: parent
4022- color: "#3b3b3b"
4023- }
4024-
4025- Item {
4026- objectName: "infoNoticeArea"
4027- anchors.fill: parent
4028- opacity: infoNoteDisplayed ? 1 : 0
4029- visible: opacity > 0
4030- Behavior on opacity {
4031- UbuntuNumberAnimation { }
4032- }
4033-
4034- Column {
4035- anchors.centerIn: parent
4036- width: parent.width - (internalGu * 8)
4037- spacing: internalGu * 4
4038-
4039- Label {
4040- id: text
4041- text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.")
4042- color: "white"
4043- width: parent.width
4044- font.pixelSize: 2.5 * internalGu
4045- wrapMode: Text.Wrap
4046- }
4047- Icon {
4048- height: internalGu * 8
4049- width: height
4050- name: "input-touchpad-symbolic"
4051- color: "white"
4052- anchors.horizontalCenter: parent.horizontalCenter
4053- }
4054- }
4055- }
4056-
4057- InputMethod {
4058- id: inputMethod
4059- // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
4060- enabled: screens.count > 1
4061- objectName: "inputMethod"
4062+ objectName: "virtualTouchPad"
4063 anchors.fill: parent
4064 }
4065 }
4066
4067=== modified file 'qml/Greeter/Greeter.qml'
4068--- qml/Greeter/Greeter.qml 2016-09-22 10:33:39 +0000
4069+++ qml/Greeter/Greeter.qml 2016-12-12 16:49:02 +0000
4070@@ -145,11 +145,7 @@
4071 }
4072 }
4073
4074- if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
4075- return LightDMService.greeter.defaultSession;
4076- } else {
4077- return "ubuntu"; // The default / fallback
4078- }
4079+ return LightDMService.greeter.defaultSession;
4080 }
4081
4082 QtObject {
4083
4084=== modified file 'qml/Greeter/LoginList.qml'
4085--- qml/Greeter/LoginList.qml 2016-09-22 10:33:39 +0000
4086+++ qml/Greeter/LoginList.qml 2016-12-12 16:49:02 +0000
4087@@ -165,7 +165,7 @@
4088 }
4089
4090 delegate: Item {
4091- width: parent.width
4092+ width: userList.width
4093 height: root.cellHeight
4094
4095 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
4096
4097=== modified file 'qml/Greeter/WideView.qml'
4098--- qml/Greeter/WideView.qml 2016-09-07 17:13:38 +0000
4099+++ qml/Greeter/WideView.qml 2016-12-12 16:49:02 +0000
4100@@ -125,6 +125,8 @@
4101 id: loginList
4102 objectName: "loginList"
4103
4104+ property int selectedUserIndex: 0
4105+
4106 width: units.gu(40)
4107 anchors {
4108 left: parent.left
4109@@ -139,9 +141,12 @@
4110 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
4111
4112 model: root.userModel
4113- currentSession: LightDMService.greeter.defaultSession
4114+ currentSession: LightDMService.users.data(selectedUserIndex, LightDMService.userRoles.SessionRole);
4115 onResponded: root.responded(response)
4116- onSelected: root.selected(index)
4117+ onSelected: {
4118+ root.selected(index)
4119+ loginList.selectedUserIndex = index;
4120+ }
4121 onSessionChooserButtonClicked: parent.state = "SessionsList"
4122
4123 Keys.forwardTo: [sessionChooserLoader.item]
4124
4125=== added file 'qml/Launcher/BackgroundBlur.qml'
4126--- qml/Launcher/BackgroundBlur.qml 1970-01-01 00:00:00 +0000
4127+++ qml/Launcher/BackgroundBlur.qml 2016-12-12 16:49:02 +0000
4128@@ -0,0 +1,81 @@
4129+/*
4130+ * Copyright (C) 2016 Canonical, Ltd.
4131+ *
4132+ * This program is free software; you can redistribute it and/or modify
4133+ * it under the terms of the GNU General Public License as published by
4134+ * the Free Software Foundation; version 3.
4135+ *
4136+ * This program is distributed in the hope that it will be useful,
4137+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4138+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4139+ * GNU General Public License for more details.
4140+ *
4141+ * You should have received a copy of the GNU General Public License
4142+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4143+ */
4144+
4145+import QtQuick 2.4
4146+import Ubuntu.Components 1.3
4147+import QtGraphicalEffects 1.0
4148+
4149+Item {
4150+ id: root
4151+
4152+ property int blurAmount: 32
4153+ property Item sourceItem
4154+ property rect blurRect: Qt.rect(0,0,0,0)
4155+ property alias cached: fastBlur.cached
4156+
4157+ Rectangle {
4158+ id: blurMask
4159+ color: "yellow"
4160+ x: blurRect.x
4161+ y: blurRect.y
4162+ width: blurRect.width
4163+ height: blurRect.height
4164+ }
4165+
4166+ ShaderEffect {
4167+ id: maskedBlurEffect
4168+ x: blurRect.x
4169+ y: blurRect.y
4170+ width: blurRect.width
4171+ height: blurRect.height
4172+
4173+ property variant source: ShaderEffectSource {
4174+ id: shaderEffectSource
4175+ sourceItem: root.sourceItem
4176+ hideSource: false
4177+ sourceRect: root.blurRect
4178+ }
4179+
4180+ property var mask: ShaderEffectSource {
4181+ sourceItem: blurMask
4182+ hideSource: true
4183+ }
4184+
4185+ fragmentShader: "
4186+ varying highp vec2 qt_TexCoord0;
4187+ uniform sampler2D source;
4188+ uniform sampler2D mask;
4189+ void main(void)
4190+ {
4191+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
4192+ highp vec4 maskColor = texture2D(mask, qt_TexCoord0);
4193+
4194+ sourceColor *= maskColor.a;
4195+
4196+ gl_FragColor = sourceColor;
4197+ }"
4198+ }
4199+
4200+ FastBlur {
4201+ id: fastBlur
4202+ x: blurRect.x
4203+ y: blurRect.y
4204+ width: blurRect.width
4205+ height: blurRect.height
4206+ source: maskedBlurEffect
4207+ radius: Math.min(blurAmount, 128)
4208+ }
4209+ }
4210
4211=== added file 'qml/Launcher/Drawer.qml'
4212--- qml/Launcher/Drawer.qml 1970-01-01 00:00:00 +0000
4213+++ qml/Launcher/Drawer.qml 2016-12-12 16:49:02 +0000
4214@@ -0,0 +1,306 @@
4215+/*
4216+ * Copyright (C) 2016 Canonical, Ltd.
4217+ *
4218+ * This program is free software; you can redistribute it and/or modify
4219+ * it under the terms of the GNU General Public License as published by
4220+ * the Free Software Foundation; version 3.
4221+ *
4222+ * This program is distributed in the hope that it will be useful,
4223+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4224+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4225+ * GNU General Public License for more details.
4226+ *
4227+ * You should have received a copy of the GNU General Public License
4228+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4229+ */
4230+
4231+import QtQuick 2.4
4232+import Ubuntu.Components 1.3
4233+import Unity.Launcher 0.1
4234+import Utils 0.1
4235+import "../Components"
4236+import Qt.labs.settings 1.0
4237+
4238+FocusScope {
4239+ id: root
4240+
4241+ property int panelWidth: 0
4242+ readonly property bool moving: listLoader.item && listLoader.item.moving
4243+
4244+ signal applicationSelected(string appId)
4245+
4246+ property bool draggingHorizontally: false
4247+ property int dragDistance: 0
4248+
4249+ onFocusChanged: {
4250+ if (focus) {
4251+ searchField.selectAll();
4252+ }
4253+ }
4254+
4255+ function focusInput() {
4256+ searchField.selectAll();
4257+ searchField.focus = true;
4258+ }
4259+
4260+ Settings {
4261+ property alias selectedTab: sections.selectedIndex
4262+ }
4263+
4264+ MouseArea {
4265+ anchors.fill: parent
4266+ hoverEnabled: true
4267+ acceptedButtons: Qt.AllButtons
4268+ onWheel: wheel.accepted = true
4269+ }
4270+
4271+ Rectangle {
4272+ anchors.fill: parent
4273+ color: "#BF000000"
4274+
4275+ AppDrawerModel {
4276+ id: appDrawerModel
4277+ }
4278+
4279+ AppDrawerProxyModel {
4280+ id: sortProxyModel
4281+ source: appDrawerModel
4282+ filterString: searchField.displayText
4283+ sortBy: AppDrawerProxyModel.SortByAToZ
4284+ }
4285+
4286+ Item {
4287+ id: contentContainer
4288+ anchors.fill: parent
4289+ anchors.leftMargin: root.panelWidth
4290+
4291+ TextField {
4292+ id: searchField
4293+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
4294+ placeholderText: i18n.tr("Search…")
4295+ focus: true
4296+ onAccepted: {
4297+ if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
4298+ root.applicationSelected(listLoader.item.getFirstAppId());
4299+ }
4300+ }
4301+ }
4302+
4303+ Item {
4304+ id: sectionsContainer
4305+ anchors { left: parent.left; top: searchField.bottom; right: parent.right; }
4306+ height: sections.height
4307+ clip: true
4308+ z: 2
4309+
4310+ Sections {
4311+ id: sections
4312+ width: parent.width
4313+ actions: [
4314+ Action {
4315+ text: i18n.ctr("Apps sorted alphabetically", "A-Z")
4316+ // TODO: Disabling this for now as we don't get the right input from u-a-l yet.
4317+// },
4318+// Action {
4319+// text: i18n.ctr("Most used apps", "Most used")
4320+ }
4321+ ]
4322+
4323+ Rectangle {
4324+ anchors.bottom: parent.bottom
4325+ height: units.dp(1)
4326+ color: 'gray'
4327+ width: contentContainer.width
4328+ }
4329+ }
4330+ }
4331+
4332+ Loader {
4333+ id: listLoader
4334+ anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
4335+ sourceComponent: {
4336+ switch (sections.selectedIndex) {
4337+ case 0: return aToZComponent;
4338+ case 1: return mostUsedComponent;
4339+ }
4340+ }
4341+ Binding {
4342+ target: listLoader.item || null
4343+ property: "objectName"
4344+ value: "drawerItemList"
4345+ }
4346+ }
4347+
4348+ MouseArea {
4349+ parent: listLoader.item ? listLoader.item : null
4350+ anchors.fill: parent
4351+ propagateComposedEvents: true
4352+ property int oldX: 0
4353+ onPressed: {
4354+ oldX = mouseX;
4355+ }
4356+ onMouseXChanged: {
4357+ var diff = oldX - mouseX;
4358+ root.draggingHorizontally |= diff > units.gu(2);
4359+ if (!root.draggingHorizontally) {
4360+ return;
4361+ }
4362+ propagateComposedEvents = false;
4363+ parent.interactive = false;
4364+ root.dragDistance += diff;
4365+ oldX = mouseX
4366+ }
4367+ onReleased: {
4368+ if (root.draggingHorizontally) {
4369+ root.draggingHorizontally = false;
4370+ parent.interactive = true;
4371+ }
4372+ reactivateTimer.start();
4373+ }
4374+ Timer {
4375+ id: reactivateTimer
4376+ interval: 0
4377+ onTriggered: parent.propagateComposedEvents = true;
4378+ }
4379+ }
4380+
4381+ Component {
4382+ id: mostUsedComponent
4383+ DrawerListView {
4384+
4385+ header: MoreAppsHeader {
4386+ width: parent.width
4387+ height: units.gu(6)
4388+ }
4389+
4390+ model: AppDrawerProxyModel {
4391+ source: sortProxyModel
4392+ group: AppDrawerProxyModel.GroupByAll
4393+ sortBy: AppDrawerProxyModel.SortByUsage
4394+ }
4395+
4396+ delegate: UbuntuShape {
4397+ width: parent.width
4398+ color: "#20ffffff"
4399+ aspect: UbuntuShape.Flat
4400+ // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
4401+ // which messes up the ListView.
4402+ height: (Math.ceil(mostUsedGridView.model.count / mostUsedGridView.columns) * mostUsedGridView.delegateHeight) + units.gu(2)
4403+
4404+ readonly property string appId: model.appId
4405+
4406+ DrawerGridView {
4407+ id: mostUsedGridView
4408+ anchors.fill: parent
4409+ topMargin: units.gu(1)
4410+ bottomMargin: units.gu(1)
4411+ clip: true
4412+
4413+ model: sortProxyModel
4414+
4415+ delegateWidth: units.gu(8)
4416+ delegateHeight: units.gu(10)
4417+ delegate: drawerDelegateComponent
4418+ }
4419+ }
4420+ }
4421+ }
4422+
4423+ Component {
4424+ id: aToZComponent
4425+ DrawerListView {
4426+
4427+ header: MoreAppsHeader {
4428+ width: parent.width
4429+ height: units.gu(6)
4430+ }
4431+
4432+ model: AppDrawerProxyModel {
4433+ source: sortProxyModel
4434+ sortBy: AppDrawerProxyModel.SortByAToZ
4435+ group: AppDrawerProxyModel.GroupByAToZ
4436+ }
4437+
4438+ delegate: UbuntuShape {
4439+ width: parent.width
4440+ color: "#20ffffff"
4441+ aspect: UbuntuShape.Flat
4442+
4443+ readonly property string appId: model.appId
4444+
4445+ // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
4446+ // which messes up the ListView.
4447+ height: (Math.ceil(gridView.model.count / gridView.columns) * gridView.delegateHeight) +
4448+ categoryNameLabel.implicitHeight + units.gu(2)
4449+
4450+ Label {
4451+ id: categoryNameLabel
4452+ anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
4453+ text: model.letter
4454+ }
4455+
4456+ DrawerGridView {
4457+ id: gridView
4458+ anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
4459+ height: rows * delegateHeight
4460+
4461+ interactive: false
4462+
4463+ model: AppDrawerProxyModel {
4464+ id: categoryModel
4465+ source: sortProxyModel
4466+ filterLetter: model.letter
4467+ }
4468+ delegateWidth: units.gu(8)
4469+ delegateHeight: units.gu(10)
4470+ delegate: drawerDelegateComponent
4471+ }
4472+ }
4473+ }
4474+ }
4475+ }
4476+
4477+ Component {
4478+ id: drawerDelegateComponent
4479+ AbstractButton {
4480+ width: GridView.view.cellWidth
4481+ height: units.gu(10)
4482+ objectName: "drawerItem_" + model.appId
4483+
4484+ onClicked: root.applicationSelected(model.appId)
4485+
4486+ Column {
4487+ width: units.gu(8)
4488+ anchors.horizontalCenter: parent.horizontalCenter
4489+ height: childrenRect.height
4490+ spacing: units.gu(1)
4491+
4492+ UbuntuShape {
4493+ id: appIcon
4494+ width: units.gu(6)
4495+ height: 7.5 / 8 * width
4496+ anchors.horizontalCenter: parent.horizontalCenter
4497+ backgroundMode: UbuntuShape.SolidColor
4498+ backgroundColor: UbuntuColors.lightGrey
4499+ radius: "medium"
4500+ borderSource: 'undefined'
4501+ source: Image {
4502+ id: sourceImage
4503+ sourceSize.width: appIcon.width
4504+ source: model.icon
4505+ }
4506+ sourceFillMode: UbuntuShape.PreserveAspectCrop
4507+ }
4508+
4509+ Label {
4510+ text: model.name
4511+ width: parent.width
4512+ horizontalAlignment: Text.AlignHCenter
4513+ fontSize: "small"
4514+ elide: Text.ElideRight
4515+ }
4516+ }
4517+ }
4518+ }
4519+ }
4520+}
4521
4522=== added file 'qml/Launcher/DrawerGridView.qml'
4523--- qml/Launcher/DrawerGridView.qml 1970-01-01 00:00:00 +0000
4524+++ qml/Launcher/DrawerGridView.qml 2016-12-12 16:49:02 +0000
4525@@ -0,0 +1,47 @@
4526+/*
4527+ * Copyright (C) 2016 Canonical, Ltd.
4528+ *
4529+ * This program is free software; you can redistribute it and/or modify
4530+ * it under the terms of the GNU General Public License as published by
4531+ * the Free Software Foundation; version 3.
4532+ *
4533+ * This program is distributed in the hope that it will be useful,
4534+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4535+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4536+ * GNU General Public License for more details.
4537+ *
4538+ * You should have received a copy of the GNU General Public License
4539+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4540+ */
4541+
4542+import QtQuick 2.4
4543+import "../Components"
4544+
4545+Item {
4546+ id: root
4547+
4548+ property int delegateWidth: units.gu(10)
4549+ property int delegateHeight: units.gu(10)
4550+ property alias delegate: gridView.delegate
4551+ property alias model: gridView.model
4552+ property alias interactive: gridView.interactive
4553+
4554+ property alias header: gridView.header
4555+ property alias topMargin: gridView.topMargin
4556+ property alias bottomMargin: gridView.bottomMargin
4557+
4558+ readonly property int columns: width / delegateWidth
4559+ readonly property int rows: Math.ceil(gridView.model.count / root.columns)
4560+
4561+ GridView {
4562+ id: gridView
4563+ anchors.fill: parent
4564+ leftMargin: spacing
4565+
4566+ readonly property int overflow: width - (root.columns * root.delegateWidth)
4567+ readonly property real spacing: overflow / (root.columns)
4568+
4569+ cellWidth: root.delegateWidth + spacing
4570+ cellHeight: root.delegateHeight
4571+ }
4572+}
4573
4574=== added file 'qml/Launcher/DrawerListView.qml'
4575--- qml/Launcher/DrawerListView.qml 1970-01-01 00:00:00 +0000
4576+++ qml/Launcher/DrawerListView.qml 2016-12-12 16:49:02 +0000
4577@@ -0,0 +1,32 @@
4578+/*
4579+ * Copyright (C) 2016 Canonical, Ltd.
4580+ *
4581+ * This program is free software; you can redistribute it and/or modify
4582+ * it under the terms of the GNU General Public License as published by
4583+ * the Free Software Foundation; version 3.
4584+ *
4585+ * This program is distributed in the hope that it will be useful,
4586+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4587+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4588+ * GNU General Public License for more details.
4589+ *
4590+ * You should have received a copy of the GNU General Public License
4591+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4592+ */
4593+
4594+import QtQuick 2.4
4595+import Ubuntu.Components 1.3
4596+import "../Components"
4597+
4598+ListView {
4599+ id: root
4600+ anchors.fill: parent
4601+ topMargin: units.gu(1)
4602+ bottomMargin: units.gu(1)
4603+ spacing: units.gu(1)
4604+ clip: true
4605+
4606+ function getFirstAppId() {
4607+ return model.appId(0);
4608+ }
4609+}
4610
4611=== modified file 'qml/Launcher/Launcher.qml'
4612--- qml/Launcher/Launcher.qml 2016-09-22 10:14:18 +0000
4613+++ qml/Launcher/Launcher.qml 2016-12-12 16:49:02 +0000
4614@@ -19,18 +19,23 @@
4615 import Ubuntu.Components 1.3
4616 import Ubuntu.Gestures 0.1
4617 import Unity.Launcher 0.1
4618+import Utils 0.1 as Utils
4619
4620 FocusScope {
4621 id: root
4622
4623+ readonly property int ignoreHideIfMouseOverLauncher: 1
4624+
4625 property bool autohideEnabled: false
4626 property bool lockedVisible: false
4627 property bool available: true // can be used to disable all interactions
4628 property alias inverted: panel.inverted
4629+ property Item blurSource: null
4630+ property int topPanelHeight: 0
4631+ property bool drawerEnabled: true
4632
4633 property int panelWidth: units.gu(10)
4634 property int dragAreaWidth: units.gu(1)
4635- property int minimizeDistance: units.gu(26)
4636 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
4637 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
4638
4639@@ -43,13 +48,11 @@
4640 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
4641
4642 readonly property bool shown: panel.x > -panel.width
4643+ readonly property bool drawerShown: drawer.x == 0
4644
4645 // emitted when an application is selected
4646 signal launcherApplicationSelected(string appId)
4647
4648- // emitted when the apps dash should be shown because of a swipe gesture
4649- signal dash()
4650-
4651 // emitted when the dash icon in the launcher has been tapped
4652 signal showDashHome()
4653
4654@@ -62,6 +65,9 @@
4655 }
4656
4657 onSuperPressedChanged: {
4658+ if (state == "drawer")
4659+ return;
4660+
4661 if (superPressed) {
4662 superPressTimer.start();
4663 superLongPressTimer.start();
4664@@ -102,7 +108,10 @@
4665 }
4666 }
4667
4668- function hide() {
4669+ function hide(flags) {
4670+ if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
4671+ return;
4672+ }
4673 switchToNextState("")
4674 }
4675
4676@@ -132,7 +141,7 @@
4677 }
4678
4679 function pushEdge(amount) {
4680- if (root.state === "") {
4681+ if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
4682 edgeBarrier.push(amount);
4683 }
4684 }
4685@@ -143,6 +152,22 @@
4686 switchToNextState("visible")
4687 }
4688
4689+ function openDrawer(focusInputField) {
4690+ if (!drawerEnabled) {
4691+ return;
4692+ }
4693+
4694+ panel.shortcutHintsShown = false;
4695+ superPressTimer.stop();
4696+ superLongPressTimer.stop();
4697+ root.focus = true;
4698+ drawer.focus = true;
4699+ if (focusInputField) {
4700+ drawer.focusInput();
4701+ }
4702+ switchToNextState("drawer")
4703+ }
4704+
4705 Keys.onPressed: {
4706 switch (event.key) {
4707 case Qt.Key_Backtab:
4708@@ -278,7 +303,7 @@
4709 InverseMouseArea {
4710 id: closeMouseArea
4711 anchors.fill: panel
4712- enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
4713+ enabled: root.state == "visible" || root.state == "drawer"
4714 visible: enabled
4715 onPressed: {
4716 mouse.accepted = false;
4717@@ -307,23 +332,61 @@
4718 }
4719 }
4720
4721- EdgeBarrier {
4722- id: edgeBarrier
4723- edge: Qt.LeftEdge
4724- target: parent
4725- enabled: root.available
4726- onPassed: { root.switchToNextState("visibleTemporary"); }
4727- material: Component {
4728- Item {
4729- Rectangle {
4730- width: parent.height
4731- height: parent.width
4732- rotation: -90
4733- anchors.centerIn: parent
4734- gradient: Gradient {
4735- GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
4736- GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
4737- }
4738+ BackgroundBlur {
4739+ id: backgroundBlur
4740+ anchors.fill: parent
4741+ anchors.topMargin: root.inverted ? 0 : -root.topPanelHeight
4742+ visible: root.blurSource && drawer.x > -drawer.width
4743+ blurAmount: units.gu(6)
4744+ sourceItem: root.blurSource
4745+ blurRect: Qt.rect(panel.width,
4746+ root.topPanelHeight,
4747+ drawer.width + drawer.x - panel.width,
4748+ height - root.topPanelHeight)
4749+ cached: drawer.moving
4750+ }
4751+
4752+ Drawer {
4753+ id: drawer
4754+ objectName: "drawer"
4755+ anchors {
4756+ top: parent.top
4757+ topMargin: root.inverted ? root.topPanelHeight : 0
4758+ bottom: parent.bottom
4759+ right: parent.left
4760+ }
4761+ width: Math.min(root.width, units.gu(90)) * .9
4762+ panelWidth: panel.width
4763+ visible: x > -width
4764+
4765+ Behavior on anchors.rightMargin {
4766+ enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate && !drawer.draggingHorizontally
4767+ NumberAnimation {
4768+ duration: 300
4769+ easing.type: Easing.OutCubic
4770+ }
4771+ }
4772+
4773+ onApplicationSelected: {
4774+ root.hide();
4775+ root.launcherApplicationSelected(appId)
4776+ root.focus = false;
4777+ }
4778+
4779+ Keys.onEscapePressed: {
4780+ switchToNextState("");
4781+ root.focus = false;
4782+ }
4783+
4784+ onDragDistanceChanged: {
4785+ anchors.rightMargin = Math.max(-drawer.width, anchors.rightMargin + dragDistance);
4786+ }
4787+ onDraggingHorizontallyChanged: {
4788+ if (!draggingHorizontally) {
4789+ if (drawer.x < -units.gu(10)) {
4790+ root.hide();
4791+ } else {
4792+ root.openDrawer();
4793 }
4794 }
4795 }
4796@@ -332,7 +395,7 @@
4797 LauncherPanel {
4798 id: panel
4799 objectName: "launcherPanel"
4800- enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
4801+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
4802 width: root.panelWidth
4803 anchors {
4804 top: parent.top
4805@@ -359,11 +422,11 @@
4806 property bool animate: true
4807
4808 onApplicationSelected: {
4809- root.hide();
4810+ root.hide(ignoreHideIfMouseOverLauncher);
4811 launcherApplicationSelected(appId)
4812 }
4813 onShowDashHome: {
4814- root.hide();
4815+ root.hide(ignoreHideIfMouseOverLauncher);
4816 root.showDashHome();
4817 }
4818
4819@@ -394,6 +457,38 @@
4820 }
4821 }
4822
4823+ EdgeBarrier {
4824+ id: edgeBarrier
4825+ edge: Qt.LeftEdge
4826+ target: parent
4827+ enabled: root.available
4828+ onProgressChanged: {
4829+ if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
4830+ root.switchToNextState("visibleTemporary");
4831+ }
4832+ }
4833+ onPassed: {
4834+ if (root.drawerEnabled) {
4835+ root.switchToNextState("drawer");
4836+ }
4837+ }
4838+
4839+ material: Component {
4840+ Item {
4841+ Rectangle {
4842+ width: parent.height
4843+ height: parent.width
4844+ rotation: -90
4845+ anchors.centerIn: parent
4846+ gradient: Gradient {
4847+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
4848+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
4849+ }
4850+ }
4851+ }
4852+ }
4853+ }
4854+
4855 SwipeArea {
4856 id: dragArea
4857 objectName: "launcherDragArea"
4858@@ -405,25 +500,62 @@
4859 width: root.dragAreaWidth
4860 height: root.height
4861
4862+ function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
4863+
4864+ property var lastDragPoints: []
4865+
4866+ function dragDirection() {
4867+ if (lastDragPoints.length < 5) {
4868+ return "unknown";
4869+ }
4870+
4871+ var toRight = true;
4872+ var toLeft = true;
4873+ for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
4874+ if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
4875+ toRight = false;
4876+ }
4877+ if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
4878+ toLeft = false;
4879+ }
4880+ }
4881+ return toRight ? "right" : toLeft ? "left" : "unknown";
4882+ }
4883+
4884 onDistanceChanged: {
4885- if (!dragging || launcher.state == "visible")
4886- return;
4887+ if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
4888+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
4889+ }
4890
4891- panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
4892+ if (root.drawerEnabled && dragging && launcher.state != "drawer") {
4893+ lastDragPoints.push(distance)
4894+ var drawerHintDistance = panel.width + units.gu(1)
4895+ if (distance < drawerHintDistance) {
4896+ drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
4897+ } else {
4898+ var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
4899+ var linearDrawerProgress = linearDrawerX / (drawer.width)
4900+ var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
4901+ drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
4902+ }
4903+ }
4904 }
4905
4906 onDraggingChanged: {
4907 if (!dragging) {
4908 if (distance > panel.width / 2) {
4909- root.switchToNextState("visible")
4910- if (distance > minimizeDistance) {
4911- root.dash();
4912+ if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
4913+ root.switchToNextState("drawer");
4914+ root.focus = true;
4915+ } else {
4916+ root.switchToNextState("visible");
4917 }
4918 } else if (root.state === "") {
4919 // didn't drag far enough. rollback
4920- root.switchToNextState("")
4921+ root.switchToNextState("");
4922 }
4923 }
4924+ lastDragPoints = [];
4925 }
4926 }
4927
4928@@ -434,6 +566,10 @@
4929 target: panel
4930 x: -root.panelWidth
4931 }
4932+ PropertyChanges {
4933+ target: drawer
4934+ anchors.rightMargin: 0
4935+ }
4936