Merge lp:~mterry/unity8/greeter-refactor into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Daniel d'Andrada
Approved revision: 1507
Merged at revision: 1622
Proposed branch: lp:~mterry/unity8/greeter-refactor
Merge into: lp:unity8
Prerequisite: lp:~mterry/unity8/tutorial-new-screens
Diff against target: 5614 lines (+2954/-1668)
28 files modified
cmake/modules/QmlTest.cmake (+1/-1)
qml/Components/DelayedLockscreen.qml (+3/-1)
qml/Components/PassphraseLockscreen.qml (+2/-3)
qml/Components/PinLockscreen.qml (+3/-4)
qml/Greeter/CoverPage.qml (+163/-110)
qml/Greeter/Greeter.qml (+358/-186)
qml/Greeter/Infographics.qml (+1/-0)
qml/Greeter/LoginList.qml (+70/-88)
qml/Greeter/NarrowView.qml (+170/-0)
qml/Greeter/WideView.qml (+134/-0)
qml/Shell.qml (+78/-364)
tests/autopilot/unity8/shell/emulators/greeter.py (+19/-3)
tests/autopilot/unity8/shell/emulators/main_window.py (+0/-6)
tests/autopilot/unity8/shell/tests/__init__.py (+2/-3)
tests/autopilot/unity8/shell/tests/test_lock_screen.py (+8/-12)
tests/plugins/LightDM/CMakeLists.txt (+21/-6)
tests/plugins/LightDM/usersmodel.cpp (+84/-0)
tests/qmltests/CMakeLists.txt (+4/-3)
tests/qmltests/Greeter/TestView.qml (+99/-0)
tests/qmltests/Greeter/tst_Greeter.qml (+472/-0)
tests/qmltests/Greeter/tst_MultiGreeter.qml (+0/-475)
tests/qmltests/Greeter/tst_NarrowView.qml (+522/-0)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+0/-244)
tests/qmltests/Greeter/tst_WideView.qml (+519/-0)
tests/qmltests/Tutorial/tst_Tutorial.qml (+5/-4)
tests/qmltests/tst_Shell.qml (+65/-43)
tests/qmltests/tst_ShellWithPin.qml (+97/-48)
tests/qmltests/tst_TabletShell.qml (+54/-64)
To merge this branch: bzr merge lp:~mterry/unity8/greeter-refactor
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
Daniel d'Andrada (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Review via email: mp+248829@code.launchpad.net

This proposal supersedes a proposal from 2015-01-29.

Commit message

Refactor the greeter code to be more compartmented and isolatable.

This will help make it an optional piece of the shell when we allow a split greeter/shell.

My driving goals were:

- Remove as much logic from Shell.qml as possible. Right now, there is so much Greeter logic spread all throughout Shell.qml in different bits. For example, the Lockscreen widget sitting there instead of in qml/Greeter/.

- Clean up how we load the two different views (tablet & phone). Right now we have some widgets always present but sometimes hidden (Lockscreen) and Loaders within Loaders (LoginList inside GreeterContent). I wanted just two top-level view widgets that could be separately loaded depending on which mode we are in.

- Toward that end, I wanted to consolidate all the Greeter logic out of Shell and out of those two view classes to avoid duplication. So I made the main Greeter entry point widget handle all the login logic and interactions with LightDM. It controls the views, who always ask it to do things on their behalf. Classic MVC stuff.

- I separated out some of the widgetry from the previous Greeter class into a CoverPage class that both NarrowView and WideView use. This class now holds the drag handle for sliding the greeter away and the infographic.

- Now that each piece of the greeter has a much more brightly defined boundary, testing them in isolation is much easier. I've added tst_WideView, tst_NarrowView, and tst_Greeter files. I've moved existing tests around a bit and added some more. But it should be much more clear where a prospective test belongs now (logic tests in Greeter, visual tests in the respective View).

- I also moved some tests we had in qmluitests/ that were purely testing the LightDM plugin into their own plugin test file.

Description of the change

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

* 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.
Revision history for this message
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

I'm going to submit for review, but there is a tiny issue still with the OSK and the NarrowView passphrase interface. The OSK doesn't come back up automatically after an invalid passphrase, and the OSK stays up after a suspend.

So there is some focus issue there I'll look into. But it's good enough to review.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

About the commit message: Mind doing the "short summary on first line, empty line, rest of the explanation" format?

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

"""
1174 + * Copyright (C) 2014 Canonical, Ltd.
"""

We're past that year, man. :)

Revision history for this message
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

OK, copied "Description" into "Commit Message" and updated years. :)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in qml/Greeter/CoverPage.qml
Text conflict in qml/Greeter/Greeter.qml
Text conflict in qml/Shell.qml
Text conflict in tests/plugins/LightDM/CMakeLists.txt
Text conflict in tests/qmltests/CMakeLists.txt
Contents conflict in tests/qmltests/Greeter/tst_MultiGreeter.qml
Text conflict in tests/qmltests/tst_TabletShell.qml
7 conflicts encountered.

Revision history for this message
Michael Terry (mterry) wrote :

I rebased this MP on tutorial-new-screens / trunk, hopefully fixing all current conflicts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1485. By Michael Terry

Fix tutorial autopilot test with new greeter refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1486. By Michael Terry

Fix autopilot harder

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1487. By Michael Terry

Wait until userlist settles at beginning of TabletShell test

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

There seems to be a badly resolved merge conflict:
qml/Components/EdgeDemo.qml got renamed to qml/Components/EdgeDemo.qml.THIS

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Please update the copyright year of qml/Components/PassphraseLockscreen.qml

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I run "make tryGreeter" and got a black screen on the left side. clicking the "show greeter" button has no visible effect. Is that expected?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
=== modified file 'qml/Components/PassphraseLockscreen.qml'
--- qml/Components/PassphraseLockscreen.qml 2014-10-23 13:23:07 +0000
+++ qml/Components/PassphraseLockscreen.qml 2015-02-02 22:14:36 +0000
@@ -98,6 +98,11 @@ Item {
                 // has oddly sized password characters that don't scale right)
                 opacity: 0

+ onFocusChanged: {
+ // Within our FocusScope, we always want to have focus
+ focus = true;
+ }
+
                 // simulate being disabled, but without removing OSK focus
                 maximumLength: root.entryEnabled ? 32767 : length

"""

This looks like a desperate hack. Setting "focus: true" should suffice (along with a FocusScope surrounding the offender in the worst case, if that makes sense). Do you know who is taking away the focus from this item?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1488. By Michael Terry

Small cleanups

1489. By Michael Terry

Make fake TestView more obviously intentionally-blank

Revision history for this message
Michael Terry (mterry) wrote :

> There seems to be a badly resolved merge conflict:
> qml/Components/EdgeDemo.qml got renamed to qml/Components/EdgeDemo.qml.THIS

Fixed, whoops!

> Please update the copyright year of qml/Components/PassphraseLockscreen.qml

Fixed.

> I run "make tryGreeter" and got a black screen on the left side.
> clicking the "show greeter" button has no visible effect. Is that expected?

Yes. tst_Greeter.qml only tests the "controller" side of things -- the glue between the user visible views and AccountsService/LightDM/PAM. It has its own View class, but it's just a fake one without any real controls. I've removed the "Show Greeter" button, since that was just a cut/paste artifact. And I've added a label to the fake view class to make it clear that there is nothing to see. "This page intentionally left blank" and all. :)

If you want to play with a view class, use tryWideView and tryNarrowView. If you want to play with the Greeter + a view, use tryShell, tryShellWithPin, or tryTabletShell.

> This looks like a desperate hack. Setting "focus: true" should suffice

Yeah... You got me. It didn't suffice, and I couldn't tell why focus was being stolen. All I could tell was that activeFocus was moving to the greeter's loader itself. But not in a situation that seemingly would require it to move (e.g. it wasn't being reloaded nor was a widget disabled as far as I could tell).

Revision history for this message
Michael Terry (mterry) wrote :

(Well, I guess it's not surprising that the loader got activeFocus, I think that was just the side effect of the entry's focus being false. What was surprising was that the entry object's focus property was being set to false in the first place. Wasn't sure why.)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1490. By Michael Terry

Restore behavior animating LoginList height

1491. By Michael Terry

Merge from tutorial-new-screens

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
        Connections {
            target: ApplicationManager

            onFocusRequested: {
                [...]
                greeter.notifyAppFocused(appId);
            }

            onFocusedApplicationIdChanged: {
                greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
                [...]
            }

            onApplicationAdded: {
                [...]
                greeter.notifyAppFocused(appId);
                [...]
            }
        }
"""

All those greeter.notifyAppFocused calls look redundant and not really correct.

A focus request, as it name says, it's just a request. It will be processed by the window manager part of unity8 (eg PhoneStage or TabletStage) which will finally change the app that is focused, causing ApplicationManager.focusedApplicationId to change. Thus for every focus change request you will be calling greeter.notifyAppFocused twice. And what's worse, you're assuming that a request will always be successful and are telling greeter that a given app now has focus before it actually happens.

The same goes for the onApplicationAdded case.

Can't we have greeter.notifyAppFocused only in onFocusedApplicationIdChanged?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

>
> A focus request, as it name says, it's just a request. It will be processed by
> the window manager part of unity8 (eg PhoneStage or TabletStage) which will
> finally change the app that is focused, causing
> ApplicationManager.focusedApplicationId to change. Thus for every focus change
> request you will be calling greeter.notifyAppFocused twice. And what's worse,
> you're assuming that a request will always be successful and are telling
> greeter that a given app now has focus before it actually happens.

Unless you wanna do something also when ApplicationManager.requestFocusApplication() is called for an application that is already focused? In which case we would need some further refactoring as the current naming would be misleading.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Shell.qml:

"""
                available: tutorial.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
"""

This line is way too long.

lp:~mterry/unity8/greeter-refactor updated
1492. By Michael Terry

Shorten a couple long lines

1493. By Michael Terry

Consolidate app-switching logic

1494. By Michael Terry

Add test and comment for the relied-upon behavior of emitting a focus id changed signal even when we focus the same app

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

run "make tryShell"

Start a left edge drag, up to 1/3 of the display width (to that you have the greeter move), then release it.

Expected outcome:
Greeter slides back into place, in an animated fashion

Actual outcome:
Greeter jumps immediately back into place. There's not animation at all.

review: Needs Fixing
Revision history for this message
Michael Terry (mterry) wrote :

> This line is way too long.

Fixed, plus another similar line.

> All those greeter.notifyAppFocused calls look redundant and not really correct.

Well, you are right. Back when I originally added "emergency dialer" support, I tested and the focusChanged signal was not emitted in all cases (like if it was already focused) and not everything went through the requestFocus path (some things were just focused without a request, by the shell). So I had redundant bits in each signal handler.

But not 100% redundant. Each behaved slightly different. In this branch, I've consolidated all that down to one function call per signal handler, and I felt pretty good about that. But you're right, the whole thing looks silly from someone that isn't in the weeds of it.

So stepping back, I retested. And nowadays, we seem to always properly requestFocus, not to mention that a focusChanged signal is emitted in all cases now. So all those signal handlers can consolidate down to one, when focusChanged is received.

So I've done that. Thanks for the kick in the pants! :) I've also added a test to cover the specific case of a re-focus on an already focused app from the greeter. We didn't test that precise case before, but since that one depends on special behavior of re-emitting a changed signal even when nothing changed, a test & comment seemed prudent.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> run "make tryShell"
>
> Start a left edge drag, up to 1/3 of the display width (to that you have the
> greeter move), then release it.
>
> Expected outcome:
> Greeter slides back into place, in an animated fashion
>
> Actual outcome:
> Greeter jumps immediately back into place. There's not animation at all.

Wonder if we could have a qml regression test for that, that checks that the greeter.x is changed to a couple of intermediate values before finally reaching zero.

lp:~mterry/unity8/greeter-refactor updated
1495. By Michael Terry

Animate letting go of the coverPage

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

1- make tryShellWithPin
2- swipe away the cover page
3- press "Show greeter" button

You get those warnings:
"""
qml/Components/PinLockscreen.qml:24: TypeError: Cannot read property 'top' of null
qml/Components/PinLockscreen.qml:26: TypeError: Cannot read property 'horizontalCenter' of null
"""

review: Needs Fixing
Revision history for this message
Michael Terry (mterry) wrote :

The animation when letting go of the coverPage while making a left drag is fixed too.

Revision history for this message
Michael Terry (mterry) wrote :

Oh, let me see about a test...

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> 1- make tryShellWithPin
> 2- swipe away the cover page
> 3- press "Show greeter" button
>
> You get those warnings:
> """
> qml/Components/PinLockscreen.qml:24: TypeError: Cannot read property 'top' of
> null
> qml/Components/PinLockscreen.qml:26: TypeError: Cannot read property
> 'horizontalCenter' of null
> """

It also happens in trunk, but it would be good to have it fixed here

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

1- run make tryShell

A bunch of new warnings show up:
"""
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataDisappeared"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToDisappear"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataChanged"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToChange"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAppeared"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToAppear"
"""

then when you swipe away the greeter and press the "show greeter" button, you get all the warnings above plus this one:

"""
qml/Components/PassphraseLockscreen.qml:23: TypeError: Cannot read property 'top' of null
"""

review: Needs Fixing
lp:~mterry/unity8/greeter-refactor updated
1496. By Michael Terry

Stop some warnings from being printed

Revision history for this message
Michael Terry (mterry) wrote :

OK, warnings fixed.

lp:~mterry/unity8/greeter-refactor updated
1497. By Michael Terry

Add tiny test to confirm that we don't just jump to launcherOffset = 0

Revision history for this message
Michael Terry (mterry) wrote :

And a test added for the animation of the coverPage being let go.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in qml/Greeter/CoverPage.qml
1 conflicts encountered.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

This launcherOffsetProxy thing in Greeter.qml is rather messy. Have you tried using a SmoothedAnimation, like that? http://paste.ubuntu.com/10189227/

Seems to work fine.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 10/02/15 18:16, Michael Terry wrote:
>> This looks like a desperate hack. Setting "focus: true" should suffice
> Yeah... You got me. It didn't suffice, and I couldn't tell why focus was being stolen. All I could tell was that activeFocus was moving to the greeter's loader itself. But not in a situation that seemingly would require it to move (e.g. it wasn't being reloaded nor was a widget disabled as far as I could tell).

The latest unity8 now has a tool to help you find out who's stealing
your focus. See tests/uqmlscene/ActiveFocusLogger.cpp

You can use it to debug focus with "make tryFoo" by changing "#define
UQMLSCENE_DEBUG_ACTIVE_FOCUS 0" to 1 in tests/uqmlscene/main.cpp.

If you can only reproduce the issue on the device then you will have to
use ActiveFocusLogger in src/main.cpp. Just see how it's done in
tests/uqmlscene/main.cpp.

This gotta be clarified and fixed.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> This launcherOffsetProxy thing in Greeter.qml is rather messy. Have you tried
> using a SmoothedAnimation, like that? http://paste.ubuntu.com/10189227/
>
> Seems to work fine.

This seems perfect: http://paste.ubuntu.com/10189619/

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1498. By Michael Terry

Merge from trunk

Revision history for this message
Michael Terry (mterry) wrote :

> This seems perfect: http://paste.ubuntu.com/10189619/

As I said on IRC:

"""
We don't want [the Behavior animation] when the user is driving the abrupt changes
Then we want to be immediately responsive
It makes the phone look laggy if we trail their finger
[Your latest patch is] still a noticable delay when jittering the mouse/finger. It's better for sure. But still noticable.
It feels clear to me that rather than speeding up a behavior so fast as to not be noticable in that case, we just want to disable it...
I understand it's more code, but it feels more correct
"""

So I'm still pushing back on this request. I'm happy to bow to more pressure, if we want a third opinion.

I am looking into the focus issue.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1499. By Michael Terry

Add unused tst_ShellWithPassphrase.qml file, to allow tryShellWithPassphrase

1500. By Michael Terry

Remove (unneeded?) focus hack

Revision history for this message
Michael Terry (mterry) wrote :

Uh, so I guess the focus issue just doesn't exist anymore? Removing that code seems to have no effect. I can't reproduce the bug I was trying to fix (which occurred when entering a bad passphrase).

So I've taken the hack out entirely.

I've also added a bare-bones tst_ShellWithPassphrase.qml to allow testing the passphrase shell, which I just realized is hard to do without the benefit of "./run.sh -k" now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> I've also added a bare-bones tst_ShellWithPassphrase.qml to allow testing the
> passphrase shell, which I just realized is hard to do without the benefit of
> "./run.sh -k" now.

Now that the lightdm mock libs are unified we no longer need separate test files for different lightdm mock modes.

We can have them all under tst_Shell, like this:
http://paste.ubuntu.com/10202880/

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I'm getting some test failures:

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(without launcher) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(with launcher) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(long swipe) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(long swipe) property x
   Actual (): -64
   Expected (): 0
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(315)]

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Greeter.qml:

"""
   loader.item.authenticated(false);
"""

Please name it like a function, instead of like a boolean property.

I would suggest:
onAuthenticationSucceeded() and onAuthenticationFailed() (or replace the "on" prefix with "nofify", as it's the pattern you seem to be using already) as dropping the boolean parameter makes Greeter.qml code more readable. After all you're not saving any work with this boolean parameter as the implementation does "if(booleanParam){doSuccessfulPath}else{doFailurePath}" anyway.

eg:

"""
    function authenticated(success) {
        if (success) {
            lockscreen.hide();
        } else {
            lockscreen.clear(true);
        }
    }
"""

Would be:

"""
    function onAuthenticationSucceeded() { lockscreen.hide(); }
    function onAuthenticationFailed() { lockscreen.clear(true); }
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

You have this both in Greeter.qml and CoverPage.qml:

"""
 * Copyright (C) 2013 Canonical, Ltd.
"""

Please update.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Greeter.qml

"""
 d.startUnlock(false);
"""

This forces the reader to jump to the function definition to figure out what the heck that booelan means, hurting code readability.

Please at least add an inline comment saying what this boolean is about, like:

"""
d.startUnlock(false /* toTheRight */);
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Both in NarrowView.qml and in WideView.qml\

"""
    QtObject {
        id: d
    }
"""

The intention is good, but right now it's just dead code.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In CoverPager.qml:

"""
    property bool ready: greeterBackground.source == "" || greeterBackground.status == Image.Ready || greeterBackground.status == Image.Error
"""

This line is too long.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tst_WideView.qml

"""
    Binding {
        target: LightDM.Users
        property: "mockMode"
        value: "full"
    }
"""

Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not seing it being done anywhere in this file.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> In tst_WideView.qml
>
> """
> Binding {
> target: LightDM.Users
> property: "mockMode"
> value: "full"
> }
> """
>
> Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not
> seing it being done anywhere in this file.

Hmm, maybe it doesn't matter as LightDM.Greeter doesn't seem to be used by WideView anyway.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tst_Greeter.qml

"""
 * Copyright 2013 Canonical Ltd.
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

tst_Greeter.tst_launcherOffsetAnimation is not really checking that the value got animated back to zero.

Suggestion:
http://paste.ubuntu.com/10204838/

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tests/plugins/LightDM/usersmodel.cpp

"""
 * Copyright (C) 2013 Canonical, Ltd.
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I think that would be all. Tested manually on N4 and N10 and didn't spot any obvious regressions.

lp:~mterry/unity8/greeter-refactor updated
1501. By Michael Terry

Merge from trunk

1502. By Michael Terry

Fix some review nits

1503. By Michael Terry

More nits

1504. By Michael Terry

More nits

1505. By Michael Terry

Fix shell tests, by reverting a small cleanup I tried to make

Revision history for this message
Michael Terry (mterry) wrote :

> """
> QtObject {
> id: d
> }
> """
>
> The intention is good, but right now it's just dead code.

I tend to use QtObject 'd' blocks like that as barriers between the "public" code and the implementation code. So they are useful as a visual separator, even if empty. But alright, removed.

> In tst_Greeter.qml [and usersmodel.cpp, Greeter.qml, and CoverPage.qml]
>
> """
> * Copyright 2013 Canonical Ltd.
> """

Fixed.

> """
> property bool ready: greeterBackground.source == "" || greeterBackground.status == Image.Ready || greeterBackground.status == Image.Error
> """
>
> This line is too long.

Fixed by removing it. It's not actually used anywhere now with this refactor.

> tst_Greeter.tst_launcherOffsetAnimation is not really checking that the value got animated back to zero.

Fair enough, used your version. Thanks!

> Now that the lightdm mock libs are unified we no longer need separate test files for different lightdm mock modes.

Fair enough, used your version. And added a line for "single-pin", though I didn't take the further step of consolidating tst_Shell.qml and tst_ShellWithPin.qml. That can be cleanup for some future branch.

> Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not
> seing it being done anywhere in this file.
>
> Hmm, maybe it doesn't matter as LightDM.Greeter doesn't seem to be used by WideView anyway.

That's why I didn't have it. The views should never need the LightDM.Greeter object, they should get everything from the Greeter.qml class.

> Please at least add an inline comment saying what this boolean is about, like:
>
> """
> d.startUnlock(false /* toTheRight */);
> """

Done.

> """
> loader.item.authenticated(false);
> """
>
> Please name it like a function, instead of like a boolean property.

Fair, especially since LightDM.Greeter is already a property that exists in and around this code.

I've changed it to notifyAuthenticationFailed and notifyAuthenticationSucceeded (I don't like re-using the on* prefix because that same pattern is used for signals).

> I'm getting some test failures:
> [snip]

Fixed. I got overzealous in my consolidation of app-switching code in r1493.

=================

OK, that should be all your comments addressed.

Revision history for this message
Michael Terry (mterry) wrote :

"Fair, especially since LightDM.Greeter.authenticated is already a property that exists in and around this code."

is what I meant to say.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Have to remove this line from tests/qmltests/CMakeLists.txt:

"""
add_qml_test(. ShellWithPassphrase)
"""

review: Needs Fixing
lp:~mterry/unity8/greeter-refactor updated
1506. By Michael Terry

Whoops, remove reference to ShellWithPassphrase

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

Yup, thanks. :P Fixed

lp:~mterry/unity8/greeter-refactor updated
1507. By Michael Terry

Don't enable backspace pin button when not supposed to

Revision history for this message
Daniel d'Andrada (dandrader) 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.
Unrelated, known, failures due to the move to qt 5.4 (I think)

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
lp:~mterry/unity8/greeter-refactor updated
1508. By Michael Terry

Simplify behavior launcher/greeter/dash when doing a long-left-drag; now we never show dash while locked

Revision history for this message
Michael Zanetti (mzanetti) wrote :

Approving changed behavior in the last commit. Works fine in my opinion now. Also had a discussion with design about it and we're good.

 * 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.
Unrelated, known, failures due to the move to qt 5.4 (I think)

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Revision history for this message
Vesa Rautiainen (vesar) wrote :

> Approving changed behavior in the last commit. Works fine in my opinion now.
> Also had a discussion with design about it and we're good.
>
>
> * 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.
> Unrelated, known, failures due to the move to qt 5.4 (I think)
>
> * Did you make sure that the branch does not contain spurious tags?
> Yes

Just to confirm here that Michael and I we had a chat about lockscreen blocking the long left edge swipe behaviour and that's all fine for design.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmake/modules/QmlTest.cmake'
2--- cmake/modules/QmlTest.cmake 2014-12-11 15:48:35 +0000
3+++ cmake/modules/QmlTest.cmake 2015-02-23 15:44:00 +0000
4@@ -28,7 +28,7 @@
5
6 set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
7
8-set(test_env UNITY_TESTING=1)
9+set(test_env LC_ALL=C UNITY_TESTING=1)
10
11 macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
12 set(options NO_ADD_TEST NO_TARGETS)
13
14=== modified file 'qml/Components/DelayedLockscreen.qml'
15--- qml/Components/DelayedLockscreen.qml 2014-09-16 00:00:06 +0000
16+++ qml/Components/DelayedLockscreen.qml 2015-02-23 15:44:00 +0000
17@@ -20,11 +20,13 @@
18
19 Item {
20 id: root
21- anchors.fill: parent
22
23 property int delayMinutes
24 property bool alphaNumeric
25
26+ signal entered(string passphrase) // unused
27+ signal cancel() // unused
28+
29 function clear(playAnimation) {}
30
31 Column {
32
33=== modified file 'qml/Components/PassphraseLockscreen.qml'
34--- qml/Components/PassphraseLockscreen.qml 2014-10-23 13:23:07 +0000
35+++ qml/Components/PassphraseLockscreen.qml 2015-02-23 15:44:00 +0000
36@@ -1,5 +1,5 @@
37 /*
38- * Copyright (C) 2013 Canonical, Ltd.
39+ * Copyright (C) 2013,2014,2015 Canonical, Ltd.
40 *
41 * This program is free software; you can redistribute it and/or modify
42 * it under the terms of the GNU General Public License as published by
43@@ -20,8 +20,7 @@
44
45 Item {
46 id: root
47- anchors.top: parent.top
48- anchors.topMargin: units.gu(4)
49+ y: units.gu(4)
50 height: shakeContainer.height
51
52 property string infoText
53
54=== modified file 'qml/Components/PinLockscreen.qml'
55--- qml/Components/PinLockscreen.qml 2014-10-23 13:23:07 +0000
56+++ qml/Components/PinLockscreen.qml 2015-02-23 15:44:00 +0000
57@@ -21,9 +21,7 @@
58
59 Column {
60 id: root
61- anchors.top: parent.top
62- anchors.topMargin: units.gu(4)
63- anchors.horizontalCenter: parent.horizontalCenter
64+ y: units.gu(4)
65 spacing: units.gu(2)
66
67 property string infoText
68@@ -123,6 +121,7 @@
69 objectName: "backspaceIcon"
70 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
71 width: height
72+ enabled: root.entryEnabled
73
74 Icon {
75 anchors.fill: parent
76@@ -130,7 +129,7 @@
77 color: "#f3f3e7"
78 }
79
80- opacity: (pinentryField.text.length && !pinentryField.incorrectOverride) > 0 ? 1 : 0
81+ opacity: (pinentryField.text.length > 0 && !pinentryField.incorrectOverride) ? 1 : 0
82
83 Behavior on opacity {
84 UbuntuNumberAnimation {}
85
86=== renamed file 'qml/Greeter/GreeterContent.qml' => 'qml/Greeter/CoverPage.qml'
87--- qml/Greeter/GreeterContent.qml 2015-02-11 16:04:13 +0000
88+++ qml/Greeter/CoverPage.qml 2015-02-23 15:44:00 +0000
89@@ -1,5 +1,5 @@
90 /*
91- * Copyright (C) 2013 Canonical, Ltd.
92+ * Copyright (C) 2013,2014,2015 Canonical, Ltd.
93 *
94 * This program is free software; you can redistribute it and/or modify
95 * it under the terms of the GNU General Public License as published by
96@@ -14,34 +14,48 @@
97 * along with this program. If not, see <http://www.gnu.org/licenses/>.
98 */
99
100-import QtQuick 2.0
101-import AccountsService 0.1
102-import Ubuntu.Components 0.1
103-import LightDM 0.1 as LightDM
104+import QtQuick 2.3
105+import Ubuntu.Components 1.1
106+import Ubuntu.Gestures 0.1
107 import "../Components"
108
109-Item {
110+Showable {
111 id: root
112- anchors.fill: parent
113-
114- property var inputMethod
115-
116- property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
117-
118- signal selected(int uid)
119- signal unlocked(int uid)
120-
121- function tryToUnlock() {
122- if (loginLoader.item) {
123- loginLoader.item.tryToUnlock()
124- }
125- }
126-
127- function reset() {
128- if (loginLoader.item) {
129- loginLoader.item.reset()
130- }
131- }
132+
133+ property real dragHandleLeftMargin
134+ property real launcherOffset
135+ property alias background: greeterBackground.source
136+ property real backgroundTopMargin
137+ property var infographicModel
138+ property bool draggable: true
139+
140+ property alias infographics: infographics
141+
142+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
143+
144+ signal tease()
145+
146+ function hideRight() {
147+ d.forceRightOnNextHideAnimation = true;
148+ hide();
149+ }
150+
151+ QtObject {
152+ id: d
153+ property bool forceRightOnNextHideAnimation: false
154+ }
155+
156+ prepareToHide: function () {
157+ hideTranslation.from = root.x + translation.x
158+ hideTranslation.to = root.x > 0 || d.forceRightOnNextHideAnimation ? root.width : -root.width;
159+ d.forceRightOnNextHideAnimation = false;
160+ }
161+
162+ // We don't directly bind "x" because that's owned by the DragHandle. So
163+ // instead, we can get a little extra horizontal push by using transforms.
164+ transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
165+
166+ MouseArea { anchors.fill: parent; }
167
168 Rectangle {
169 // In case background fails to load
170@@ -51,17 +65,16 @@
171 }
172
173 CrossFadeImage {
174- id: background
175+ id: greeterBackground
176 objectName: "greeterBackground"
177 anchors {
178 fill: parent
179- topMargin: backgroundTopMargin
180+ topMargin: root.backgroundTopMargin
181 }
182 fillMode: Image.PreserveAspectCrop
183 // Limit how much memory we'll reserve for this image
184 sourceSize.height: height
185 sourceSize.width: width
186- source: greeter.background
187 }
188
189 Rectangle {
190@@ -70,94 +83,134 @@
191 opacity: 0.4
192 }
193
194- Loader {
195- id: loginLoader
196- objectName: "loginLoader"
197- anchors {
198- left: parent.left
199- leftMargin: Math.min(parent.width * 0.16, units.gu(20))
200- top: parent.top
201- }
202- width: units.gu(29)
203- height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
204- : parent.height
205- Behavior on height { UbuntuNumberAnimation {} }
206-
207- // TODO: Once we have a system API for determining which mode we are
208- // in, tablet/phone/desktop, that should be used instead of narrowMode.
209- source: greeter.narrowMode ? "" : "LoginList.qml"
210-
211- onLoaded: {
212- item.currentIndex = greeterContentLoader.currentIndex;
213- }
214-
215- Binding {
216- target: loginLoader.item
217- property: "model"
218- value: greeterContentLoader.model
219- }
220-
221- Connections {
222- target: loginLoader.item
223-
224- onSelected: {
225- root.selected(uid);
226- }
227-
228- onUnlocked: {
229- root.unlocked(uid);
230- }
231-
232- onCurrentIndexChanged: {
233- if (greeterContentLoader.currentIndex !== loginLoader.item.currentIndex) {
234- greeterContentLoader.currentIndex = loginLoader.item.currentIndex;
235- }
236- }
237- }
238- }
239-
240 Infographics {
241 id: infographics
242 objectName: "infographics"
243- height: narrowMode ? parent.height : 0.75 * parent.height
244- model: greeterContentLoader.infographicModel
245+ height: parent.height
246+ model: root.infographicModel
247 clip: true // clip large data bubbles
248
249- property string selectedUser
250- property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
251- onInfographicUserChanged: greeterContentLoader.infographicModel.username = infographicUser
252-
253- Component.onCompleted: {
254- selectedUser = greeterContentLoader.model.data(greeterContentLoader.currentIndex, LightDM.UserRoles.NameRole)
255- greeterContentLoader.infographicModel.username = infographicUser
256- greeterContentLoader.infographicModel.readyForDataChange()
257- }
258-
259- Connections {
260- target: root
261- onSelected: infographics.selectedUser = greeterContentLoader.model.data(uid, LightDM.UserRoles.NameRole)
262- }
263-
264- Connections {
265- target: i18n
266- onLanguageChanged: greeterContentLoader.infographicModel.readyForDataChange()
267- }
268-
269 anchors {
270 verticalCenter: parent.verticalCenter
271- left: narrowMode ? root.left : loginLoader.right
272- right: root.right
273- }
274- }
275-
276- Clock {
277- id: clock
278- visible: narrowMode
279-
280- anchors {
281- top: parent.top
282- topMargin: units.gu(2)
283- horizontalCenter: parent.horizontalCenter
284+ left: parent.left
285+ right: parent.right
286+ }
287+ }
288+
289+ Label {
290+ id: swipeHint
291+ property real baseOpacity: 0.5
292+ opacity: 0.0
293+ anchors.horizontalCenter: parent.horizontalCenter
294+ anchors.bottom: parent.bottom
295+ anchors.bottomMargin: units.gu(5)
296+ text: "《 " + i18n.tr("Unlock") + " 》"
297+ color: "white"
298+ font.weight: Font.Light
299+
300+ SequentialAnimation on opacity {
301+ id: showLabelAnimation
302+ running: false
303+ loops: 2
304+
305+ StandardAnimation {
306+ from: 0.0
307+ to: swipeHint.baseOpacity
308+ duration: UbuntuAnimation.SleepyDuration
309+ }
310+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
311+ StandardAnimation {
312+ from: swipeHint.baseOpacity
313+ to: 0.0
314+ duration: UbuntuAnimation.SleepyDuration
315+ }
316+ }
317+ }
318+
319+ DragHandle {
320+ id: dragHandle
321+ anchors.fill: parent
322+ anchors.leftMargin: root.dragHandleLeftMargin
323+ enabled: root.draggable
324+ direction: Direction.Horizontal
325+
326+ onDraggingChanged: {
327+ if (dragging) {
328+ root.tease();
329+ showLabelAnimation.start();
330+ }
331+ }
332+ }
333+
334+ // right side shadow
335+ Image {
336+ anchors.left: parent.right
337+ anchors.top: parent.top
338+ anchors.bottom: parent.bottom
339+ fillMode: Image.Tile
340+ source: "../graphics/dropshadow_right.png"
341+ }
342+
343+ // left side shadow
344+ Image {
345+ anchors.right: parent.left
346+ anchors.top: parent.top
347+ anchors.bottom: parent.bottom
348+ fillMode: Image.Tile
349+ source: "../graphics/dropshadow_left.png"
350+ }
351+
352+ Binding {
353+ id: positionLock
354+
355+ property bool enabled: false
356+ onEnabledChanged: {
357+ if (enabled === __enabled) {
358+ return;
359+ }
360+
361+ if (enabled) {
362+ if (root.x > 0) {
363+ value = Qt.binding(function() { return root.width; })
364+ } else {
365+ value = Qt.binding(function() { return -root.width; })
366+ }
367+ }
368+
369+ __enabled = enabled;
370+ }
371+
372+ property bool __enabled: false
373+
374+ target: root
375+ when: __enabled
376+ property: "x"
377+ }
378+
379+ hideAnimation: SequentialAnimation {
380+ id: hideAnimation
381+ objectName: "hideAnimation"
382+ property var target // unused, here to silence Showable warning
383+ StandardAnimation {
384+ id: hideTranslation
385+ property: "x"
386+ target: root
387+ }
388+ PropertyAction { target: root; property: "visible"; value: false }
389+ PropertyAction { target: positionLock; property: "enabled"; value: true }
390+ }
391+
392+ showAnimation: SequentialAnimation {
393+ id: showAnimation
394+ objectName: "showAnimation"
395+ property var target // unused, here to silence Showable warning
396+ PropertyAction { target: root; property: "visible"; value: true }
397+ PropertyAction { target: positionLock; property: "enabled"; value: false }
398+ StandardAnimation {
399+ property: "x"
400+ target: root
401+ to: 0
402+ duration: UbuntuAnimation.FastDuration
403 }
404 }
405 }
406
407=== modified file 'qml/Greeter/Greeter.qml'
408--- qml/Greeter/Greeter.qml 2015-01-23 19:47:37 +0000
409+++ qml/Greeter/Greeter.qml 2015-02-23 15:44:00 +0000
410@@ -1,5 +1,5 @@
411 /*
412- * Copyright (C) 2013 Canonical, Ltd.
413+ * Copyright (C) 2013,2014,2015 Canonical, Ltd.
414 *
415 * This program is free software; you can redistribute it and/or modify
416 * it under the terms of the GNU General Public License as published by
417@@ -14,66 +14,185 @@
418 * along with this program. If not, see <http://www.gnu.org/licenses/>.
419 */
420
421-import QtQuick 2.0
422-import Ubuntu.Components 0.1
423-import Ubuntu.Gestures 0.1
424+import QtQuick 2.3
425+import AccountsService 0.1
426 import LightDM 0.1 as LightDM
427+import Ubuntu.Components 1.1
428+import Ubuntu.SystemImage 0.1
429+import Unity.Connectivity 0.1
430+import Unity.Launcher 0.1
431 import "../Components"
432
433 Showable {
434- id: greeter
435- enabled: shown
436- created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
437+ id: root
438+ created: loader.status == Loader.Ready
439
440 property real dragHandleLeftMargin: 0
441- property alias dragging: dragHandle.dragging
442
443 property url background
444
445- // so that it can be replaced in tests with a mock object
446- property var inputMethod: Qt.inputMethod
447-
448- prepareToHide: function () {
449- hideTranslation.to = greeter.x > 0 || d.forceRightOnNextHideAnimation ? greeter.width : -greeter.width;
450- d.forceRightOnNextHideAnimation = false;
451+ // How far to offset the top greeter layer during a launcher left-drag
452+ property real launcherOffset
453+
454+ readonly property bool active: shown || hasLockedApp
455+ readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
456+
457+ // True when the greeter is waiting for PAM or other setup process
458+ readonly property alias waiting: d.waiting
459+
460+ property string lockedApp: ""
461+ readonly property bool hasLockedApp: lockedApp !== ""
462+
463+ property bool forcedUnlock
464+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
465+
466+ property bool tabletMode
467+ property url viewSource // only used for testing
468+
469+ property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
470+ property int failedLoginsDelayAttempts: 7 // number of failed logins
471+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
472+
473+ signal tease()
474+ signal sessionStarted()
475+ signal emergencyCall()
476+
477+ function forceShow() {
478+ showNow();
479+ loader.item.reset();
480+ }
481+
482+ function notifyAppFocused(appId) {
483+ if (!active) {
484+ return;
485+ }
486+
487+ if (hasLockedApp) {
488+ if (appId === lockedApp) {
489+ hide(); // show locked app
490+ } else {
491+ show();
492+ d.startUnlock(false /* toTheRight */);
493+ }
494+ } else if (appId !== "unity8-dash") { // dash isn't started by user
495+ d.startUnlock(false /* toTheRight */);
496+ }
497+ }
498+
499+ function notifyAboutToFocusApp(appId) {
500+ if (!active) {
501+ return;
502+ }
503+
504+ // A hint that we're about to focus an app. This way we can look
505+ // a little more responsive, rather than waiting for the above
506+ // notifyAppFocused call. We also need this in case we have a locked
507+ // app, in order to show lockscreen instead of new app.
508+ d.startUnlock(false /* toTheRight */);
509+ }
510+
511+ // This is a just a glorified notifyAboutToFocusApp(), but it does one
512+ // other thing: it hides any cover pages to the RIGHT, because the user
513+ // just came from a launcher drag starting on the left.
514+ // It also returns a boolean value, indicating whether there was a visual
515+ // change or not (the shell only wants to hide the launcher if there was
516+ // a change).
517+ function notifyShowingDashFromDrag() {
518+ if (!active) {
519+ return false;
520+ }
521+
522+ return d.startUnlock(true /* toTheRight */);
523 }
524
525 QtObject {
526 id: d
527- property bool forceRightOnNextHideAnimation: false
528- }
529-
530- property bool loadContent: required
531-
532- // 1 when fully shown and 0 when fully hidden
533- property real showProgress: visible ? MathUtils.clamp((width - Math.abs(x)) / width, 0, 1) : 0
534-
535- property alias model: greeterContentLoader.model
536- property bool locked: true
537-
538- readonly property bool narrowMode: !multiUser && height > width
539- readonly property bool multiUser: LightDM.Users.count > 1
540-
541- readonly property int currentIndex: greeterContentLoader.currentIndex
542-
543- signal selected(int uid)
544- signal unlocked(int uid)
545- signal tapped()
546-
547- function hideRight() {
548- d.forceRightOnNextHideAnimation = true;
549- hide();
550- }
551-
552- function tryToUnlock() {
553- if (created) {
554- greeterContentLoader.item.tryToUnlock()
555- }
556- }
557-
558- function reset() {
559- if (created) {
560- greeterContentLoader.item.reset()
561+
562+ readonly property bool multiUser: LightDM.Users.count > 1
563+ property int currentIndex
564+ property int delayMinutes
565+ property bool waiting
566+
567+ // We want 'launcherOffset' to animate down to zero. But not to animate
568+ // while being dragged. So ideally we change this only when the user
569+ // lets go and launcherOffset drops to zero. But we need to wait for
570+ // the behavior to be enabled first. So we cache the last known good
571+ // launcherOffset value to cover us during that brief gap between
572+ // release and the behavior turning on.
573+ property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
574+ property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
575+ Behavior on launcherOffsetProxy {
576+ id: launcherOffsetProxyBehavior
577+ enabled: launcherOffset === 0
578+ UbuntuNumberAnimation {}
579+ }
580+
581+ function selectUser(uid, reset) {
582+ d.waiting = true;
583+ if (reset) {
584+ loader.item.reset();
585+ }
586+ currentIndex = uid;
587+ var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
588+ AccountsService.user = user;
589+ LauncherModel.setUser(user);
590+ LightDM.Greeter.authenticate(user); // always resets auth state
591+ }
592+
593+ function login() {
594+ enabled = false;
595+ if (LightDM.Greeter.startSessionSync()) {
596+ sessionStarted();
597+ loader.item.notifyAuthenticationSucceeded();
598+ } else {
599+ loader.item.notifyAuthenticationFailed();
600+ }
601+ enabled = true;
602+ }
603+
604+ function startUnlock(toTheRight) {
605+ if (loader.item) {
606+ return loader.item.tryToUnlock(toTheRight);
607+ } else {
608+ return false;
609+ }
610+ }
611+ }
612+
613+ onLauncherOffsetChanged: {
614+ if (launcherOffset > 0) {
615+ d.lastKnownPositiveOffset = launcherOffset;
616+ }
617+ }
618+
619+ onForcedUnlockChanged: {
620+ if (forcedUnlock && shown) {
621+ // pretend we were just authenticated
622+ loader.item.notifyAuthenticationSucceeded();
623+ }
624+ }
625+
626+ onRequiredChanged: {
627+ if (required) {
628+ d.waiting = true;
629+ lockedApp = "";
630+ }
631+ }
632+
633+ Component.onCompleted: {
634+ Connectivity.unlockAllModems();
635+ }
636+
637+ Timer {
638+ id: forcedDelayTimer
639+ interval: 1000 * 60
640+ onTriggered: {
641+ if (d.delayMinutes > 0) {
642+ d.delayMinutes -= 1;
643+ if (d.delayMinutes > 0) {
644+ start(); // go again
645+ }
646+ }
647 }
648 }
649
650@@ -82,152 +201,205 @@
651 MouseArea { anchors.fill: parent }
652
653 Loader {
654- id: greeterContentLoader
655- objectName: "greeterContentLoader"
656+ id: loader
657+ objectName: "loader"
658+
659 anchors.fill: parent
660- property var model: LightDM.Users
661- property int currentIndex: 0
662- property var infographicModel: LightDM.Infographic
663- readonly property int backgroundTopMargin: -greeter.y
664
665- source: loadContent ? "GreeterContent.qml" : ""
666+ active: root.required
667+ source: root.viewSource.toString() ? root.viewSource :
668+ (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
669
670 onLoaded: {
671- selected(currentIndex);
672+ root.lockedApp = "";
673+ root.forceActiveFocus();
674+ d.selectUser(d.currentIndex, true);
675+ LightDM.Infographic.readyForDataChange();
676 }
677
678 Connections {
679- target: greeterContentLoader.item
680-
681+ target: loader.item
682 onSelected: {
683- greeter.selected(uid);
684- greeterContentLoader.currentIndex = uid;
685- }
686- onUnlocked: greeter.unlocked(uid);
687- }
688- Binding {
689- target: greeterContentLoader.item
690- property: "inputMethod"
691- value: greeter.inputMethod
692- }
693- }
694-
695- DragHandle {
696- id: dragHandle
697- anchors.fill: parent
698- anchors.leftMargin: greeter.dragHandleLeftMargin
699- enabled: (greeter.narrowMode || !greeter.locked) && greeter.enabled && greeter.shown
700- direction: Direction.Horizontal
701-
702- onTapped: {
703- greeter.tapped();
704- showLabelAnimation.start();
705- }
706-
707- onDraggingChanged: {
708- if (dragging) {
709- showLabelAnimation.start();
710- }
711- }
712- }
713-
714- Label {
715- id: swipeHint
716- property real baseOpacity: 0.5
717- opacity: 0.0
718- anchors.horizontalCenter: parent.horizontalCenter
719- anchors.bottom: parent.bottom
720- anchors.bottomMargin: units.gu(5)
721- text: "《 " + i18n.tr("Unlock") + " 》"
722- color: "white"
723- font.weight: Font.Light
724-
725- SequentialAnimation on opacity {
726- id: showLabelAnimation
727- running: false
728- loops: 2
729-
730- StandardAnimation {
731- from: 0.0
732- to: swipeHint.baseOpacity
733- duration: UbuntuAnimation.SleepyDuration
734- }
735- PauseAnimation { duration: UbuntuAnimation.BriskDuration }
736- StandardAnimation {
737- from: swipeHint.baseOpacity
738- to: 0.0
739- duration: UbuntuAnimation.SleepyDuration
740- }
741- }
742- }
743-
744- // right side shadow
745- Image {
746- anchors.left: parent.right
747- anchors.top: parent.top
748- anchors.bottom: parent.bottom
749- fillMode: Image.Tile
750- source: "../graphics/dropshadow_right.png"
751- }
752-
753- // left side shadow
754- Image {
755- anchors.right: parent.left
756- anchors.top: parent.top
757- anchors.bottom: parent.bottom
758- fillMode: Image.Tile
759- source: "../graphics/dropshadow_left.png"
760- }
761-
762- Binding {
763- id: positionLock
764-
765- property bool enabled: false
766- onEnabledChanged: {
767- if (enabled === __enabled) {
768- return;
769- }
770-
771- if (enabled) {
772- if (greeter.x > 0) {
773- value = Qt.binding(function() { return greeter.width; })
774+ d.selectUser(index, true);
775+ }
776+ onResponded: {
777+ if (root.locked) {
778+ LightDM.Greeter.respond(response);
779 } else {
780- value = Qt.binding(function() { return -greeter.width; })
781- }
782- }
783-
784- __enabled = enabled;
785- }
786-
787- property bool __enabled: false
788-
789- target: greeter
790- when: __enabled
791- property: "x"
792- }
793-
794- hideAnimation: SequentialAnimation {
795- id: hideAnimation
796- objectName: "hideAnimation"
797- StandardAnimation {
798- id: hideTranslation
799- property: "x"
800- target: greeter
801- }
802- PropertyAction { target: greeter; property: "visible"; value: false }
803- PropertyAction { target: positionLock; property: "enabled"; value: true }
804- }
805-
806- showAnimation: SequentialAnimation {
807- id: showAnimation
808- objectName: "showAnimation"
809- PropertyAction { target: greeter; property: "visible"; value: true }
810- PropertyAction { target: positionLock; property: "enabled"; value: false }
811- StandardAnimation {
812- property: "x"
813- target: greeter
814- to: 0
815- duration: UbuntuAnimation.FastDuration
816- }
817+ if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
818+ d.login();
819+ }
820+ loader.item.hide();
821+ }
822+ }
823+ onTease: root.tease()
824+ onEmergencyCall: root.emergencyCall()
825+ onRequiredChanged: {
826+ if (!loader.item.required) {
827+ root.hide();
828+ }
829+ }
830+ }
831+
832+ Binding {
833+ target: loader.item
834+ property: "backgroundTopMargin"
835+ value: -root.y
836+ }
837+
838+ Binding {
839+ target: loader.item
840+ property: "launcherOffset"
841+ value: d.launcherOffsetProxy
842+ }
843+
844+ Binding {
845+ target: loader.item
846+ property: "dragHandleLeftMargin"
847+ value: root.dragHandleLeftMargin
848+ }
849+
850+ Binding {
851+ target: loader.item
852+ property: "delayMinutes"
853+ value: d.delayMinutes
854+ }
855+
856+ Binding {
857+ target: loader.item
858+ property: "background"
859+ value: root.background
860+ }
861+
862+ Binding {
863+ target: loader.item
864+ property: "locked"
865+ value: root.locked
866+ }
867+
868+ Binding {
869+ target: loader.item
870+ property: "alphanumeric"
871+ value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
872+ }
873+
874+ Binding {
875+ target: loader.item
876+ property: "currentIndex"
877+ value: d.currentIndex
878+ }
879+
880+ Binding {
881+ target: loader.item
882+ property: "userModel"
883+ value: LightDM.Users
884+ }
885+
886+ Binding {
887+ target: loader.item
888+ property: "infographicModel"
889+ value: LightDM.Infographic
890+ }
891+ }
892+
893+ Connections {
894+ target: LightDM.Greeter
895+
896+ onShowGreeter: root.forceShow()
897+
898+ onHideGreeter: {
899+ d.login();
900+ loader.item.hide();
901+ }
902+
903+ onShowMessage: {
904+ if (!LightDM.Greeter.active) {
905+ return; // could happen if hideGreeter() comes in before we prompt
906+ }
907+
908+ // inefficient, but we only rarely deal with messages
909+ var html = text.replace(/&/g, "&amp;")
910+ .replace(/</g, "&lt;")
911+ .replace(/>/g, "&gt;")
912+ .replace(/\n/g, "<br>");
913+ if (isError) {
914+ html = "<font color=\"#df382c\">" + html + "</font>";
915+ }
916+
917+ loader.item.showMessage(html);
918+ }
919+
920+ onShowPrompt: {
921+ d.waiting = false;
922+
923+ if (!LightDM.Greeter.active) {
924+ return; // could happen if hideGreeter() comes in before we prompt
925+ }
926+
927+ loader.item.showPrompt(text, isSecret, isDefaultPrompt);
928+ }
929+
930+ onAuthenticationComplete: {
931+ d.waiting = false;
932+
933+ if (LightDM.Greeter.authenticated) {
934+ AccountsService.failedLogins = 0;
935+ d.login();
936+ if (!LightDM.Greeter.promptless) {
937+ loader.item.hide();
938+ }
939+ } else {
940+ if (!LightDM.Greeter.promptless) {
941+ AccountsService.failedLogins++;
942+ }
943+
944+ // Check if we should initiate a factory reset
945+ if (maxFailedLogins >= 2) { // require at least a warning
946+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
947+ loader.item.showLastChance();
948+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
949+ SystemImage.factoryReset(); // Ouch!
950+ }
951+ }
952+
953+ // Check if we should initiate a forced login delay
954+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
955+ d.delayMinutes = failedLoginsDelayMinutes;
956+ forcedDelayTimer.start();
957+ }
958+
959+ loader.item.notifyAuthenticationFailed();
960+ if (!LightDM.Greeter.promptless) {
961+ d.selectUser(d.currentIndex, false);
962+ }
963+ }
964+ }
965+
966+ onRequestAuthenticationUser: {
967+ // Find index for requested user, if it exists
968+ for (var i = 0; i < LightDM.Users.count; i++) {
969+ if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
970+ d.selectUser(i, true);
971+ return;
972+ }
973+ }
974+ }
975+ }
976+
977+ Binding {
978+ target: LightDM.Greeter
979+ property: "active"
980+ value: root.active
981+ }
982+
983+ Binding {
984+ target: LightDM.Infographic
985+ property: "username"
986+ value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
987+ }
988+
989+ Connections {
990+ target: i18n
991+ onLanguageChanged: LightDM.Infographic.readyForDataChange()
992 }
993 }
994
995=== modified file 'qml/Greeter/Infographics.qml'
996--- qml/Greeter/Infographics.qml 2015-01-09 10:41:28 +0000
997+++ qml/Greeter/Infographics.qml 2015-02-23 15:44:00 +0000
998@@ -38,6 +38,7 @@
999
1000 Connections {
1001 target: model
1002+ ignoreUnknownSignals: model === undefined
1003
1004 onDataAboutToAppear: startHideAnimation() // hide "no data" label
1005 onDataAppeared: startShowAnimation()
1006
1007=== modified file 'qml/Greeter/LoginList.qml'
1008--- qml/Greeter/LoginList.qml 2014-11-04 12:52:49 +0000
1009+++ qml/Greeter/LoginList.qml 2015-02-23 15:44:00 +0000
1010@@ -14,44 +14,76 @@
1011 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1012 */
1013
1014-import QtQuick 2.0
1015-import Ubuntu.Components 0.1
1016-import LightDM 0.1 as LightDM
1017+import QtQuick 2.3
1018+import Ubuntu.Components 1.1
1019 import "../Components"
1020
1021 Item {
1022 id: root
1023
1024- property alias userList: userList
1025 property alias model: userList.model
1026- property alias currentIndex: userList.currentIndex
1027+ property int currentIndex
1028+ property bool locked
1029
1030 readonly property int numAboveBelow: 4
1031 readonly property int cellHeight: units.gu(5)
1032 readonly property int highlightedHeight: units.gu(10)
1033 readonly property int moveDuration: 200
1034+ readonly property string currentUser: userList.currentItem.username
1035 property bool wasPrompted: false
1036
1037- signal selected(int uid)
1038- signal unlocked(int uid)
1039+ signal selected(int index)
1040+ signal responded(string response)
1041
1042 function tryToUnlock() {
1043- if (LightDM.Greeter.promptless) {
1044- if (LightDM.Greeter.authenticated) {
1045- root.unlocked(userList.currentIndex)
1046+ if (wasPrompted) {
1047+ passwordInput.forceActiveFocus();
1048+ } else {
1049+ if (root.locked) {
1050+ root.selected(currentIndex);
1051 } else {
1052- root.resetAuthentication()
1053+ root.responded("");
1054 }
1055+ }
1056+ }
1057+
1058+ function showMessage(html) {
1059+ if (infoLabel.text === "") {
1060+ infoLabel.text = html;
1061 } else {
1062- passwordInput.forceActiveFocus()
1063+ infoLabel.text += "<br>" + html;
1064+ }
1065+ }
1066+
1067+ function showPrompt(text, isSecret, isDefaultPrompt) {
1068+ passwordInput.text = "";
1069+ passwordInput.promptText = text;
1070+ passwordInput.enabled = true;
1071+ passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
1072+ if (wasPrompted) // stay in text field if second prompt
1073+ passwordInput.focus = true;
1074+ wasPrompted = true;
1075+ }
1076+
1077+ function showError() {
1078+ wrongPasswordAnimation.start();
1079+ root.resetAuthentication();
1080+ if (wasPrompted) {
1081+ passwordInput.focus = true;
1082 }
1083 }
1084
1085 function reset() {
1086- root.resetAuthentication()
1087- }
1088-
1089- Keys.onEscapePressed: root.resetAuthentication()
1090+ root.resetAuthentication();
1091+ }
1092+
1093+ Keys.onEscapePressed: {
1094+ selected(currentIndex);
1095+ }
1096+
1097+ onCurrentIndexChanged: {
1098+ userList.currentIndex = currentIndex;
1099+ }
1100
1101 Rectangle {
1102 id: highlightItem
1103@@ -81,26 +113,24 @@
1104 flickDeceleration: 10000
1105
1106 readonly property bool movingInternally: moveTimer.running || userList.moving
1107-
1108- onCurrentIndexChanged: {
1109- if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
1110- root.resetAuthentication();
1111- }
1112- }
1113-
1114 onMovingInternallyChanged: {
1115- // Only emit the selected signal once we stop moving to avoid frequent background changes
1116 if (!movingInternally) {
1117- root.selected(userList.currentIndex);
1118+ root.selected(currentIndex);
1119 }
1120 }
1121
1122+ onCurrentIndexChanged: {
1123+ root.resetAuthentication();
1124+ moveTimer.start();
1125+ }
1126+
1127 delegate: Item {
1128 width: parent.width
1129 height: root.cellHeight
1130
1131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
1132 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
1133+ readonly property string username: name
1134
1135 opacity: {
1136 // The goal here is to make names less and less opaque as they
1137@@ -148,11 +178,8 @@
1138 topMargin: parent.belowHighlight ? parent.belowOffset : 0
1139 }
1140 height: parent.height
1141- enabled: userList.currentIndex !== index
1142- onClicked: {
1143- moveTimer.start();
1144- userList.currentIndex = index;
1145- }
1146+ enabled: userList.currentIndex !== index && parent.opacity > 0
1147+ onClicked: root.selected(index)
1148
1149 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
1150 }
1151@@ -204,6 +231,11 @@
1152 width: parent.width - anchors.margins * 2
1153 opacity: userList.movingInternally ? 0 : 1
1154
1155+ property string promptText
1156+ placeholderText: root.wasPrompted ? promptText
1157+ : (root.locked ? i18n.tr("Retry")
1158+ : i18n.tr("Tap to unlock"))
1159+
1160 Behavior on opacity {
1161 NumberAnimation { duration: 100 }
1162 }
1163@@ -213,9 +245,11 @@
1164 return;
1165 root.focus = true; // so that it can handle Escape presses for us
1166 enabled = false;
1167- LightDM.Greeter.respond(text);
1168- }
1169- Keys.onEscapePressed: root.resetAuthentication()
1170+ root.responded(text);
1171+ }
1172+ Keys.onEscapePressed: {
1173+ root.selected(currentIndex);
1174+ }
1175
1176 Image {
1177 anchors {
1178@@ -223,7 +257,7 @@
1179 rightMargin: units.gu(2)
1180 verticalCenter: parent.verticalCenter
1181 }
1182- visible: LightDM.Greeter.promptless
1183+ visible: !root.wasPrompted
1184 source: "graphics/icon_arrow.png"
1185 }
1186
1187@@ -247,7 +281,7 @@
1188 id: passwordMouseArea
1189 objectName: "passwordMouseArea"
1190 anchors.fill: passwordInput
1191- enabled: LightDM.Greeter.promptless
1192+ enabled: !root.wasPrompted
1193 onClicked: root.tryToUnlock()
1194 }
1195
1196@@ -256,62 +290,10 @@
1197 return;
1198 }
1199 infoLabel.text = "";
1200- passwordInput.placeholderText = "";
1201+ passwordInput.promptText = "";
1202 passwordInput.text = "";
1203 passwordInput.focus = false;
1204 passwordInput.enabled = true;
1205 root.wasPrompted = false;
1206- LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
1207- }
1208-
1209- Connections {
1210- target: LightDM.Greeter
1211-
1212- onShowMessage: {
1213- // inefficient, but we only rarely deal with messages
1214- var html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")
1215- if (isError)
1216- html = "<font color=\"#df382c\">" + html + "</font>"
1217- if (infoLabel.text == "")
1218- infoLabel.text = html
1219- else
1220- infoLabel.text = infoLabel.text + "<br>" + html
1221- }
1222-
1223- onShowPrompt: {
1224- passwordInput.text = "";
1225- passwordInput.placeholderText = text;
1226- passwordInput.enabled = true;
1227- passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
1228- if (root.wasPrompted) // stay in text field if second prompt
1229- passwordInput.focus = true;
1230- root.wasPrompted = true;
1231- }
1232-
1233- onAuthenticationComplete: {
1234- if (LightDM.Greeter.promptless) {
1235- passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
1236- return;
1237- }
1238- if (LightDM.Greeter.authenticated) {
1239- root.unlocked(userList.currentIndex);
1240- } else {
1241- wrongPasswordAnimation.start();
1242- root.resetAuthentication();
1243- passwordInput.focus = true;
1244- }
1245- passwordInput.text = "";
1246- }
1247-
1248- onRequestAuthenticationUser: {
1249- // Find index for requested user, if it exists
1250- for (var i = 0; i < userList.model.count; i++) {
1251- if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
1252- moveTimer.start();
1253- userList.currentIndex = i;
1254- return;
1255- }
1256- }
1257- }
1258 }
1259 }
1260
1261=== added file 'qml/Greeter/NarrowView.qml'
1262--- qml/Greeter/NarrowView.qml 1970-01-01 00:00:00 +0000
1263+++ qml/Greeter/NarrowView.qml 2015-02-23 15:44:00 +0000
1264@@ -0,0 +1,170 @@
1265+/*
1266+ * Copyright (C) 2015 Canonical, Ltd.
1267+ *
1268+ * This program is free software; you can redistribute it and/or modify
1269+ * it under the terms of the GNU General Public License as published by
1270+ * the Free Software Foundation; version 3.
1271+ *
1272+ * This program is distributed in the hope that it will be useful,
1273+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1274+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1275+ * GNU General Public License for more details.
1276+ *
1277+ * You should have received a copy of the GNU General Public License
1278+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1279+ */
1280+
1281+import QtQuick 2.3
1282+import Ubuntu.Components 1.1
1283+import "../Components"
1284+
1285+FocusScope {
1286+ id: root
1287+
1288+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
1289+ property alias launcherOffset: coverPage.launcherOffset
1290+ property int currentIndex // unused
1291+ property alias delayMinutes: lockscreen.delayMinutes
1292+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
1293+ property url background
1294+ property bool locked
1295+ property bool alphanumeric
1296+ property var userModel // unused
1297+ property alias infographicModel: coverPage.infographicModel
1298+ readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
1299+ readonly property bool required: coverPage.required || lockscreen.required
1300+
1301+ signal selected(int index) // unused
1302+ signal responded(string response)
1303+ signal tease()
1304+ signal emergencyCall()
1305+
1306+ function showMessage(html) {
1307+ // TODO
1308+ }
1309+
1310+ function showPrompt(text, isSecret, isDefaultPrompt) {
1311+ lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
1312+ lockscreen.maybeShow();
1313+ }
1314+
1315+ function showLastChance() {
1316+ var title = lockscreen.alphaNumeric ?
1317+ i18n.tr("Sorry, incorrect passphrase.") :
1318+ i18n.tr("Sorry, incorrect passcode.");
1319+ var text = i18n.tr("This will be your last attempt.") + " " +
1320+ (lockscreen.alphaNumeric ?
1321+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
1322+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
1323+ lockscreen.showInfoPopup(title, text);
1324+ }
1325+
1326+ function hide() {
1327+ lockscreen.hide();
1328+ coverPage.hide();
1329+ }
1330+
1331+ function notifyAuthenticationSucceeded() {
1332+ lockscreen.hide();
1333+ }
1334+
1335+ function notifyAuthenticationFailed() {
1336+ lockscreen.clear(true);
1337+ }
1338+
1339+ function reset() {
1340+ coverPage.show();
1341+ }
1342+
1343+ function tryToUnlock(toTheRight) {
1344+ var coverChanged = coverPage.shown;
1345+ lockscreen.maybeShow();
1346+ if (toTheRight) {
1347+ coverPage.hideRight();
1348+ } else {
1349+ coverPage.hide();
1350+ }
1351+ return coverChanged;
1352+ }
1353+
1354+ onLockedChanged: {
1355+ if (locked) {
1356+ lockscreen.maybeShow();
1357+ } else {
1358+ lockscreen.hide();
1359+ }
1360+ }
1361+
1362+ Lockscreen {
1363+ id: lockscreen
1364+ objectName: "lockscreen"
1365+
1366+ shown: false
1367+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
1368+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
1369+ anchors.fill: parent
1370+ visible: required
1371+ enabled: !coverPage.shown
1372+ background: root.background
1373+ darkenBackground: 0.4
1374+ alphaNumeric: root.alphanumeric
1375+ minPinLength: 4
1376+ maxPinLength: 4
1377+
1378+ property string promptText
1379+ infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
1380+ alphaNumeric ? i18n.tr("Enter passphrase") :
1381+ i18n.tr("Enter passcode")
1382+ errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
1383+ alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
1384+ i18n.tr("Please re-enter") :
1385+ i18n.tr("Sorry, incorrect passcode")
1386+
1387+ onEntered: root.responded(passphrase)
1388+ onCancel: coverPage.show()
1389+ onEmergencyCall: root.emergencyCall()
1390+
1391+ function maybeShow() {
1392+ if (root.locked && !shown) {
1393+ showNow();
1394+ }
1395+ }
1396+ }
1397+
1398+ Rectangle {
1399+ anchors.fill: parent
1400+ color: "black"
1401+ opacity: coverPage.showProgress * 0.8
1402+ }
1403+
1404+ CoverPage {
1405+ id: coverPage
1406+ objectName: "coverPage"
1407+ height: parent.height
1408+ width: parent.width
1409+ background: root.background
1410+ onTease: root.tease()
1411+
1412+ onShowProgressChanged: {
1413+ if (showProgress === 1) {
1414+ lockscreen.reset();
1415+ }
1416+
1417+ if (showProgress === 0) {
1418+ if (root.locked) {
1419+ lockscreen.clear(false); // to reset focus if necessary
1420+ } else {
1421+ root.responded("");
1422+ }
1423+ }
1424+ }
1425+
1426+ Clock {
1427+ anchors {
1428+ top: parent.top
1429+ topMargin: units.gu(2)
1430+ horizontalCenter: parent.horizontalCenter
1431+ }
1432+ }
1433+ }
1434+}
1435
1436=== added file 'qml/Greeter/WideView.qml'
1437--- qml/Greeter/WideView.qml 1970-01-01 00:00:00 +0000
1438+++ qml/Greeter/WideView.qml 2015-02-23 15:44:00 +0000
1439@@ -0,0 +1,134 @@
1440+/*
1441+ * Copyright (C) 2015 Canonical, Ltd.
1442+ *
1443+ * This program is free software; you can redistribute it and/or modify
1444+ * it under the terms of the GNU General Public License as published by
1445+ * the Free Software Foundation; version 3.
1446+ *
1447+ * This program is distributed in the hope that it will be useful,
1448+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1449+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1450+ * GNU General Public License for more details.
1451+ *
1452+ * You should have received a copy of the GNU General Public License
1453+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1454+ */
1455+
1456+import QtQuick 2.3
1457+import Ubuntu.Components 1.1
1458+
1459+FocusScope {
1460+ id: root
1461+
1462+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
1463+ property alias launcherOffset: coverPage.launcherOffset
1464+ property alias currentIndex: loginList.currentIndex
1465+ property int delayMinutes // TODO
1466+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
1467+ property alias background: coverPage.background
1468+ property bool locked
1469+ property bool alphanumeric // unused
1470+ property alias userModel: loginList.model
1471+ property alias infographicModel: coverPage.infographicModel
1472+ readonly property bool fullyShown: coverPage.showProgress === 1
1473+ readonly property bool required: coverPage.required
1474+
1475+ // so that it can be replaced in tests with a mock object
1476+ property var inputMethod: Qt.inputMethod
1477+
1478+ signal selected(int index)
1479+ signal responded(string response)
1480+ signal tease()
1481+ signal emergencyCall() // unused
1482+
1483+ function showMessage(html) {
1484+ loginList.showMessage(html);
1485+ }
1486+
1487+ function showPrompt(text, isSecret, isDefaultPrompt) {
1488+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
1489+ }
1490+
1491+ function showLastChance() {
1492+ // TODO
1493+ }
1494+
1495+ function hide() {
1496+ coverPage.hide();
1497+ }
1498+
1499+ function notifyAuthenticationSucceeded() {
1500+ // Nothing needed
1501+ }
1502+
1503+ function notifyAuthenticationFailed() {
1504+ loginList.showError();
1505+ }
1506+
1507+ function reset() {
1508+ loginList.reset();
1509+ }
1510+
1511+ function tryToUnlock(toTheRight) {
1512+ if (root.locked) {
1513+ coverPage.show();
1514+ loginList.tryToUnlock();
1515+ return false;
1516+ } else {
1517+ var coverChanged = coverPage.shown;
1518+ if (toTheRight) {
1519+ coverPage.hideRight();
1520+ } else {
1521+ coverPage.hide();
1522+ }
1523+ return coverChanged;
1524+ }
1525+ }
1526+
1527+ Rectangle {
1528+ anchors.fill: parent
1529+ color: "black"
1530+ opacity: coverPage.showProgress * 0.8
1531+ }
1532+
1533+ CoverPage {
1534+ id: coverPage
1535+ objectName: "coverPage"
1536+ height: parent.height
1537+ width: parent.width
1538+ draggable: !root.locked
1539+
1540+ infographics {
1541+ height: 0.75 * parent.height
1542+ anchors.leftMargin: loginList.x + loginList.width
1543+ }
1544+
1545+ onTease: root.tease()
1546+
1547+ onShowProgressChanged: {
1548+ if (showProgress === 0 && !root.locked) {
1549+ root.responded("");
1550+ }
1551+ }
1552+
1553+ LoginList {
1554+ id: loginList
1555+ objectName: "loginList"
1556+
1557+ anchors {
1558+ left: parent.left
1559+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
1560+ top: parent.top
1561+ }
1562+ width: units.gu(29)
1563+ height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
1564+ : parent.height
1565+ Behavior on height { UbuntuNumberAnimation {} }
1566+
1567+ locked: root.locked
1568+
1569+ onSelected: root.selected(index)
1570+ onResponded: root.responded(response)
1571+ }
1572+ }
1573+}
1574
1575=== modified file 'qml/Shell.qml'
1576--- qml/Shell.qml 2015-02-11 17:12:49 +0000
1577+++ qml/Shell.qml 2015-02-23 15:44:00 +0000
1578@@ -22,9 +22,7 @@
1579 import Ubuntu.Components 0.1
1580 import Ubuntu.Components.Popups 1.0
1581 import Ubuntu.Gestures 0.1
1582-import Ubuntu.SystemImage 0.1
1583 import Ubuntu.Telephony 0.1 as Telephony
1584-import Unity.Connectivity 0.1
1585 import Unity.Launcher 0.1
1586 import Utils 0.1
1587 import LightDM 0.1 as LightDM
1588@@ -46,9 +44,9 @@
1589 Item {
1590 id: shell
1591
1592- // Disable everything so that user can't swipe greeter or launcher until
1593- // we get first prompt/authenticate, which will re-enable the shell.
1594- enabled: false
1595+ // Disable everything while greeter is waiting, so that the user can't swipe
1596+ // the greeter or launcher until we know whether the session is locked.
1597+ enabled: !greeter.waiting
1598
1599 // this is only here to select the width / height of the window if not running fullscreen
1600 property bool tablet: false
1601@@ -61,18 +59,9 @@
1602 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
1603 readonly property real panelHeight: panel.panelHeight
1604
1605- readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
1606- readonly property alias hasLockedApp: greeter.hasLockedApp
1607- readonly property bool forcedUnlock: tutorial.running
1608- onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
1609-
1610 property bool sideStageEnabled: shell.width >= units.gu(100)
1611 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
1612
1613- property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
1614- property int failedLoginsDelayAttempts: 7 // number of failed logins
1615- property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
1616-
1617 property int orientation
1618 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
1619 onDeviceOrientationAngleChanged: {
1620@@ -99,7 +88,7 @@
1621 }
1622
1623 function startLockedApp(app) {
1624- if (shell.locked) {
1625+ if (greeter.locked) {
1626 greeter.lockedApp = app;
1627 }
1628 shell.activateApplication(app);
1629@@ -117,6 +106,7 @@
1630
1631 GSettings {
1632 id: backgroundSettings
1633+ objectName: "backgroundSettings"
1634 schema.id: "org.gnome.desktop.background"
1635 }
1636
1637@@ -222,53 +212,32 @@
1638
1639 Connections {
1640 target: ApplicationManager
1641- onFocusRequested: {
1642- if (greeter.narrowMode) {
1643- if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
1644- // If we are in the middle of a call, make dialer lockedApp and show it.
1645- // This can happen if user backs out of dialer back to greeter, then
1646- // launches dialer again.
1647- greeter.lockedApp = appId;
1648- }
1649- if (greeter.hasLockedApp) {
1650- if (appId === greeter.lockedApp) {
1651- lockscreen.hide() // show locked app
1652- } else {
1653- greeter.startUnlock() // show lockscreen if necessary
1654- }
1655- }
1656- greeter.hide();
1657- } else {
1658- if (LightDM.Greeter.active) {
1659- greeter.startUnlock()
1660- }
1661- }
1662- }
1663
1664+ // This signal is also fired when we try to focus the current app
1665+ // again. We rely on this!
1666 onFocusedApplicationIdChanged: {
1667- if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
1668- greeter.startUnlock()
1669- }
1670- panel.indicators.hide();
1671- }
1672-
1673- onApplicationAdded: {
1674- if (appId != "unity8-dash") {
1675- if (greeter.shown) {
1676- greeter.startUnlock();
1677- }
1678-
1679+ var appId = ApplicationManager.focusedApplicationId;
1680+
1681+ if (tutorial.running && appId != "unity8-dash") {
1682 // If this happens on first boot, we may be in edge
1683 // tutorial or wizard while receiving a call. But a call
1684 // is more important than wizard so just bail out of those.
1685- if (tutorial.running) {
1686- tutorial.finish();
1687- wizard.hide();
1688- }
1689- }
1690- if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
1691- lockscreen.hide() // show locked app
1692- }
1693+ tutorial.finish();
1694+ wizard.hide();
1695+ }
1696+
1697+ if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
1698+ // If we are in the middle of a call, make dialer lockedApp and show it.
1699+ // This can happen if user backs out of dialer back to greeter, then
1700+ // launches dialer again.
1701+ greeter.lockedApp = appId;
1702+ }
1703+ greeter.notifyAppFocused(appId);
1704+
1705+ panel.indicators.hide();
1706+ }
1707+
1708+ onApplicationAdded: {
1709 launcher.hide();
1710 }
1711 }
1712@@ -292,7 +261,6 @@
1713
1714 property bool interactive: tutorial.spreadEnabled
1715 && !greeter.shown
1716- && !lockscreen.shown
1717 && panel.indicators.fullyClosed
1718 && launcher.progress == 0
1719 && !notifications.useModal
1720@@ -328,7 +296,7 @@
1721 Binding {
1722 target: applicationsDisplayLoader.item
1723 property: "inverseProgress"
1724- value: launcher.progress
1725+ value: greeter.locked ? 0 : launcher.progress
1726 }
1727 Binding {
1728 target: applicationsDisplayLoader.item
1729@@ -379,299 +347,50 @@
1730 }
1731 }
1732
1733- Lockscreen {
1734- id: lockscreen
1735- objectName: "lockscreen"
1736+ Greeter {
1737+ id: greeter
1738+ objectName: "greeter"
1739
1740 hides: [launcher, panel.indicators]
1741- shown: false
1742- enabled: true
1743- showAnimation: StandardAnimation { property: "opacity"; to: 1 }
1744- hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
1745- y: panel.panelHeight
1746- visible: required
1747- width: parent.width
1748- height: parent.height - panel.panelHeight
1749+ tabletMode: shell.sideStageEnabled
1750+ launcherOffset: launcher.progress
1751+ forcedUnlock: tutorial.running
1752 background: shell.background
1753- darkenBackground: 0.4
1754- alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
1755- minPinLength: 4
1756- maxPinLength: 4
1757-
1758- property string promptText
1759- infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
1760- alphaNumeric ? i18n.tr("Enter passphrase") :
1761- i18n.tr("Enter passcode")
1762- errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
1763- alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
1764- i18n.tr("Please re-enter") :
1765- i18n.tr("Sorry, incorrect passcode")
1766-
1767- // FIXME: We *should* show emergency dialer if there is a SIM present,
1768- // regardless of whether the side stage is enabled. But right now,
1769- // the assumption is that narrow screens are phones which have SIMs
1770- // and wider screens are tablets which don't. When we do allow this
1771- // on devices with a side stage and a SIM, work should be done to
1772- // ensure that the main stage is disabled while the dialer is present
1773- // in the side stage. See the FIXME in the stage loader in this file.
1774- showEmergencyCallButton: !shell.sideStageEnabled
1775-
1776- onEntered: LightDM.Greeter.respond(passphrase);
1777- onCancel: greeter.show()
1778+
1779+ anchors.fill: parent
1780+ anchors.topMargin: panel.panelHeight
1781+
1782+ // avoid overlapping with Launcher's edge drag area
1783+ // FIXME: Fix TouchRegistry & friends and remove this workaround
1784+ // Issue involves launcher's DDA getting disabled on a long
1785+ // left-edge drag
1786+ dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
1787+
1788+ onSessionStarted: {
1789+ launcher.hide();
1790+ }
1791+
1792+ onTease: {
1793+ if (!tutorial.running) {
1794+ launcher.tease();
1795+ }
1796+ }
1797+
1798 onEmergencyCall: startLockedApp("dialer-app")
1799
1800- onShownChanged: if (shown) greeter.lockedApp = ""
1801-
1802- function maybeShow() {
1803- if (!shell.forcedUnlock && !shown) {
1804- showNow();
1805- }
1806- }
1807-
1808 Timer {
1809- id: forcedDelayTimer
1810- interval: 1000 * 60
1811+ // See powerConnection for why this is useful
1812+ id: showGreeterDelayed
1813+ interval: 1
1814 onTriggered: {
1815- if (lockscreen.delayMinutes > 0) {
1816- lockscreen.delayMinutes -= 1
1817- if (lockscreen.delayMinutes > 0) {
1818- start() // go again
1819- }
1820- }
1821- }
1822- }
1823-
1824- Component.onCompleted: {
1825- if (greeter.narrowMode) {
1826- LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
1827- }
1828- }
1829- }
1830-
1831- Connections {
1832- target: LightDM.Greeter
1833-
1834- onShowGreeter: greeter.show()
1835- onHideGreeter: greeter.login()
1836-
1837- onShowPrompt: {
1838- shell.enabled = true;
1839- if (!LightDM.Greeter.active) {
1840- return; // could happen if hideGreeter() comes in before we prompt
1841- }
1842- if (greeter.narrowMode) {
1843- lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
1844- lockscreen.maybeShow();
1845- }
1846- }
1847-
1848- onPromptlessChanged: {
1849- if (!LightDM.Greeter.active) {
1850- return; // could happen if hideGreeter() comes in before we prompt
1851- }
1852- if (greeter.narrowMode) {
1853- if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
1854- lockscreen.hide()
1855- } else {
1856- lockscreen.reset();
1857- lockscreen.maybeShow();
1858- }
1859- }
1860- }
1861-
1862- onAuthenticationComplete: {
1863- shell.enabled = true;
1864- if (LightDM.Greeter.authenticated) {
1865- AccountsService.failedLogins = 0
1866- }
1867- // Else only penalize user for a failed login if they actually were
1868- // prompted for a password. We do this below after the promptless
1869- // early exit.
1870-
1871- if (LightDM.Greeter.promptless) {
1872- return;
1873- }
1874-
1875- if (LightDM.Greeter.authenticated) {
1876- greeter.login();
1877- } else {
1878- AccountsService.failedLogins++
1879- if (maxFailedLogins >= 2) { // require at least a warning
1880- if (AccountsService.failedLogins === maxFailedLogins - 1) {
1881- var title = lockscreen.alphaNumeric ?
1882- i18n.tr("Sorry, incorrect passphrase.") :
1883- i18n.tr("Sorry, incorrect passcode.")
1884- var text = i18n.tr("This will be your last attempt.") + " " +
1885- (lockscreen.alphaNumeric ?
1886- i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
1887- i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
1888- lockscreen.showInfoPopup(title, text)
1889- } else if (AccountsService.failedLogins >= maxFailedLogins) {
1890- SystemImage.factoryReset() // Ouch!
1891- }
1892- }
1893- if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
1894- lockscreen.delayMinutes = failedLoginsDelayMinutes
1895- forcedDelayTimer.start()
1896- }
1897-
1898- lockscreen.clear(true);
1899- if (greeter.narrowMode) {
1900- LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
1901- }
1902- }
1903- }
1904- }
1905-
1906- Binding {
1907- target: LightDM.Greeter
1908- property: "active"
1909- value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
1910- }
1911-
1912- Rectangle {
1913- anchors.fill: parent
1914- color: "black"
1915- opacity: greeterWrapper.showProgress * 0.8
1916- }
1917-
1918- Item {
1919- // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
1920- id: greeterWrapper
1921- objectName: "greeterWrapper"
1922- x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
1923- y: panel.panelHeight
1924- width: parent.width
1925- height: parent.height - panel.panelHeight
1926-
1927- Behavior on x {
1928- enabled: !launcher.dashSwipe
1929- StandardAnimation {}
1930- }
1931-
1932- property bool fullyShown: showProgress === 1.0
1933- onFullyShownChanged: {
1934- // Wait until the greeter is completely covering lockscreen before resetting it.
1935- if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
1936- lockscreen.reset();
1937- lockscreen.maybeShow();
1938- }
1939- }
1940-
1941- readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
1942- onShowProgressChanged: {
1943- if (showProgress === 0) {
1944- if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
1945- greeter.login()
1946- } else if (greeter.narrowMode) {
1947- lockscreen.clear(false) // to reset focus if necessary
1948- }
1949- }
1950- }
1951-
1952- Greeter {
1953- id: greeter
1954- objectName: "greeter"
1955-
1956- signal sessionStarted() // helpful for tests
1957-
1958- property string lockedApp: ""
1959- property bool hasLockedApp: lockedApp !== ""
1960-
1961- hides: [launcher, panel.indicators]
1962- loadContent: required || lockscreen.required // keeps content in memory for quick show()
1963-
1964- locked: shell.locked
1965-
1966- background: shell.background
1967-
1968- width: parent.width
1969- height: parent.height
1970-
1971-
1972- // avoid overlapping with Launcher's edge drag area
1973- // FIXME: Fix TouchRegistry & friends and remove this workaround
1974- // Issue involves launcher's DDA getting disabled on a long
1975- // left-edge drag
1976- dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
1977-
1978- function startUnlock() {
1979- if (narrowMode) {
1980- if (!LightDM.Greeter.authenticated) {
1981- lockscreen.maybeShow()
1982- }
1983- hide()
1984- } else {
1985- show()
1986- tryToUnlock()
1987- }
1988- }
1989-
1990- function login() {
1991- enabled = false;
1992- if (LightDM.Greeter.startSessionSync()) {
1993- sessionStarted();
1994- greeter.hide();
1995- lockscreen.hide();
1996- launcher.hide();
1997- }
1998- enabled = true;
1999- }
2000-
2001- Timer {
2002- // See powerConnection for why this is useful
2003- id: showGreeterDelayed
2004- interval: 1
2005- onTriggered: {
2006- greeter.showNow();
2007- }
2008- }
2009-
2010- onShownChanged: {
2011- if (shown) {
2012- // Disable everything so that user can't swipe greeter or
2013- // launcher until we get the next prompt/authenticate, which
2014- // will re-enable the shell.
2015- shell.enabled = false;
2016-
2017- if (greeter.narrowMode) {
2018- LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
2019- } else {
2020- reset()
2021- }
2022- greeter.lockedApp = "";
2023- greeter.forceActiveFocus();
2024- }
2025- }
2026-
2027- Component.onCompleted: {
2028- Connectivity.unlockAllModems()
2029- }
2030-
2031- onUnlocked: greeter.hide()
2032- onSelected: {
2033- // Update launcher items for new user
2034- var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
2035- AccountsService.user = user;
2036- LauncherModel.setUser(user);
2037- }
2038-
2039- onTapped: {
2040- if (!tutorial.running) {
2041- launcher.tease();
2042- }
2043- }
2044- onDraggingChanged: {
2045- if (dragging && !tutorial.running) {
2046- launcher.tease();
2047- }
2048- }
2049-
2050- Binding {
2051- target: ApplicationManager
2052- property: "suspended"
2053- value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
2054- }
2055+ greeter.forceShow();
2056+ }
2057+ }
2058+
2059+ Binding {
2060+ target: ApplicationManager
2061+ property: "suspended"
2062+ value: greeter.shown
2063 }
2064 }
2065
2066@@ -680,7 +399,7 @@
2067 target: callManager
2068
2069 onHasCallsChanged: {
2070- if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
2071+ if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
2072 // We just received an incoming call while locked. The
2073 // indicator will have already launched dialer-app for us, but
2074 // there is a race between "hasCalls" changing and the dialer
2075@@ -720,9 +439,7 @@
2076 return
2077 }
2078
2079- if (LightDM.Greeter.active) {
2080- greeter.startUnlock()
2081- }
2082+ greeter.notifyAboutToFocusApp("unity8-dash");
2083
2084 var animate = !LightDM.Greeter.active && !stages.shown
2085 dash.setCurrentScope(0, animate, false)
2086@@ -730,17 +447,11 @@
2087 }
2088
2089 function showDash() {
2090- if (greeter.hasLockedApp || // just in case user gets here
2091- (!greeter.narrowMode && shell.locked)) {
2092- return
2093- }
2094-
2095- if (greeter.shown) {
2096- greeter.hideRight();
2097+ if (greeter.notifyShowingDashFromDrag()) {
2098 launcher.fadeOut();
2099 }
2100
2101- if (ApplicationManager.focusedApplicationId != "unity8-dash") {
2102+ if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
2103 ApplicationManager.requestFocusApplication("unity8-dash")
2104 launcher.fadeOut();
2105 }
2106@@ -758,7 +469,9 @@
2107 anchors.fill: parent //because this draws indicator menus
2108 indicators {
2109 hides: [launcher]
2110- available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
2111+ available: tutorial.panelEnabled
2112+ && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
2113+ && !greeter.hasLockedApp
2114 contentEnabled: tutorial.panelContentEnabled
2115 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
2116
2117@@ -772,7 +485,7 @@
2118 }
2119 }
2120 callHint {
2121- greeterShown: greeter.shown || lockscreen.shown
2122+ greeterShown: greeter.shown
2123 }
2124
2125 property bool topmostApplicationIsFullscreen:
2126@@ -794,7 +507,9 @@
2127 anchors.bottom: parent.bottom
2128 width: parent.width
2129 dragAreaWidth: shell.edgeSize
2130- available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
2131+ available: tutorial.launcherEnabled
2132+ && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
2133+ && !greeter.hasLockedApp
2134 inverted: usageModeSettings.usageMode === "Staged"
2135 shadeBackground: !tutorial.running
2136
2137@@ -806,11 +521,10 @@
2138 }
2139 }
2140 onLauncherApplicationSelected: {
2141- if (greeter.hasLockedApp) {
2142- greeter.startUnlock()
2143- }
2144- if (!tutorial.running)
2145+ if (!tutorial.running) {
2146+ greeter.notifyAboutToFocusApp(appId);
2147 shell.activateApplication(appId)
2148+ }
2149 }
2150 onShownChanged: {
2151 if (shown) {
2152
2153=== modified file 'tests/autopilot/unity8/shell/emulators/greeter.py'
2154--- tests/autopilot/unity8/shell/emulators/greeter.py 2014-09-08 10:33:50 +0000
2155+++ tests/autopilot/unity8/shell/emulators/greeter.py 2015-02-23 15:44:00 +0000
2156@@ -19,6 +19,7 @@
2157
2158 import ubuntuuitoolkit
2159
2160+from autopilot.utilities import sleep
2161 from unity8.shell.emulators import UnityEmulatorBase
2162
2163
2164@@ -26,12 +27,27 @@
2165 """An emulator that understands the greeter screen."""
2166
2167 def wait_swiped_away(self):
2168- self.showProgress.wait_for(0)
2169+ # We have to be careful here, because coverPage can go away at any time
2170+ # if there isn't a lockscreen behind it (it hides completely, then
2171+ # the greeter disposes it). But if there *is* a lockscreen, then we
2172+ # need a different check, by looking at its showProgress. So make our
2173+ # own wait_for loop and check both possibilities.
2174+ for i in range(10):
2175+ if not self.required:
2176+ return
2177+ coverPage = self.select_single(objectName='coverPage')
2178+ if coverPage.showProgress == 0:
2179+ return
2180+ sleep(1)
2181+
2182+ raise AssertionError("Greeter cover page still up after 10s")
2183+
2184
2185 def swipe(self):
2186 """Swipe the greeter screen away."""
2187- self.created.wait_for(True)
2188- self.showProgress.wait_for(1)
2189+ self.waiting.wait_for(False)
2190+ coverPage = self.select_single(objectName='coverPage')
2191+ coverPage.showProgress.wait_for(1)
2192
2193 rect = self.globalRect
2194 start_x = rect[0] + rect[2] - 3
2195
2196=== modified file 'tests/autopilot/unity8/shell/emulators/main_window.py'
2197--- tests/autopilot/unity8/shell/emulators/main_window.py 2015-01-21 04:11:36 +0000
2198+++ tests/autopilot/unity8/shell/emulators/main_window.py 2015-02-23 15:44:00 +0000
2199@@ -35,12 +35,6 @@
2200 def get_greeter(self):
2201 return self.select_single(Greeter)
2202
2203- def get_greeter_content_loader(self):
2204- return self.wait_select_single(
2205- "QQuickLoader",
2206- objectName="greeterContentLoader"
2207- )
2208-
2209 def get_login_loader(self):
2210 return self.select_single("QQuickLoader", objectName="loginLoader")
2211
2212
2213=== modified file 'tests/autopilot/unity8/shell/tests/__init__.py'
2214--- tests/autopilot/unity8/shell/tests/__init__.py 2015-01-21 13:50:14 +0000
2215+++ tests/autopilot/unity8/shell/tests/__init__.py 2015-02-23 15:44:00 +0000
2216@@ -375,9 +375,8 @@
2217 self._proxy = None
2218
2219 def wait_for_unity(self):
2220- greeter_content_loader = self.main_window.wait_select_single(
2221- objectName='greeterContentLoader')
2222- greeter_content_loader.progress.wait_for(1)
2223+ greeter = self.main_window.wait_select_single(objectName='greeter')
2224+ greeter.waiting.wait_for(False)
2225
2226 def get_dash(self):
2227 pid = process_helpers.get_job_pid('unity8-dash')
2228
2229=== modified file 'tests/autopilot/unity8/shell/tests/test_lock_screen.py'
2230--- tests/autopilot/unity8/shell/tests/test_lock_screen.py 2015-01-21 13:50:14 +0000
2231+++ tests/autopilot/unity8/shell/tests/test_lock_screen.py 2015-02-23 15:44:00 +0000
2232@@ -47,14 +47,13 @@
2233 self.launch_unity()
2234 greeter = self.main_window.get_greeter()
2235
2236- if greeter.narrowMode:
2237+ if not greeter.tabletMode:
2238 greeter.swipe()
2239 lockscreen = self._wait_for_lockscreen()
2240 self.main_window.enter_pin_code("1234")
2241- self.assertThat(lockscreen.shown, Eventually(Equals(False)))
2242 else:
2243 self._enter_prompt_passphrase("1234\n")
2244- self.assertThat(greeter.shown, Eventually(Equals(False)))
2245+ self.assertThat(greeter.shown, Eventually(Equals(False)))
2246
2247 def test_can_unlock_passphrase_screen(self):
2248 """Must be able to unlock the passphrase entry screen."""
2249@@ -63,14 +62,13 @@
2250 self.launch_unity()
2251 greeter = self.main_window.get_greeter()
2252
2253- if greeter.narrowMode:
2254+ if not greeter.tabletMode:
2255 greeter.swipe()
2256 lockscreen = self._wait_for_lockscreen()
2257 self._enter_pin_passphrase("password")
2258- self.assertThat(lockscreen.shown, Eventually(Equals(False)))
2259 else:
2260 self._enter_prompt_passphrase("password")
2261- self.assertThat(greeter.shown, Eventually(Equals(False)))
2262+ self.assertThat(greeter.shown, Eventually(Equals(False)))
2263
2264 def test_pin_screen_wrong_code(self):
2265 """Entering the wrong pin code must not dismiss the lock screen."""
2266@@ -78,18 +76,17 @@
2267 self.launch_unity()
2268 greeter = self.main_window.get_greeter()
2269
2270- if greeter.narrowMode:
2271+ if not greeter.tabletMode:
2272 greeter.swipe()
2273 lockscreen = self._wait_for_lockscreen()
2274 self.main_window.enter_pin_code("4321")
2275 pinentryField = self.main_window.get_pinentryField()
2276 self.assertThat(pinentryField.text, Eventually(Equals("")))
2277- self.assertThat(lockscreen.shown, Eventually(Equals(True)))
2278 else:
2279 self._enter_prompt_passphrase("4231\n")
2280 prompt = self.main_window.get_greeter().get_prompt()
2281 self.assertThat(prompt.text, Eventually(Equals("")))
2282- self.assertThat(greeter.shown, Eventually(Equals(True)))
2283+ self.assertThat(greeter.shown, Eventually(Equals(True)))
2284
2285 def test_passphrase_screen_wrong_password(self):
2286 """Entering the wrong password must not dismiss the lock screen."""
2287@@ -97,18 +94,17 @@
2288 self.launch_unity()
2289 greeter = self.main_window.get_greeter()
2290
2291- if greeter.narrowMode:
2292+ if not greeter.tabletMode:
2293 greeter.swipe()
2294 lockscreen = self._wait_for_lockscreen()
2295 self._enter_pin_passphrase("foobar")
2296 pinentryField = self.main_window.get_pinentryField()
2297 self.assertThat(pinentryField.text, Eventually(Equals("")))
2298- self.assertThat(lockscreen.shown, Eventually(Equals(True)))
2299 else:
2300 self._enter_prompt_passphrase("foobar")
2301 prompt = self.main_window.get_greeter().get_prompt()
2302 self.assertThat(prompt.text, Eventually(Equals("")))
2303- self.assertThat(greeter.shown, Eventually(Equals(True)))
2304+ self.assertThat(greeter.shown, Eventually(Equals(True)))
2305
2306 def _wait_for_lockscreen(self):
2307 """Wait for the lock screen to load, and return it."""
2308
2309=== modified file 'tests/plugins/LightDM/CMakeLists.txt'
2310--- tests/plugins/LightDM/CMakeLists.txt 2015-01-27 15:50:02 +0000
2311+++ tests/plugins/LightDM/CMakeLists.txt 2015-02-23 15:44:00 +0000
2312@@ -4,17 +4,32 @@
2313 )
2314 qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
2315
2316+add_executable(GreeterUsersModelTestExec
2317+ usersmodel.cpp
2318+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
2319+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
2320+ )
2321+qt5_use_modules(GreeterUsersModelTestExec Core Test)
2322+
2323 include_directories(
2324 ${CMAKE_CURRENT_BINARY_DIR}
2325 ${CMAKE_SOURCE_DIR}/plugins/LightDM
2326+ ${CMAKE_SOURCE_DIR}/plugins/Utils
2327 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
2328- ${LIBLIGHTDM_INCLUDE_DIRS}
2329- )
2330-
2331-add_dependencies(GreeterDBusTestExec MockLightDM)
2332-target_link_libraries(GreeterDBusTestExec -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
2333- -llightdm-qt5-2)
2334+ )
2335+
2336+target_link_libraries(GreeterDBusTestExec
2337+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
2338+ -llightdm-qt5-2
2339+ )
2340+
2341+target_link_libraries(GreeterUsersModelTestExec
2342+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
2343+ -llightdm-qt5-2
2344+ )
2345
2346 add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
2347
2348 add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
2349+
2350+add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "LIBLIGHTDM_MOCK_MODE=full")
2351
2352=== added file 'tests/plugins/LightDM/usersmodel.cpp'
2353--- tests/plugins/LightDM/usersmodel.cpp 1970-01-01 00:00:00 +0000
2354+++ tests/plugins/LightDM/usersmodel.cpp 2015-02-23 15:44:00 +0000
2355@@ -0,0 +1,84 @@
2356+/*
2357+ * Copyright (C) 2015 Canonical, Ltd.
2358+ *
2359+ * This program is free software; you can redistribute it and/or modify
2360+ * it under the terms of the GNU General Public License as published by
2361+ * the Free Software Foundation; version 3.
2362+ *
2363+ * This program is distributed in the hope that it will be useful,
2364+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2365+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2366+ * GNU General Public License for more details.
2367+ *
2368+ * You should have received a copy of the GNU General Public License
2369+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2370+ */
2371+
2372+#include "UsersModel.h"
2373+
2374+#include <QLightDM/UsersModel>
2375+#include <QtTest>
2376+
2377+class GreeterUsersModelTest : public QObject
2378+{
2379+ Q_OBJECT
2380+
2381+private:
2382+ static int findName(QAbstractItemModel *model, const QString &name)
2383+ {
2384+ for (int i = 0; i < model->rowCount(QModelIndex()); i++) {
2385+ if (model->data(model->index(i, 0), QLightDM::UsersModel::NameRole).toString() == name) {
2386+ return i;
2387+ }
2388+ }
2389+ return -1;
2390+ }
2391+
2392+ static QString getStringValue(QAbstractItemModel *model, const QString &name, int role)
2393+ {
2394+ int i = findName(model, name);
2395+ return model->data(model->index(i, 0), role).toString();
2396+ }
2397+
2398+private Q_SLOTS:
2399+
2400+ void init()
2401+ {
2402+ model = new UsersModel();
2403+ QVERIFY(model);
2404+ sourceModel = new QLightDM::UsersModel();
2405+ QVERIFY(sourceModel);
2406+ }
2407+
2408+ void cleanup()
2409+ {
2410+ delete model;
2411+ delete sourceModel;
2412+ }
2413+
2414+ void testMangleColor()
2415+ {
2416+ QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
2417+ QVERIFY(background == "#dd4814");
2418+
2419+ background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
2420+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
2421+ }
2422+
2423+ void testMangleEmptyName()
2424+ {
2425+ QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
2426+ QVERIFY(name == "");
2427+
2428+ name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
2429+ QVERIFY(name == "empty-name");
2430+ }
2431+
2432+private:
2433+ UsersModel *model;
2434+ QLightDM::UsersModel *sourceModel;
2435+};
2436+
2437+QTEST_MAIN(GreeterUsersModelTest)
2438+
2439+#include "usersmodel.moc"
2440
2441=== modified file 'tests/qmltests/CMakeLists.txt'
2442--- tests/qmltests/CMakeLists.txt 2015-02-11 17:11:41 +0000
2443+++ tests/qmltests/CMakeLists.txt 2015-02-23 15:44:00 +0000
2444@@ -60,10 +60,11 @@
2445 add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
2446 add_qml_test(Dash/ScopeSettings ScopeSettingString)
2447 add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
2448-add_qml_test(Greeter MultiGreeter)
2449-add_qml_test(Greeter SingleGreeter)
2450+add_qml_test(Greeter Clock)
2451+add_qml_test(Greeter Greeter)
2452 add_qml_test(Greeter Infographics)
2453-add_qml_test(Greeter Clock)
2454+add_qml_test(Greeter NarrowView)
2455+add_qml_test(Greeter WideView)
2456 add_qml_test(Launcher Launcher)
2457 add_qml_test(Notifications Notifications)
2458 add_qml_test(Notifications VisualSnapDecisionsQueue)
2459
2460=== added file 'tests/qmltests/Greeter/TestView.qml'
2461--- tests/qmltests/Greeter/TestView.qml 1970-01-01 00:00:00 +0000
2462+++ tests/qmltests/Greeter/TestView.qml 2015-02-23 15:44:00 +0000
2463@@ -0,0 +1,99 @@
2464+/*
2465+ * Copyright (C) 2015 Canonical, Ltd.
2466+ *
2467+ * This program is free software; you can redistribute it and/or modify
2468+ * it under the terms of the GNU General Public License as published by
2469+ * the Free Software Foundation; version 3.
2470+ *
2471+ * This program is distributed in the hope that it will be useful,
2472+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2473+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2474+ * GNU General Public License for more details.
2475+ *
2476+ * You should have received a copy of the GNU General Public License
2477+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2478+ */
2479+
2480+import QtQuick 2.3
2481+import Ubuntu.Components 1.1
2482+import "../Components"
2483+
2484+Item {
2485+ objectName: "testView"
2486+
2487+ property real dragHandleLeftMargin
2488+ property real launcherOffset
2489+ property int currentIndex
2490+ property int delayMinutes
2491+ property real backgroundTopMargin
2492+ property url background
2493+ property bool locked
2494+ property bool alphanumeric
2495+ property var userModel
2496+ property var infographicModel
2497+ readonly property bool fullyShown: _fullyShown
2498+ readonly property bool required: _required
2499+
2500+ property bool _fullyShown: true
2501+ property bool _required: true
2502+
2503+ signal selected(int index)
2504+ signal responded(string response)
2505+ signal tease()
2506+ signal emergencyCall()
2507+
2508+ signal _showMessageCalled(string html)
2509+ signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
2510+ signal _showLastChanceCalled()
2511+ signal _hideCalled()
2512+ signal _notifyAuthenticationSucceededCalled()
2513+ signal _notifyAuthenticationFailedCalled()
2514+ signal _resetCalled()
2515+ signal _tryToUnlockCalled(bool toTheRight)
2516+
2517+ function showMessage(html) {
2518+ _showMessageCalled(html);
2519+ }
2520+
2521+ function showPrompt(text, isSecret, isDefaultPrompt) {
2522+ _showPromptCalled(text, isSecret, isDefaultPrompt);
2523+ }
2524+
2525+ function showLastChance() {
2526+ _showLastChanceCalled();
2527+ }
2528+
2529+ function hide() {
2530+ _hideCalled();
2531+ _required = false;
2532+ _fullyShown = false;
2533+ }
2534+
2535+ function notifyAuthenticationSucceeded() {
2536+ _notifyAuthenticationSucceededCalled();
2537+ }
2538+
2539+ function notifyAuthenticationFailed() {
2540+ _notifyAuthenticationFailedCalled();
2541+ }
2542+
2543+ function reset() {
2544+ _resetCalled();
2545+ }
2546+
2547+ function tryToUnlock(toTheRight) {
2548+ _tryToUnlockCalled(toTheRight);
2549+ return true;
2550+ }
2551+
2552+ Rectangle {
2553+ anchors.fill: parent
2554+ color: "black"
2555+
2556+ Label {
2557+ text: "Fake view, nothing to see here"
2558+ color: "white"
2559+ anchors.centerIn: parent
2560+ }
2561+ }
2562+}
2563
2564=== added file 'tests/qmltests/Greeter/tst_Greeter.qml'
2565--- tests/qmltests/Greeter/tst_Greeter.qml 1970-01-01 00:00:00 +0000
2566+++ tests/qmltests/Greeter/tst_Greeter.qml 2015-02-23 15:44:00 +0000
2567@@ -0,0 +1,472 @@
2568+/*
2569+ * Copyright 2015 Canonical Ltd.
2570+ *
2571+ * This program is free software; you can redistribute it and/or modify
2572+ * it under the terms of the GNU General Public License as published by
2573+ * the Free Software Foundation; version 3.
2574+ *
2575+ * This program is distributed in the hope that it will be useful,
2576+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2577+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2578+ * GNU General Public License for more details.
2579+ *
2580+ * You should have received a copy of the GNU General Public License
2581+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2582+ */
2583+
2584+import QtQuick 2.0
2585+import QtTest 1.0
2586+import ".."
2587+import "../../../qml/Greeter"
2588+import Ubuntu.Components 0.1
2589+import AccountsService 0.1
2590+import LightDM 0.1 as LightDM
2591+import Unity.Test 0.1 as UT
2592+
2593+Item {
2594+ width: units.gu(120)
2595+ height: units.gu(80)
2596+
2597+ property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
2598+
2599+ Component.onCompleted: {
2600+ // set the mock mode before loading
2601+ LightDM.Greeter.mockMode = "full";
2602+ LightDM.Users.mockMode = "full";
2603+ loader.active = true;
2604+ }
2605+
2606+ Loader {
2607+ id: loader
2608+
2609+ active: false
2610+ anchors.fill: parent
2611+
2612+ property bool itemDestroyed: false
2613+ sourceComponent: Component {
2614+ Greeter {
2615+ width: loader.width
2616+ height: loader.height
2617+ background: defaultBackground
2618+ viewSource: Qt.resolvedUrl("TestView.qml")
2619+
2620+ Component.onDestruction: {
2621+ loader.itemDestroyed = true;
2622+ }
2623+ }
2624+ }
2625+ }
2626+
2627+ SignalSpy {
2628+ id: teaseSpy
2629+ target: loader.item
2630+ signalName: "tease"
2631+ }
2632+
2633+ SignalSpy {
2634+ id: sessionStartedSpy
2635+ target: loader.item
2636+ signalName: "sessionStarted"
2637+ }
2638+
2639+ SignalSpy {
2640+ id: emergencyCallSpy
2641+ target: loader.item
2642+ signalName: "emergencyCall"
2643+ }
2644+
2645+ UT.UnityTestCase {
2646+ id: testCase
2647+ name: "Greeter"
2648+ when: windowShown
2649+
2650+ property Item greeter: loader.status === Loader.Ready ? loader.item : null
2651+ property Item view
2652+
2653+ SignalSpy {
2654+ id: viewShowMessageSpy
2655+ target: testCase.view
2656+ signalName: "_showMessageCalled"
2657+ }
2658+
2659+ SignalSpy {
2660+ id: viewShowPromptSpy
2661+ target: testCase.view
2662+ signalName: "_showPromptCalled"
2663+ }
2664+
2665+ SignalSpy {
2666+ id: viewShowLastChanceSpy
2667+ target: testCase.view
2668+ signalName: "_showLastChanceCalled"
2669+ }
2670+
2671+ SignalSpy {
2672+ id: viewHideSpy
2673+ target: testCase.view
2674+ signalName: "_hideCalled"
2675+ }
2676+
2677+ SignalSpy {
2678+ id: viewAuthenticationSucceededSpy
2679+ target: testCase.view
2680+ signalName: "_notifyAuthenticationSucceededCalled"
2681+ }
2682+
2683+ SignalSpy {
2684+ id: viewAuthenticationFailedSpy
2685+ target: testCase.view
2686+ signalName: "_notifyAuthenticationFailedCalled"
2687+ }
2688+
2689+ SignalSpy {
2690+ id: viewResetSpy
2691+ target: testCase.view
2692+ signalName: "_resetCalled"
2693+ }
2694+
2695+ SignalSpy {
2696+ id: viewTryToUnlockSpy
2697+ target: testCase.view
2698+ signalName: "_tryToUnlockCalled"
2699+ }
2700+
2701+ function init() {
2702+ teaseSpy.clear();
2703+ sessionStartedSpy.clear();
2704+ emergencyCallSpy.clear();
2705+ viewShowMessageSpy.clear();
2706+ viewShowPromptSpy.clear();
2707+ viewShowLastChanceSpy.clear();
2708+ viewHideSpy.clear();
2709+ viewAuthenticationSucceededSpy.clear();
2710+ viewAuthenticationFailedSpy.clear();
2711+ viewResetSpy.clear();
2712+ viewTryToUnlockSpy.clear();
2713+ tryCompare(greeter, "waiting", false);
2714+ view = findChild(greeter, "testView");
2715+ verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
2716+ }
2717+
2718+ function cleanup() {
2719+ loader.itemDestroyed = false;
2720+ loader.active = false;
2721+ tryCompare(loader, "status", Loader.Null);
2722+ tryCompare(loader, "item", null);
2723+ tryCompare(loader, "itemDestroyed", true);
2724+ loader.active = true;
2725+ tryCompare(loader, "status", Loader.Ready);
2726+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
2727+ }
2728+
2729+ function getIndexOf(name) {
2730+ for (var i = 0; i < LightDM.Users.count; i++) {
2731+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
2732+ return i;
2733+ }
2734+ }
2735+ fail("Didn't find name")
2736+ return -1;
2737+ }
2738+
2739+ function selectUser(name) {
2740+ var i = getIndexOf(name);
2741+ view.selected(i);
2742+ verifySelected(name);
2743+ return i;
2744+ }
2745+
2746+ function verifySelected(name) {
2747+ var i = getIndexOf(name);
2748+ compare(view.currentIndex, i);
2749+ compare(AccountsService.user, name);
2750+ compare(LightDM.Greeter.authenticationUser, name);
2751+ }
2752+
2753+ function verifyLoggedIn() {
2754+ tryCompare(sessionStartedSpy, "count", 1);
2755+ verify(viewAuthenticationSucceededSpy.count > 0);
2756+ compare(LightDM.Greeter.authenticated, true);
2757+ compare(greeter.shown, false);
2758+ }
2759+
2760+ function test_unlockPass() {
2761+ selectUser("has-password");
2762+ tryCompare(viewShowPromptSpy, "count", 1);
2763+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2764+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2765+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2766+
2767+ view.responded("password");
2768+ verifyLoggedIn();
2769+ }
2770+
2771+ function test_unlockFail() {
2772+ selectUser("has-password");
2773+ tryCompare(viewShowPromptSpy, "count", 1);
2774+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2775+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2776+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2777+
2778+ view.responded("wr0ng p4ssw0rd");
2779+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
2780+
2781+ tryCompare(viewShowPromptSpy, "count", 2);
2782+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2783+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2784+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2785+ }
2786+
2787+ function test_promptless() {
2788+ selectUser("no-password");
2789+ tryCompare(viewAuthenticationSucceededSpy, "count", 1);
2790+ compare(sessionStartedSpy.count, 1);
2791+ compare(viewShowPromptSpy.count, 0);
2792+ compare(viewHideSpy.count, 0);
2793+ compare(view.locked, false);
2794+ }
2795+
2796+ function test_twoFactorPass() {
2797+ selectUser("two-factor");
2798+ tryCompare(viewShowPromptSpy, "count", 1);
2799+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2800+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2801+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2802+
2803+ view.responded("password");
2804+ tryCompare(viewShowPromptSpy, "count", 2);
2805+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
2806+ compare(viewShowPromptSpy.signalArguments[1][1], false);
2807+ compare(viewShowPromptSpy.signalArguments[1][2], false);
2808+
2809+ view.responded("otp");
2810+ verifyLoggedIn();
2811+ }
2812+
2813+ function test_twoFactorFailOnFirst() {
2814+ selectUser("two-factor");
2815+ tryCompare(viewShowPromptSpy, "count", 1);
2816+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2817+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2818+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2819+
2820+ view.responded("wr0ng p4ssw0rd");
2821+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
2822+
2823+ tryCompare(viewShowPromptSpy, "count", 2);
2824+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2825+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2826+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2827+ }
2828+
2829+ function test_twoFactorFailOnSecond() {
2830+ selectUser("two-factor");
2831+ tryCompare(viewShowPromptSpy, "count", 1);
2832+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2833+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2834+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2835+
2836+ view.responded("password");
2837+ tryCompare(viewShowPromptSpy, "count", 2);
2838+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
2839+ compare(viewShowPromptSpy.signalArguments[1][1], false);
2840+ compare(viewShowPromptSpy.signalArguments[1][2], false);
2841+
2842+ view.responded("wr0ng p4ssw0rd");
2843+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
2844+
2845+ tryCompare(viewShowPromptSpy, "count", 3);
2846+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
2847+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2848+ compare(viewShowPromptSpy.signalArguments[0][2], true);
2849+ }
2850+
2851+ function test_htmlInfoPrompt() {
2852+ selectUser("html-info-prompt");
2853+ tryCompare(viewShowPromptSpy, "count", 1);
2854+ compare(viewShowMessageSpy.count, 1);
2855+ compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
2856+ }
2857+
2858+ function test_multiInfoPrompt() {
2859+ selectUser("multi-info-prompt");
2860+ tryCompare(viewShowPromptSpy, "count", 1);
2861+ compare(viewShowMessageSpy.count, 3);
2862+ compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
2863+ compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
2864+ compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
2865+ }
2866+
2867+ function test_waiting() {
2868+ // Make sure we unset 'waiting' on prompt
2869+ selectUser("has-password");
2870+ compare(greeter.waiting, true);
2871+ tryCompare(greeter, "waiting", false);
2872+
2873+ // Make sure we unset 'waiting' on authentication
2874+ selectUser("no-password");
2875+ compare(greeter.waiting, true);
2876+ tryCompare(greeter, "waiting", false);
2877+ }
2878+
2879+ function test_locked() {
2880+ selectUser("has-password");
2881+ compare(view.locked, true);
2882+
2883+ LightDM.Greeter.active = false;
2884+ compare(view.locked, false);
2885+ LightDM.Greeter.active = true;
2886+
2887+ greeter.forcedUnlock = true;
2888+ compare(view.locked, false);
2889+ greeter.forcedUnlock = false;
2890+
2891+ selectUser("no-password");
2892+ tryCompare(view, "locked", false);
2893+ selectUser("has-password");
2894+ }
2895+
2896+ function test_fullyShown() {
2897+ compare(greeter.fullyShown, true);
2898+ view.hide();
2899+ compare(greeter.fullyShown, false);
2900+ }
2901+
2902+ function test_alphanumeric() {
2903+ selectUser("has-password");
2904+ compare(view.alphanumeric, true);
2905+ selectUser("has-pin");
2906+ compare(view.alphanumeric, false);
2907+ }
2908+
2909+ function test_background() {
2910+ greeter.background = "testing";
2911+ compare(view.background, Qt.resolvedUrl("testing"));
2912+ }
2913+
2914+ function test_notifyAboutToFocusApp() {
2915+ greeter.notifyAboutToFocusApp("fake-app");
2916+ compare(viewTryToUnlockSpy.count, 1);
2917+ compare(viewTryToUnlockSpy.signalArguments[0][0], false);
2918+ }
2919+
2920+ function test_notifyShowingDashFromDrag() {
2921+ compare(greeter.notifyShowingDashFromDrag("fake-app"), true);
2922+ compare(viewTryToUnlockSpy.count, 1);
2923+ compare(viewTryToUnlockSpy.signalArguments[0][0], true);
2924+ }
2925+
2926+ function test_dragHandleLeftMargin() {
2927+ compare(view.dragHandleLeftMargin, 0);
2928+ greeter.dragHandleLeftMargin = 5;
2929+ compare(view.dragHandleLeftMargin, 5);
2930+ }
2931+
2932+ function test_launcherOffset() {
2933+ compare(view.launcherOffset, 0);
2934+ greeter.launcherOffset = 5;
2935+ tryCompare(view, "launcherOffset", 5);
2936+ }
2937+
2938+ function test_laucherOffsetAnimation() {
2939+ // Our logic for smoothing launcherOffset when it suddenly goes to
2940+ // zero is a bit complicated. Let's just make sure it works here.
2941+
2942+ launcherOffsetWatcher.target = view;
2943+
2944+ // should follow immediately
2945+ launcherOffsetWatcher.values = [];
2946+ greeter.launcherOffset = 100;
2947+ compare(view.launcherOffset, 100);
2948+ compare(launcherOffsetWatcher.values.length, 1);
2949+
2950+ // should interpolate values until it reaches 0
2951+ launcherOffsetWatcher.values = [];
2952+ greeter.launcherOffset = 0;
2953+ tryCompare(view, "launcherOffset", 0);
2954+ verify(launcherOffsetWatcher.values.length > 1);
2955+ for (var i = 0; i < launcherOffsetWatcher.values.length - 1; ++i) {
2956+ verify(launcherOffsetWatcher.values[i] > 0.0);
2957+ verify(launcherOffsetWatcher.values[i] < 100.0);
2958+ }
2959+ }
2960+ Connections {
2961+ id: launcherOffsetWatcher
2962+ property var values: []
2963+ onLauncherOffsetChanged: {
2964+ values.push(target.launcherOffset);
2965+ }
2966+ }
2967+
2968+ function test_backgroundTopMargin() {
2969+ compare(view.backgroundTopMargin, 0);
2970+ greeter.y = 5;
2971+ compare(view.backgroundTopMargin, -5);
2972+ }
2973+
2974+ function test_differentPrompt() {
2975+ selectUser("different-prompt");
2976+ tryCompare(viewShowPromptSpy, "count", 1);
2977+ compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
2978+ compare(viewShowPromptSpy.signalArguments[0][1], true);
2979+ compare(viewShowPromptSpy.signalArguments[0][2], false);
2980+ }
2981+
2982+ function test_authError() {
2983+ selectUser("auth-error");
2984+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
2985+ compare(viewShowPromptSpy.count, 0);
2986+ compare(view.locked, true);
2987+ }
2988+
2989+ function test_statsWelcomeScreen() {
2990+ // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
2991+ selectUser("has-password");
2992+ compare(LightDM.Infographic.username, "has-password");
2993+ AccountsService.statsWelcomeScreen = false;
2994+ compare(LightDM.Infographic.username, "");
2995+ AccountsService.statsWelcomeScreen = true;
2996+ compare(LightDM.Infographic.username, "has-password");
2997+ }
2998+
2999+ function test_dbusRequestAuthenticationUser() {
3000+ selectUser("no-password");
3001+ LightDM.Greeter.requestAuthenticationUser("has-password");
3002+ verifySelected("has-password");
3003+ }
3004+
3005+ function test_dbusHideGreeter() {
3006+ compare(view.required, true);
3007+ LightDM.Greeter.hideGreeter();
3008+ compare(view.required, false);
3009+ compare(greeter.required, false);
3010+ }
3011+
3012+ function test_dbusShowGreeterFromHiddenState() {
3013+ greeter.hide();
3014+ compare(greeter.required, false);
3015+
3016+ LightDM.Greeter.showGreeter();
3017+ compare(greeter.required, true);
3018+ compare(greeter.fullyShown, true);
3019+ view = findChild(greeter, "testView");
3020+ compare(view.required, true);
3021+
3022+ // Can't test some of the stuff called on 'view' here because
3023+ // the view was torn down and created again. So the spies missed
3024+ // the good stuff while it down. See next test for more.
3025+ }
3026+
3027+ function test_dbusShowGreeterFromShownState() {
3028+ selectUser("has-password");
3029+ compare(viewResetSpy.count, 1);
3030+ tryCompare(viewShowPromptSpy, "count", 1);
3031+
3032+ viewResetSpy.clear();
3033+ viewShowPromptSpy.clear();
3034+
3035+ LightDM.Greeter.showGreeter();
3036+ compare(viewResetSpy.count, 1);
3037+ }
3038+ }
3039+}
3040
3041=== removed file 'tests/qmltests/Greeter/tst_MultiGreeter.qml'
3042--- tests/qmltests/Greeter/tst_MultiGreeter.qml 2015-02-02 14:28:32 +0000
3043+++ tests/qmltests/Greeter/tst_MultiGreeter.qml 1970-01-01 00:00:00 +0000
3044@@ -1,475 +0,0 @@
3045-/*
3046- * Copyright 2013 Canonical Ltd.
3047- *
3048- * This program is free software; you can redistribute it and/or modify
3049- * it under the terms of the GNU General Public License as published by
3050- * the Free Software Foundation; version 3.
3051- *
3052- * This program is distributed in the hope that it will be useful,
3053- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3054- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3055- * GNU General Public License for more details.
3056- *
3057- * You should have received a copy of the GNU General Public License
3058- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3059- */
3060-
3061-import QtQuick 2.0
3062-import QtQuick.Layouts 1.1
3063-import QtTest 1.0
3064-import ".."
3065-import "../../../qml/Greeter"
3066-import Ubuntu.Components 0.1
3067-import LightDM 0.1 as LightDM
3068-import Unity.Test 0.1 as UT
3069-
3070-Item {
3071- id: root
3072- width: units.gu(140)
3073- height: units.gu(80)
3074-
3075- QtObject {
3076- id: fakeInputMethod
3077- property bool visible: fakeKeyboard.visible
3078- property var keyboardRectangle: QtObject {
3079- property real x: fakeKeyboard.x
3080- property real y: fakeKeyboard.y
3081- property real width: fakeKeyboard.width
3082- property real height: fakeKeyboard.height
3083- }
3084- }
3085-
3086- Binding {
3087- target: LightDM.Greeter
3088- property: "mockMode"
3089- value: "full"
3090- }
3091- Binding {
3092- target: LightDM.Users
3093- property: "mockMode"
3094- value: "full"
3095- }
3096-
3097- Greeter {
3098- id: greeter
3099- width: units.gu(120)
3100- height: root.height
3101- inputMethod: fakeInputMethod
3102- locked: !LightDM.Greeter.authenticated
3103- }
3104-
3105- Component {
3106- id: greeterComponent
3107- Greeter {
3108- SignalSpy {
3109- objectName: "selectedSpy"
3110- target: parent
3111- signalName: "selected"
3112- }
3113- }
3114- }
3115-
3116- Rectangle {
3117- id: fakeKeyboard
3118- color: "green"
3119- opacity: 0.7
3120- anchors.bottom: root.bottom
3121- width: greeter.width
3122- height: greeter.height * 0.6
3123- visible: keyboardVisibleCheckbox.checked
3124- Text {
3125- text: "Keyboard Rectangle"
3126- color: "yellow"
3127- font.bold: true
3128- fontSizeMode: Text.Fit
3129- minimumPixelSize: 10; font.pixelSize: 200
3130- verticalAlignment: Text.AlignVCenter
3131- x: (parent.width - width) / 2
3132- y: (parent.height - height) / 2
3133- width: parent.width
3134- height: parent.height
3135- }
3136- }
3137-
3138- Item {
3139- anchors {
3140- top: root.top
3141- bottom: root.bottom
3142- left: greeter.right
3143- right: root.right
3144- }
3145- RowLayout {
3146- Layout.fillWidth: true
3147- CheckBox {
3148- id: keyboardVisibleCheckbox
3149- }
3150- Label { text: "Keyboard Visible"; anchors.verticalCenter: parent.verticalCenter }
3151- }
3152- }
3153-
3154- SignalSpy {
3155- id: unlockSpy
3156- target: greeter
3157- signalName: "unlocked"
3158- }
3159-
3160- SignalSpy {
3161- id: selectionSpy
3162- target: greeter
3163- signalName: "selected"
3164- }
3165-
3166- SignalSpy {
3167- id: tappedSpy
3168- target: greeter
3169- signalName: "tapped"
3170- }
3171-
3172- UT.UnityTestCase {
3173- name: "MultiGreeter"
3174- when: windowShown
3175-
3176- function cleanup() {
3177- keyboardVisibleCheckbox.checked = false;
3178- }
3179-
3180- function select_index(i) {
3181- // We could be anywhere in list; find target index to know which direction
3182- var userlist = findChild(greeter, "userList")
3183- if (userlist.currentIndex == i)
3184- keyClick(Qt.Key_Escape) // Reset state if we're not moving
3185- while (userlist.currentIndex != i) {
3186- var next = userlist.currentIndex + 1
3187- if (userlist.currentIndex > i) {
3188- next = userlist.currentIndex - 1
3189- }
3190- var account = findChild(greeter, "username"+next)
3191- mouseClick(account, 1, 1)
3192- tryCompare(userlist, "currentIndex", next)
3193- tryCompare(userlist, "movingInternally", false)
3194- }
3195- }
3196-
3197- function select_user(name) {
3198- // We could be anywhere in list; find target index to know which direction
3199- for (var i = 0; i < greeter.model.count; i++) {
3200- if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
3201- break
3202- }
3203- }
3204- if (i == greeter.model.count) {
3205- fail("Didn't find name")
3206- return -1
3207- }
3208- select_index(i)
3209- return i
3210- }
3211-
3212- function test_properties() {
3213- compare(greeter.multiUser, true)
3214- compare(greeter.narrowMode, false)
3215- }
3216-
3217- function test_cycle_data() {
3218- var data = new Array()
3219- for (var i = 0; i < greeter.model.count; i++) {
3220- data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), uid: i }
3221- }
3222- return data
3223- }
3224-
3225- function test_cycle(data) {
3226- selectionSpy.clear();
3227- var userList = findChild(greeter, "userList")
3228- var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
3229- select_index(data.uid)
3230- tryCompare(userList, "currentIndex", data.uid)
3231- tryCompare(greeter, "locked", data.tag !== "no-password")
3232- if (waitForSignal) {
3233- selectionSpy.wait()
3234- tryCompare(selectionSpy, "count", 1)
3235- }
3236- }
3237-
3238- function test_unlock_password() {
3239- select_user("no-password") // to guarantee a selected signal
3240- unlockSpy.clear()
3241- select_user("has-password")
3242- var passwordInput = findChild(greeter, "passwordInput")
3243- tryCompare(passwordInput, "opacity", 1)
3244- mouseClick(passwordInput, 1, 1)
3245- compare(unlockSpy.count, 0)
3246- typeString("password")
3247- keyClick(Qt.Key_Enter)
3248- unlockSpy.wait()
3249- }
3250-
3251- function test_unlock_wrong_password() {
3252- select_user("no-password") // to guarantee a selected signal
3253- unlockSpy.clear()
3254- select_user("has-password")
3255- wait(0) // spin event loop to start any pending animations
3256- var passwordInput = findChild(greeter, "passwordInput")
3257- tryCompare(passwordInput, "opacity", 1) // wait for opacity animation to be finished
3258- mouseClick(passwordInput, 1, 1)
3259- compare(unlockSpy.count, 0)
3260- typeString("wr0ng p4ssw0rd")
3261- keyClick(Qt.Key_Enter)
3262- compare(unlockSpy.count, 0)
3263- }
3264-
3265- function test_unlock_no_password() {
3266- unlockSpy.clear()
3267- select_user("no-password")
3268- var passwordInput = findChild(greeter, "passwordInput")
3269- tryCompare(passwordInput, "opacity", 1)
3270- mouseClick(passwordInput, 1, 1)
3271- unlockSpy.wait()
3272- compare(unlockSpy.count, 1)
3273- }
3274-
3275- function test_empty_name() {
3276- for (var i = 0; i < greeter.model.count; i++) {
3277- if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
3278- compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
3279- return
3280- }
3281- }
3282- fail("Didn't find empty-name")
3283- }
3284-
3285- function test_auth_error() {
3286- select_user("auth-error")
3287- var passwordInput = findChild(greeter, "passwordInput")
3288- tryCompare(passwordInput, "placeholderText", "Retry")
3289- }
3290-
3291- function test_different_prompt() {
3292- select_user("different-prompt")
3293- var passwordInput = findChild(greeter, "passwordInput")
3294- tryCompare(passwordInput, "placeholderText", "Secret word")
3295- }
3296-
3297- function test_no_response() {
3298- unlockSpy.clear()
3299- select_user("no-response")
3300- var passwordInput = findChild(greeter, "passwordInput")
3301- tryCompare(passwordInput, "opacity", 1)
3302- mouseClick(passwordInput, 1, 1)
3303- compare(unlockSpy.count, 0)
3304- typeString("password")
3305- keyClick(Qt.Key_Enter)
3306- tryCompare(passwordInput, "enabled", false)
3307- keyClick(Qt.Key_Escape)
3308- tryCompare(passwordInput, "enabled", true)
3309- compare(unlockSpy.count, 0)
3310- }
3311-
3312- function test_two_factor_correct() {
3313- unlockSpy.clear()
3314- select_user("two-factor")
3315- var passwordInput = findChild(greeter, "passwordInput")
3316- tryCompare(passwordInput, "opacity", 1)
3317- tryCompare(passwordInput, "echoMode", TextInput.Password)
3318- tryCompare(passwordInput, "placeholderText", "Password")
3319- mouseClick(passwordInput, 1, 1)
3320- compare(unlockSpy.count, 0)
3321- typeString("password")
3322- keyClick(Qt.Key_Enter)
3323- tryCompare(passwordInput, "echoMode", TextInput.Normal)
3324- tryCompare(passwordInput, "placeholderText", "otp")
3325- tryCompare(passwordInput, "enabled", true)
3326- typeString("otp")
3327- keyClick(Qt.Key_Enter)
3328- unlockSpy.wait()
3329- }
3330-
3331- function test_two_factor_wrong1() {
3332- unlockSpy.clear()
3333- select_user("two-factor")
3334- var passwordInput = findChild(greeter, "passwordInput")
3335- tryCompare(passwordInput, "opacity", 1)
3336- tryCompare(passwordInput, "placeholderText", "Password")
3337- mouseClick(passwordInput, 1, 1)
3338- compare(unlockSpy.count, 0)
3339- typeString("wr0ng p4ssw0rd")
3340- keyClick(Qt.Key_Enter)
3341- tryCompare(passwordInput, "placeholderText", "Password")
3342- tryCompare(passwordInput, "enabled", true)
3343- compare(unlockSpy.count, 0)
3344- }
3345-
3346- function test_two_factor_wrong2() {
3347- unlockSpy.clear()
3348- select_user("two-factor")
3349- var passwordInput = findChild(greeter, "passwordInput")
3350- tryCompare(passwordInput, "opacity", 1)
3351- tryCompare(passwordInput, "placeholderText", "Password")
3352- mouseClick(passwordInput, 1, 1)
3353- compare(unlockSpy.count, 0)
3354- typeString("password")
3355- keyClick(Qt.Key_Enter)
3356- tryCompare(passwordInput, "placeholderText", "otp")
3357- tryCompare(passwordInput, "enabled", true)
3358- typeString("wr0ng p4ssw0rd")
3359- keyClick(Qt.Key_Enter)
3360- tryCompare(passwordInput, "placeholderText", "Password")
3361- tryCompare(passwordInput, "enabled", true)
3362- compare(unlockSpy.count, 0)
3363- }
3364-
3365- function test_unicode() {
3366- var index = select_user("unicode")
3367- var label = findChild(greeter, "username"+index)
3368- tryCompare(label, "text", "가나다라마")
3369- }
3370-
3371- function test_long_name() {
3372- var index = select_user("long-name")
3373- var label = findChild(greeter, "username"+index)
3374- tryCompare(label, "truncated", true)
3375- }
3376-
3377- function test_info_prompt() {
3378- select_user("info-prompt")
3379- var label = findChild(greeter, "infoLabel")
3380- tryCompare(label, "text", "Welcome to Unity Greeter")
3381- tryCompare(label, "opacity", 1)
3382- tryCompare(label, "clip", true)
3383- tryCompareFunction(function() {return label.contentWidth > label.width;}, false) // c.f. wide-info-prompt
3384- var passwordInput = findChild(greeter, "passwordInput")
3385- mouseClick(passwordInput, 1, 1)
3386- keyClick(Qt.Key_Escape)
3387- }
3388-
3389- function test_info_prompt_escape() {
3390- select_user("info-prompt")
3391- var passwordInput = findChild(greeter, "passwordInput")
3392- mouseClick(passwordInput, 1, 1)
3393- keyClick(Qt.Key_Escape)
3394- var label = findChild(greeter, "infoLabel")
3395- tryCompare(label, "text", "Welcome to Unity Greeter")
3396- tryCompare(label, "opacity", 1)
3397- }
3398-
3399- function test_wide_info_prompt() {
3400- select_user("wide-info-prompt")
3401- var label = findChild(greeter, "infoLabel")
3402- tryCompare(label, "clip", true)
3403- tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
3404- }
3405-
3406- function test_html_info_prompt() {
3407- select_user("html-info-prompt")
3408- var label = findChild(greeter, "infoLabel")
3409- tryCompare(label, "text", "&lt;b&gt;&amp;&lt;/b&gt;")
3410- }
3411-
3412- function test_long_info_prompt() {
3413- select_user("long-info-prompt")
3414- var label = findChild(greeter, "infoLabel")
3415- tryCompare(label, "text", "Welcome to Unity Greeter<br><br>We like to annoy you with super ridiculously long messages.<br>Like this one<br><br>This is the last line of a multiple line message.")
3416- tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
3417- tryCompare(label, "clip", true)
3418- tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
3419- }
3420-
3421- function test_multi_info_prompt() {
3422- select_user("multi-info-prompt")
3423- var label = findChild(greeter, "infoLabel")
3424- tryCompare(label, "text", "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages")
3425- tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
3426- }
3427-
3428- function test_bg_color() {
3429- var index = select_user("color-background")
3430- compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
3431- }
3432-
3433- function test_bg_none() {
3434- var index = select_user("no-background")
3435- compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "")
3436- }
3437-
3438- function test_tappedSignal_data() {
3439- return [
3440- {tag: "left", posX: units.gu(2)},
3441- {tag: "right", posX: greeter.width - units.gu(2)}
3442- ]
3443- }
3444-
3445- function test_tappedSignal(data) {
3446- select_user("no-password");
3447- tappedSpy.clear();
3448- tap(greeter, data.posX, greeter.height - units.gu(1))
3449- tryCompare(tappedSpy, "count", 1)
3450- }
3451-
3452- function test_teaseLockedUnlocked_data() {
3453- return [
3454- {tag: "unlocked", locked: false, narrow: false},
3455- {tag: "locked", locked: true, narrow: false},
3456- ];
3457- }
3458-
3459- function test_teaseLockedUnlocked(data) {
3460- tappedSpy.clear()
3461- greeter.locked = data.locked;
3462-
3463- tap(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
3464-
3465- if (!data.locked || data.narrow) {
3466- tappedSpy.wait()
3467- tryCompare(tappedSpy, "count", 1);
3468- } else {
3469- // waiting 100ms to make sure nothing happens
3470- wait(100);
3471- compare(tappedSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
3472- }
3473-
3474- // Reset value
3475- greeter.locked = false;
3476- }
3477-
3478- function test_dbus_set_active_entry() {
3479- select_user("no-password") // to guarantee a selected signal
3480- selectionSpy.clear()
3481- LightDM.Greeter.requestAuthenticationUser("has-password")
3482-
3483- selectionSpy.wait()
3484- tryCompare(selectionSpy, "count", 1)
3485-
3486- var userlist = findChild(greeter, "userList")
3487- compare(greeter.model.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
3488- }
3489-
3490- function test_initial_selected_signal() {
3491- var greeterObj = greeterComponent.createObject(this)
3492- var spy = findChild(greeterObj, "selectedSpy")
3493- spy.wait()
3494- tryCompare(spy, "count", 1)
3495- greeterObj.destroy()
3496- }
3497-
3498- function test_login_list_not_covered_by_keyboard() {
3499- var loginList = findChild(greeter, "loginLoader").item;
3500- compare(loginList.height, greeter.height);
3501-
3502- // when the vkb shows up, loginList is moved up to remain fully uncovered
3503-
3504- keyboardVisibleCheckbox.checked = true;
3505-
3506- tryCompare(loginList, "height", greeter.height - fakeInputMethod.keyboardRectangle.height);
3507- tryCompareFunction( function() {
3508- var loginListRect = loginList.mapToItem(greeter, 0, 0, loginList.width, loginList.height);
3509- return loginListRect.y + loginListRect.height <= fakeInputMethod.keyboardRectangle.y;
3510- }, true);
3511-
3512- // once the vkb goes away, loginList goes back to its full height
3513-
3514- keyboardVisibleCheckbox.checked = false;
3515-
3516- tryCompare(loginList, "height", greeter.height);
3517- }
3518- }
3519-}
3520
3521=== added file 'tests/qmltests/Greeter/tst_NarrowView.qml'
3522--- tests/qmltests/Greeter/tst_NarrowView.qml 1970-01-01 00:00:00 +0000
3523+++ tests/qmltests/Greeter/tst_NarrowView.qml 2015-02-23 15:44:00 +0000
3524@@ -0,0 +1,522 @@
3525+/*
3526+ * Copyright 2014 Canonical Ltd.
3527+ *
3528+ * This program is free software; you can redistribute it and/or modify
3529+ * it under the terms of the GNU General Public License as published by
3530+ * the Free Software Foundation; version 3.
3531+ *
3532+ * This program is distributed in the hope that it will be useful,
3533+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3534+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3535+ * GNU General Public License for more details.
3536+ *
3537+ * You should have received a copy of the GNU General Public License
3538+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3539+ */
3540+
3541+import QtQuick 2.0
3542+import QtTest 1.0
3543+import ".."
3544+import "../../../qml/Greeter"
3545+import LightDM 0.1 as LightDM
3546+import Ubuntu.Components 0.1
3547+import Unity.Test 0.1 as UT
3548+
3549+Item {
3550+ id: root
3551+ width: units.gu(90)
3552+ height: units.gu(80)
3553+
3554+ Row {
3555+ anchors.fill: parent
3556+ Loader {
3557+ id: loader
3558+ width: root.width - controls.width
3559+ height: parent.height
3560+
3561+ property bool itemDestroyed: false
3562+ sourceComponent: Component {
3563+ NarrowView {
3564+ background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
3565+ userModel: LightDM.Users
3566+ infographicModel: LightDM.Infographic
3567+
3568+ launcherOffset: parseFloat(launcherOffsetField.text)
3569+ currentIndex: parseInt(currentIndexField.text, 10)
3570+ delayMinutes: parseInt(delayMinutesField.text, 10)
3571+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
3572+ locked: lockedCheckBox.checked
3573+ alphanumeric: alphanumericCheckBox.checked
3574+
3575+ Component.onDestruction: {
3576+ loader.itemDestroyed = true
3577+ }
3578+
3579+ onSelected: {
3580+ currentIndexField.text = index;
3581+ }
3582+ }
3583+ }
3584+ }
3585+
3586+ Rectangle {
3587+ id: controls
3588+ color: "white"
3589+ width: units.gu(40)
3590+ height: parent.height
3591+
3592+ Column {
3593+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
3594+ spacing: units.gu(1)
3595+
3596+ Row {
3597+ Button {
3598+ text: "Show Last Chance"
3599+ onClicked: loader.item.showLastChance()
3600+ }
3601+ }
3602+ Row {
3603+ Button {
3604+ text: "Hide"
3605+ onClicked: loader.item.hide()
3606+ }
3607+ }
3608+ Row {
3609+ Button {
3610+ text: "Reset"
3611+ onClicked: loader.item.reset()
3612+ }
3613+ }
3614+ Row {
3615+ Button {
3616+ text: "Show Message"
3617+ onClicked: loader.item.showMessage(messageField.text)
3618+ }
3619+ TextField {
3620+ id: messageField
3621+ width: units.gu(10)
3622+ text: ""
3623+ }
3624+ }
3625+ Row {
3626+ Button {
3627+ text: "Show Prompt"
3628+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
3629+ }
3630+ TextField {
3631+ id: promptField
3632+ width: units.gu(10)
3633+ text: ""
3634+ }
3635+ CheckBox {
3636+ id: isSecretCheckBox
3637+ }
3638+ Label {
3639+ text: "secret"
3640+ }
3641+ CheckBox {
3642+ id: isDefaultPromptCheckBox
3643+ }
3644+ Label {
3645+ text: "default"
3646+ }
3647+ }
3648+ Row {
3649+ Button {
3650+ text: "Authenticated"
3651+ onClicked: {
3652+ if (successCheckBox.checked) {
3653+ loader.item.notifyAuthenticationSucceeded();
3654+ } else {
3655+ loader.item.notifyAuthenticationFailed();
3656+ }
3657+ }
3658+ }
3659+ CheckBox {
3660+ id: successCheckBox
3661+ }
3662+ Label {
3663+ text: "success"
3664+ }
3665+ }
3666+ Row {
3667+ Button {
3668+ text: "Try To Unlock"
3669+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
3670+ }
3671+ CheckBox {
3672+ id: toTheRightCheckBox
3673+ }
3674+ Label {
3675+ text: "toTheRight"
3676+ }
3677+ }
3678+ Row {
3679+ TextField {
3680+ id: launcherOffsetField
3681+ width: units.gu(10)
3682+ text: "0"
3683+ }
3684+ Label {
3685+ text: "launcherOffset"
3686+ }
3687+ }
3688+ Row {
3689+ TextField {
3690+ id: currentIndexField
3691+ width: units.gu(10)
3692+ text: "0"
3693+ }
3694+ Label {
3695+ text: "currentIndex"
3696+ }
3697+ }
3698+ Row {
3699+ TextField {
3700+ id: delayMinutesField
3701+ width: units.gu(10)
3702+ text: "0"
3703+ }
3704+ Label {
3705+ text: "delayMinutes"
3706+ }
3707+ }
3708+ Row {
3709+ TextField {
3710+ id: backgroundTopMarginField
3711+ width: units.gu(10)
3712+ text: "0"
3713+ }
3714+ Label {
3715+ text: "backgroundTopMargin"
3716+ }
3717+ }
3718+ Row {
3719+ CheckBox {
3720+ id: lockedCheckBox
3721+ }
3722+ Label {
3723+ text: "locked"
3724+ }
3725+ }
3726+ Row {
3727+ CheckBox {
3728+ id: alphanumericCheckBox
3729+ }
3730+ Label {
3731+ text: "alphanumeric"
3732+ }
3733+ }
3734+ Row {
3735+ Label {
3736+ text: "selected: " + selectedSpy.count
3737+ }
3738+ }
3739+ Row {
3740+ Label {
3741+ text: "responded: " + respondedSpy.count
3742+ }
3743+ }
3744+ Row {
3745+ Label {
3746+ text: "teased: " + teaseSpy.count
3747+ }
3748+ }
3749+ Row {
3750+ Label {
3751+ text: "emergency: " + emergencySpy.count
3752+ }
3753+ }
3754+ Row {
3755+ Button {
3756+ text: "Reload View"
3757+ onClicked: {
3758+ loader.active = false;
3759+ loader.active = true;
3760+ }
3761+ }
3762+ }
3763+ }
3764+ }
3765+ }
3766+
3767+ Binding {
3768+ target: LightDM.Infographic
3769+ property: "username"
3770+ value: "single"
3771+ }
3772+
3773+ SignalSpy {
3774+ id: selectedSpy
3775+ target: loader.item
3776+ signalName: "selected"
3777+ }
3778+
3779+ SignalSpy {
3780+ id: respondedSpy
3781+ target: loader.item
3782+ signalName: "responded"
3783+ }
3784+
3785+ SignalSpy {
3786+ id: teaseSpy
3787+ target: loader.item
3788+ signalName: "tease"
3789+ }
3790+
3791+ SignalSpy {
3792+ id: emergencySpy
3793+ target: loader.item
3794+ signalName: "emergencyCall"
3795+ }
3796+
3797+ SignalSpy {
3798+ id: infographicDataChangedSpy
3799+ target: LightDM.Infographic
3800+ signalName: "dataChanged"
3801+ }
3802+
3803+ UT.UnityTestCase {
3804+ name: "NarrowView"
3805+ when: windowShown
3806+
3807+ property Item view: loader.status === Loader.Ready ? loader.item : null
3808+
3809+ function init() {
3810+ view.currentIndex = 0; // break binding with text field
3811+
3812+ selectedSpy.clear();
3813+ respondedSpy.clear();
3814+ teaseSpy.clear();
3815+ emergencySpy.clear();
3816+ infographicDataChangedSpy.clear();
3817+ }
3818+
3819+ function cleanup() {
3820+ loader.itemDestroyed = false;
3821+ loader.active = false;
3822+ tryCompare(loader, "status", Loader.Null);
3823+ tryCompare(loader, "item", null);
3824+ tryCompare(loader, "itemDestroyed", true);
3825+ loader.active = true;
3826+ tryCompare(loader, "status", Loader.Ready);
3827+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
3828+ }
3829+
3830+ function swipeAwayCover(toTheRight) {
3831+ if (toTheRight === undefined) {
3832+ toTheRight = false;
3833+ }
3834+
3835+ tryCompare(view, "fullyShown", true);
3836+ var touchY = view.height / 2;
3837+ if (toTheRight) {
3838+ touchFlick(view, 0, touchY, view.width, touchY);
3839+ } else {
3840+ touchFlick(view, view.width, touchY, 0, touchY);
3841+ }
3842+ var coverPage = findChild(view, "coverPage");
3843+ tryCompare(coverPage, "showProgress", 0);
3844+ waitForRendering(view);
3845+ }
3846+
3847+ function enterPin(pin) {
3848+ for (var i = 0; i < pin.length; ++i) {
3849+ var character = pin.charAt(i);
3850+ var button = findChild(view, "pinPadButton" + character);
3851+ tap(button);
3852+ }
3853+ }
3854+
3855+ function test_tease_data() {
3856+ return [
3857+ {tag: "left", x: 0, offset: 0, count: 1},
3858+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
3859+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
3860+ {tag: "right", x: view.width, offset: 0, count: 1},
3861+ ]
3862+ }
3863+ function test_tease(data) {
3864+ view.dragHandleLeftMargin = data.offset;
3865+ tap(view, data.x, 0);
3866+ compare(teaseSpy.count, data.count);
3867+ }
3868+
3869+ function test_respondedWithPin() {
3870+ view.locked = true;
3871+ swipeAwayCover();
3872+ enterPin("1234");
3873+ compare(respondedSpy.count, 1);
3874+ compare(respondedSpy.signalArguments[0][0], "1234");
3875+ }
3876+
3877+ function test_respondedWithPassphrase() {
3878+ view.locked = true;
3879+ view.alphanumeric = true;
3880+ swipeAwayCover();
3881+ typeString("test");
3882+ keyClick(Qt.Key_Enter);
3883+ compare(respondedSpy.count, 1);
3884+ compare(respondedSpy.signalArguments[0][0], "test");
3885+ }
3886+
3887+ function test_respondedWithSwipe_data() {
3888+ return [
3889+ {tag: "left", toTheRight: false, hiddenX: -view.width},
3890+ {tag: "right", toTheRight: true, hiddenX: view.width},
3891+ ];
3892+ }
3893+ function test_respondedWithSwipe(data) {
3894+ swipeAwayCover(data.toTheRight);
3895+ var coverPage = findChild(view, "coverPage");
3896+ compare(coverPage.x, data.hiddenX);
3897+ compare(respondedSpy.count, 1);
3898+ compare(respondedSpy.signalArguments[0][0], "");
3899+ }
3900+
3901+ function test_emergencyCall() {
3902+ view.locked = true;
3903+ swipeAwayCover();
3904+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
3905+ tap(emergencyCallLabel);
3906+ compare(emergencySpy.count, 1);
3907+ }
3908+
3909+ function test_fullyShown() {
3910+ tryCompare(view, "fullyShown", true);
3911+ swipeAwayCover();
3912+ tryCompare(view, "fullyShown", false);
3913+ view.locked = true;
3914+ tryCompare(view, "fullyShown", true);
3915+ view.locked = false;
3916+ tryCompare(view, "fullyShown", false);
3917+ }
3918+
3919+ function test_required() {
3920+ tryCompare(view, "required", true);
3921+ swipeAwayCover();
3922+ tryCompare(view, "required", false);
3923+ view.locked = true;
3924+ tryCompare(view, "required", true);
3925+ view.locked = false;
3926+ tryCompare(view, "required", false);
3927+ }
3928+
3929+ function test_tryToUnlock() {
3930+ var coverPage = findChild(view, "coverPage");
3931+ tryCompare(coverPage, "showProgress", 1);
3932+ compare(view.tryToUnlock(false), true);
3933+ tryCompare(coverPage, "showProgress", 0);
3934+ compare(view.tryToUnlock(false), false);
3935+ }
3936+
3937+ /*
3938+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
3939+ "User metrics can no longer be changed by double tap"
3940+ */
3941+ function test_doubleTapSwitchesToNextInfographic() {
3942+ var infographicPrivate = findInvisibleChild(view, "infographicPrivate");
3943+ verify(infographicPrivate);
3944+
3945+ // wait for the UI to settle down before double tapping it
3946+ tryCompare(infographicPrivate, "animating", false);
3947+
3948+ var dataCircle = findChild(view, "dataCircle");
3949+ verify(dataCircle);
3950+
3951+ tap(dataCircle);
3952+ wait(1);
3953+ tap(dataCircle);
3954+
3955+ tryCompare(infographicDataChangedSpy, "count", 1);
3956+ }
3957+
3958+ function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
3959+ var coverPage = findChild(view, "coverPage");
3960+
3961+ var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
3962+ verify(dragEvaluator);
3963+
3964+ // Make it easier to get a rejection/rollback. Otherwise would have to inject
3965+ // a fake timer into dragEvaluator.
3966+ // Afterall, we are testing if the CoverPage indeed moves back on a
3967+ // rollback decision, not the drag evaluation itself.
3968+ dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
3969+
3970+ // it starts as fully shown
3971+ compare(coverPage.x, 0);
3972+
3973+ // then we drag it a bit
3974+ var startX = coverPage.width - 1;
3975+ var touchY = coverPage.height / 2;
3976+ var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
3977+ touchFlick(coverPage,
3978+ startX , touchY, // start pos
3979+ startX + dragXDelta, touchY, // end pos
3980+ true /* beginTouch */, false /* endTouch */);
3981+
3982+ // which should make it move a bit
3983+ tryCompareFunction(function() {return coverPage.x < 0;}, true);
3984+
3985+ // then we release it
3986+ touchRelease(coverPage, startX + dragXDelta, touchY);
3987+
3988+ // which should make it move back into its original position as it didn't move
3989+ // far enough to have it hidden
3990+ tryCompare(coverPage, "x", 0);
3991+ }
3992+
3993+ function test_dragToHide_data() {
3994+ return [
3995+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
3996+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
3997+ ];
3998+ }
3999+ function test_dragToHide(data) {
4000+ var coverPage = findChild(view, "coverPage");
4001+ compare(coverPage.x, 0);
4002+ compare(coverPage.visible, true);
4003+ compare(coverPage.shown, true);
4004+ compare(coverPage.showProgress, 1);
4005+ compare(view.fullyShown, true);
4006+
4007+ touchFlick(view,
4008+ data.startX, view.height / 2, // start pos
4009+ data.endX, view.height / 2); // end pos
4010+
4011+ tryCompare(coverPage, "x", data.hiddenX);
4012+ tryCompare(coverPage, "visible", false);
4013+ tryCompare(coverPage, "shown", false);
4014+ tryCompare(coverPage, "showProgress", 0);
4015+ compare(view.fullyShown, false);
4016+ }
4017+
4018+ function test_hiddenViewRemainsHiddenAfterResize_data() {
4019+ return [
4020+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
4021+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
4022+ ];
4023+ }
4024+ function test_hiddenViewRemainsHiddenAfterResize(data) {
4025+ touchFlick(view,
4026+ data.startX, view.height / 2, // start pos
4027+ data.endX, view.height / 2); // end pos
4028+
4029+ var coverPage = findChild(view, "coverPage");
4030+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
4031+ tryCompare(coverPage, "visible", false);
4032+ tryCompare(coverPage, "shown", false);
4033+ tryCompare(coverPage, "showProgress", 0);
4034+
4035+ // flip dimensions to simulate an orientation change
4036+ view.width = loader.height;
4037+ view.height = loader.width;
4038+
4039+ // All properties should remain consistent
4040+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
4041+ tryCompare(coverPage, "visible", false);
4042+ tryCompare(coverPage, "shown", false);
4043+ tryCompare(coverPage, "showProgress", 0);
4044+ }
4045+ }
4046+}
4047
4048=== removed file 'tests/qmltests/Greeter/tst_SingleGreeter.qml'
4049--- tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-12-08 18:08:38 +0000
4050+++ tests/qmltests/Greeter/tst_SingleGreeter.qml 1970-01-01 00:00:00 +0000
4051@@ -1,244 +0,0 @@
4052-/*
4053- * Copyright 2013 Canonical Ltd.
4054- *
4055- * This program is free software; you can redistribute it and/or modify
4056- * it under the terms of the GNU General Public License as published by
4057- * the Free Software Foundation; version 3.
4058- *
4059- * This program is distributed in the hope that it will be useful,
4060- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4061- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4062- * GNU General Public License for more details.
4063- *
4064- * You should have received a copy of the GNU General Public License
4065- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4066- */
4067-
4068-import QtQuick 2.0
4069-import QtTest 1.0
4070-import ".."
4071-import "../../../qml/Greeter"
4072-import AccountsService 0.1
4073-import LightDM 0.1 as LightDM
4074-import Ubuntu.Components 0.1
4075-import Unity.Test 0.1 as UT
4076-
4077-Rectangle {
4078- color: "darkblue"
4079- width: units.gu(60)
4080- height: units.gu(80)
4081-
4082- Button {
4083- anchors.centerIn: parent
4084- text: "Show Greeter"
4085- onClicked: {
4086- if (greeterLoader.item)
4087- greeterLoader.item.show();
4088- }
4089- }
4090-
4091- Loader {
4092- id: greeterLoader
4093- anchors.fill: parent
4094-
4095- property bool itemDestroyed: false
4096-
4097- sourceComponent: Component {
4098- Greeter {
4099- width: greeterLoader.width
4100- height: greeterLoader.height
4101-
4102- Component.onDestruction: {
4103- greeterLoader.itemDestroyed = true;
4104- }
4105- SignalSpy {
4106- objectName: "selectedSpy"
4107- target: parent
4108- signalName: "selected"
4109- }
4110- }
4111- }
4112- }
4113-
4114- SignalSpy {
4115- id: unlockSpy
4116- target: greeterLoader.item
4117- signalName: "unlocked"
4118- }
4119-
4120- SignalSpy {
4121- id: tappedSpy
4122- target: greeterLoader.item
4123- signalName: "tapped"
4124- }
4125-
4126- SignalSpy {
4127- id: infographicDataChangedSpy
4128- target: LightDM.Infographic
4129- signalName: "dataChanged"
4130- }
4131-
4132- UT.UnityTestCase {
4133- name: "SingleGreeter"
4134- when: windowShown
4135-
4136- property Greeter greeter: greeterLoader.item
4137-
4138- function cleanup() {
4139- AccountsService.statsWelcomeScreen = true
4140-
4141- // force a reload so that we get a fresh Greeter for the next test
4142- greeterLoader.itemDestroyed = false;
4143- greeterLoader.active = false;
4144- tryCompare(greeterLoader, "itemDestroyed", true);
4145-
4146- unlockSpy.clear();
4147- tappedSpy.clear();
4148-
4149- greeterLoader.active = true;
4150- tryCompare(greeterLoader, "status", Loader.Ready);
4151- removeTimeConstraintsFromDirectionalDragAreas(greeterLoader.item);
4152- }
4153-
4154- function test_properties() {
4155- compare(greeter.multiUser, false)
4156- compare(greeter.narrowMode, true)
4157- }
4158-
4159- function test_teasingArea_data() {
4160- return [
4161- {tag: "left", posX: units.gu(2), leftPressed: true, rightPressed: false},
4162- {tag: "right", posX: greeter.width - units.gu(2), leftPressed: false, rightPressed: true}
4163- ]
4164- }
4165-
4166- function test_teasingArea(data) {
4167- tappedSpy.clear()
4168- tap(greeter, data.posX, greeter.height - units.gu(1))
4169- tappedSpy.wait()
4170- tryCompare(tappedSpy, "count", 1)
4171- }
4172-
4173- function test_statsWelcomeScreen() {
4174- // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
4175- compare(AccountsService.statsWelcomeScreen, true)
4176- tryCompare(LightDM.Infographic, "username", "single")
4177- AccountsService.statsWelcomeScreen = false
4178- tryCompare(LightDM.Infographic, "username", "")
4179- AccountsService.statsWelcomeScreen = true
4180- tryCompare(LightDM.Infographic, "username", "single")
4181- }
4182-
4183- function test_initial_selected_signal() {
4184- var selectedSpy = findChild(greeter, "selectedSpy");
4185- selectedSpy.wait();
4186- tryCompare(selectedSpy, "count", 1);
4187- }
4188-
4189- /*
4190- Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
4191- "User metrics can no longer be changed by double tap"
4192- */
4193- function test_doubleTapSwitchesToNextInfographic() {
4194- infographicDataChangedSpy.clear();
4195-
4196- var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
4197- verify(infographicPrivate);
4198-
4199- // wait for the UI to settle down before double tapping it
4200- tryCompare(infographicPrivate, "animating", false);
4201-
4202- var dataCircle = findChild(greeter, "dataCircle");
4203- verify(dataCircle);
4204-
4205- tap(dataCircle);
4206- wait(1);
4207- tap(dataCircle);
4208-
4209- tryCompare(infographicDataChangedSpy, "count", 1);
4210- }
4211-
4212- function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
4213-
4214- var dragEvaluator = findInvisibleChild(greeter, "edgeDragEvaluator");
4215- verify(dragEvaluator);
4216-
4217- // Make it easier to get a rejection/rollback. Otherwise would have to inject
4218- // a fake timer into dragEvaluator.
4219- // Afterall, we are testing if the Greeter indeed moves back on a
4220- // rollback decision, not the drag evaluation itself.
4221- dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
4222-
4223- // it starts as fully shown
4224- compare(greeter.x, 0);
4225-
4226- // then we drag it a bit
4227- var startX = greeter.width - 1;
4228- var touchY = greeter.height / 2;
4229- var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
4230- touchFlick(greeter,
4231- startX , touchY, // start pos
4232- startX + dragXDelta, touchY, // end pos
4233- true /* beginTouch */, false /* endTouch */);
4234-
4235- // which should make it move a bit
4236- tryCompareFunction(function(){return greeter.x < 0;}, true);
4237-
4238- // then we release it
4239- touchRelease(greeter, startX + dragXDelta, touchY);
4240-
4241- // which should make it move back into its original position as it didn't move
4242- // far enough to have it hidden
4243- tryCompare(greeter, "x", 0);
4244- }
4245-
4246- function test_dragToHide_data() {
4247- return [
4248- {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1, hiddenX: -greeter.width},
4249- {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95, hiddenX: greeter.width},
4250- ];
4251- }
4252- function test_dragToHide(data) {
4253- compare(greeter.x, 0);
4254- compare(greeter.visible, true);
4255- compare(greeter.shown, true);
4256- compare(greeter.showProgress, 1);
4257-
4258- touchFlick(greeter,
4259- data.startX, greeter.height / 2, // start pos
4260- data.endX, greeter.height / 2); // end pos
4261-
4262- tryCompare(greeter, "x", data.hiddenX);
4263- tryCompare(greeter, "visible", false);
4264- tryCompare(greeter, "shown", false);
4265- tryCompare(greeter, "showProgress", 0);
4266- }
4267-
4268- function test_hiddenGreeterRemainsHiddenAfterResize_data() {
4269- return [
4270- {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1},
4271- {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95},
4272- ];
4273- }
4274- function test_hiddenGreeterRemainsHiddenAfterResize(data) {
4275- touchFlick(greeter,
4276- data.startX, greeter.height / 2, // start pos
4277- data.endX, greeter.height / 2); // end pos
4278-
4279- tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
4280- tryCompare(greeter, "visible", false);
4281- tryCompare(greeter, "shown", false);
4282- tryCompare(greeter, "showProgress", 0);
4283-
4284- // flip dimensions to simulate an orientation change
4285- greeter.width = greeterLoader.height;
4286- greeter.height = greeterLoader.width;
4287-
4288- // All properties should remain consistent
4289- tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
4290- tryCompare(greeter, "visible", false);
4291- tryCompare(greeter, "shown", false);
4292- tryCompare(greeter, "showProgress", 0);
4293- }
4294- }
4295-}
4296
4297=== added file 'tests/qmltests/Greeter/tst_WideView.qml'
4298--- tests/qmltests/Greeter/tst_WideView.qml 1970-01-01 00:00:00 +0000
4299+++ tests/qmltests/Greeter/tst_WideView.qml 2015-02-23 15:44:00 +0000
4300@@ -0,0 +1,519 @@
4301+/*
4302+ * Copyright 2014 Canonical Ltd.
4303+ *
4304+ * This program is free software; you can redistribute it and/or modify
4305+ * it under the terms of the GNU General Public License as published by
4306+ * the Free Software Foundation; version 3.
4307+ *
4308+ * This program is distributed in the hope that it will be useful,
4309+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4310+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4311+ * GNU General Public License for more details.
4312+ *
4313+ * You should have received a copy of the GNU General Public License
4314+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4315+ */
4316+
4317+import QtQuick 2.0
4318+import QtTest 1.0
4319+import ".."
4320+import "../../../qml/Greeter"
4321+import LightDM 0.1 as LightDM
4322+import Ubuntu.Components 0.1
4323+import Unity.Test 0.1 as UT
4324+
4325+Item {
4326+ id: root
4327+ width: units.gu(120)
4328+ height: units.gu(80)
4329+
4330+ Binding {
4331+ target: LightDM.Users
4332+ property: "mockMode"
4333+ value: "full"
4334+ }
4335+
4336+ Row {
4337+ anchors.fill: parent
4338+ Loader {
4339+ id: loader
4340+ width: root.width - controls.width
4341+ height: parent.height
4342+
4343+ property bool itemDestroyed: false
4344+ sourceComponent: Component {
4345+ WideView {
4346+ id: view
4347+
4348+ background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
4349+ userModel: LightDM.Users
4350+ infographicModel: LightDM.Infographic
4351+
4352+ launcherOffset: parseFloat(launcherOffsetField.text)
4353+ currentIndex: parseInt(currentIndexField.text, 10)
4354+ delayMinutes: parseInt(delayMinutesField.text, 10)
4355+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
4356+ locked: lockedCheckBox.checked
4357+ inputMethod: fakeInputMethod
4358+
4359+ Component.onDestruction: {
4360+ loader.itemDestroyed = true
4361+ }
4362+
4363+ onSelected: {
4364+ currentIndexField.text = index;
4365+ }
4366+
4367+ QtObject {
4368+ id: fakeInputMethod
4369+ property bool visible: fakeKeyboard.visible
4370+ property var keyboardRectangle: QtObject {
4371+ property real x: fakeKeyboard.x
4372+ property real y: fakeKeyboard.y
4373+ property real width: fakeKeyboard.width
4374+ property real height: fakeKeyboard.height
4375+ }
4376+ }
4377+
4378+ Rectangle {
4379+ id: fakeKeyboard
4380+ color: "green"
4381+ opacity: 0.7
4382+ anchors.bottom: view.bottom
4383+ width: view.width
4384+ height: view.height * 0.6
4385+ visible: keyboardVisibleCheckBox.checked
4386+ Text {
4387+ text: "Keyboard Rectangle"
4388+ color: "yellow"
4389+ font.bold: true
4390+ fontSizeMode: Text.Fit
4391+ minimumPixelSize: 10; font.pixelSize: 200
4392+ verticalAlignment: Text.AlignVCenter
4393+ x: (parent.width - width) / 2
4394+ y: (parent.height - height) / 2
4395+ width: parent.width
4396+ height: parent.height
4397+ }
4398+ }
4399+
4400+ }
4401+ }
4402+ }
4403+
4404+ Rectangle {
4405+ id: controls
4406+ color: "white"
4407+ width: units.gu(40)
4408+ height: parent.height
4409+
4410+ Column {
4411+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
4412+ spacing: units.gu(1)
4413+
4414+ Row {
4415+ Button {
4416+ text: "Show Last Chance"
4417+ onClicked: loader.item.showLastChance()
4418+ }
4419+ }
4420+ Row {
4421+ Button {
4422+ text: "Hide"
4423+ onClicked: loader.item.hide()
4424+ }
4425+ }
4426+ Row {
4427+ Button {
4428+ text: "Reset"
4429+ onClicked: loader.item.reset()
4430+ }
4431+ }
4432+ Row {
4433+ Button {
4434+ text: "Show Message"
4435+ onClicked: loader.item.showMessage(messageField.text)
4436+ }
4437+ TextField {
4438+ id: messageField
4439+ width: units.gu(10)
4440+ text: ""
4441+ }
4442+ }
4443+ Row {
4444+ Button {
4445+ text: "Show Prompt"
4446+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
4447+ }
4448+ TextField {
4449+ id: promptField
4450+ width: units.gu(10)
4451+ text: ""
4452+ }
4453+ CheckBox {
4454+ id: isSecretCheckBox
4455+ }
4456+ Label {
4457+ text: "secret"
4458+ }
4459+ CheckBox {
4460+ id: isDefaultPromptCheckBox
4461+ }
4462+ Label {
4463+ text: "default"
4464+ }
4465+ }
4466+ Row {
4467+ Button {
4468+ text: "Authenticated"
4469+ onClicked: {
4470+ if (successCheckBox.checked) {
4471+ loader.item.notifyAuthenticationSucceeded();
4472+ } else {
4473+ loader.item.notifyAuthenticationFailed();
4474+ }
4475+ }
4476+ }
4477+ CheckBox {
4478+ id: successCheckBox
4479+ }
4480+ Label {
4481+ text: "success"
4482+ }
4483+ }
4484+ Row {
4485+ Button {
4486+ text: "Try To Unlock"
4487+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
4488+ }
4489+ CheckBox {
4490+ id: toTheRightCheckBox
4491+ }
4492+ Label {
4493+ text: "toTheRight"
4494+ }
4495+ }
4496+ Row {
4497+ TextField {
4498+ id: launcherOffsetField
4499+ width: units.gu(10)
4500+ text: "0"
4501+ }
4502+ Label {
4503+ text: "launcherOffset"
4504+ }
4505+ }
4506+ Row {
4507+ TextField {
4508+ id: currentIndexField
4509+ width: units.gu(10)
4510+ text: "0"
4511+ }
4512+ Label {
4513+ text: "currentIndex"
4514+ }
4515+ }
4516+ Row {
4517+ TextField {
4518+ id: delayMinutesField
4519+ width: units.gu(10)
4520+ text: "0"
4521+ }
4522+ Label {
4523+ text: "delayMinutes"
4524+ }
4525+ }
4526+ Row {
4527+ TextField {
4528+ id: backgroundTopMarginField
4529+ width: units.gu(10)
4530+ text: "0"
4531+ }
4532+ Label {
4533+ text: "backgroundTopMargin"
4534+ }
4535+ }
4536+ Row {
4537+ CheckBox {
4538+ id: lockedCheckBox
4539+ }
4540+ Label {
4541+ text: "locked"
4542+ }
4543+ }
4544+ Row {
4545+ Label {
4546+ text: "selected: " + selectedSpy.count
4547+ }
4548+ }
4549+ Row {
4550+ Label {
4551+ text: "responded: " + respondedSpy.count
4552+ }
4553+ }
4554+ Row {
4555+ Label {
4556+ text: "teased: " + teaseSpy.count
4557+ }
4558+ }
4559+ Row {
4560+ Label {
4561+ text: "emergency: " + emergencySpy.count
4562+ }
4563+ }
4564+ Row {
4565+ Button {
4566+ text: "Reload View"
4567+ onClicked: {
4568+ loader.active = false;
4569+ loader.active = true;
4570+ }
4571+ }
4572+ }
4573+ Row {
4574+ CheckBox {
4575+ id: keyboardVisibleCheckBox
4576+ }
4577+ Label {
4578+ text: "Keyboard Visible"
4579+ }
4580+ }
4581+ }
4582+ }
4583+ }
4584+
4585+ SignalSpy {
4586+ id: selectedSpy
4587+ target: loader.item
4588+ signalName: "selected"
4589+ }
4590+
4591+ SignalSpy {
4592+ id: respondedSpy
4593+ target: loader.item
4594+ signalName: "responded"
4595+ }
4596+
4597+ SignalSpy {
4598+ id: teaseSpy
4599+ target: loader.item
4600+ signalName: "tease"
4601+ }
4602+
4603+ SignalSpy {
4604+ id: emergencySpy
4605+ target: loader.item
4606+ signalName: "emergencyCall"
4607+ }
4608+
4609+ UT.UnityTestCase {
4610+ name: "WideView"
4611+ when: windowShown
4612+
4613+ property Item view: loader.status === Loader.Ready ? loader.item : null
4614+
4615+ function init() {
4616+ view.currentIndex = 0; // break binding with text field
4617+ selectedSpy.clear();
4618+ respondedSpy.clear();
4619+ teaseSpy.clear();
4620+ emergencySpy.clear();
4621+ }
4622+
4623+ function cleanup() {
4624+ keyboardVisibleCheckBox.checked = false;
4625+
4626+ loader.itemDestroyed = false;
4627+ loader.active = false;
4628+ tryCompare(loader, "status", Loader.Null);
4629+ tryCompare(loader, "item", null);
4630+ tryCompare(loader, "itemDestroyed", true);
4631+ loader.active = true;
4632+ tryCompare(loader, "status", Loader.Ready);
4633+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
4634+ }
4635+
4636+ function getIndexOf(name) {
4637+ for (var i = 0; i < LightDM.Users.count; i++) {
4638+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
4639+ return i;
4640+ }
4641+ }
4642+ fail("Didn't find name")
4643+ return -1;
4644+ }
4645+
4646+ function selectUser(name) {
4647+ var i = getIndexOf(name);
4648+ view.currentIndex = i;
4649+ return i;
4650+ }
4651+
4652+ function swipeAwayCover() {
4653+ tryCompare(view, "fullyShown", true);
4654+ var touchY = view.height / 2;
4655+ touchFlick(view, view.width, touchY, 0, touchY);
4656+ var coverPage = findChild(view, "coverPage");
4657+ tryCompare(coverPage, "showProgress", 0);
4658+ waitForRendering(view);
4659+ }
4660+
4661+ function test_tease_data() {
4662+ return [
4663+ {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
4664+ {tag: "left", x: 0, offset: 0, count: 1, locked: false},
4665+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
4666+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
4667+ {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
4668+ ]
4669+ }
4670+ function test_tease(data) {
4671+ view.locked = data.locked;
4672+ view.dragHandleLeftMargin = data.offset;
4673+ tap(view, data.x, 0);
4674+ compare(teaseSpy.count, data.count);
4675+ }
4676+
4677+ function test_selected() {
4678+ var delegate = findChild(view, "username2");
4679+ tap(delegate);
4680+ compare(selectedSpy.count, 1);
4681+ compare(selectedSpy.signalArguments[0][0], 2);
4682+ compare(view.currentIndex, 0); // confirm we didn't change
4683+ }
4684+
4685+ function test_respondedWithPassword() {
4686+ view.locked = true;
4687+ view.showPrompt("Prompt", true, true);
4688+ var passwordInput = findChild(view, "passwordInput");
4689+ compare(passwordInput.placeholderText, "Prompt");
4690+ compare(passwordInput.echoMode, TextInput.Password);
4691+ tap(passwordInput);
4692+ typeString("password");
4693+ keyClick(Qt.Key_Enter);
4694+ compare(respondedSpy.count, 1);
4695+ compare(respondedSpy.signalArguments[0][0], "password");
4696+ }
4697+
4698+ function test_respondedWithNonSecret() {
4699+ view.locked = true;
4700+ view.showPrompt("otp", false, false);
4701+ var passwordInput = findChild(view, "passwordInput");
4702+ compare(passwordInput.placeholderText, "otp");
4703+ compare(passwordInput.echoMode, TextInput.Normal);
4704+ tap(passwordInput);
4705+ typeString("foo");
4706+ keyClick(Qt.Key_Enter);
4707+ compare(respondedSpy.count, 1);
4708+ compare(respondedSpy.signalArguments[0][0], "foo");
4709+ }
4710+
4711+ function test_respondedWithSwipe() {
4712+ swipeAwayCover();
4713+ compare(respondedSpy.count, 1);
4714+ compare(respondedSpy.signalArguments[0][0], "");
4715+ }
4716+
4717+ function test_fullyShown() {
4718+ tryCompare(view, "fullyShown", true);
4719+ swipeAwayCover();
4720+ tryCompare(view, "fullyShown", false);
4721+ }
4722+
4723+ function test_required() {
4724+ tryCompare(view, "required", true);
4725+ swipeAwayCover();
4726+ tryCompare(view, "required", false);
4727+ }
4728+
4729+ function test_showMessage() {
4730+ view.showMessage("Welcome to Unity Greeter");
4731+ view.showMessage("<font color=\"#df382c\">This is an error</font>");
4732+ view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
4733+ var infoLabel = findChild(view, "infoLabel");
4734+ compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
4735+ compare(infoLabel.textFormat, Text.StyledText);
4736+ compare(infoLabel.clip, true);
4737+ verify(infoLabel.contentWidth > infoLabel.width);
4738+ verify(infoLabel.opacity < 1);
4739+ tryCompare(infoLabel, "opacity", 1);
4740+ }
4741+
4742+ // Escape is used to reset the authentication, especially if PAM is unresponsive
4743+ function test_escape() {
4744+ view.currentIndex = 1;
4745+ view.locked = true;
4746+ view.showPrompt("Prompt", true, true);
4747+ var passwordInput = findChild(view, "passwordInput");
4748+ tap(passwordInput);
4749+ compare(passwordInput.focus, true);
4750+ compare(passwordInput.enabled, true);
4751+
4752+ typeString("password");
4753+ keyClick(Qt.Key_Enter);
4754+ compare(passwordInput.focus, false);
4755+ compare(passwordInput.enabled, false);
4756+
4757+ compare(selectedSpy.count, 0);
4758+ keyClick(Qt.Key_Escape);
4759+ compare(selectedSpy.count, 1);
4760+ compare(selectedSpy.signalArguments[0][0], 1);
4761+
4762+ view.reset();
4763+ compare(passwordInput.focus, false);
4764+ compare(passwordInput.enabled, true);
4765+ }
4766+
4767+ function test_unicode() {
4768+ var index = selectUser("unicode");
4769+ var label = findChild(view, "username" + index);
4770+ tryCompare(label, "text", "가나다라마");
4771+ }
4772+
4773+ function test_longName() {
4774+ var index = selectUser("long-name");
4775+ var label = findChild(view, "username" + index);
4776+ tryCompare(label, "truncated", true);
4777+ }
4778+
4779+ function test_promptless() {
4780+ var passwordInput = findChild(view, "passwordInput");
4781+
4782+ view.locked = true;
4783+ compare(passwordInput.placeholderText, "Retry");
4784+ tap(passwordInput);
4785+ compare(respondedSpy.count, 0);
4786+ compare(selectedSpy.count, 1);
4787+ compare(selectedSpy.signalArguments[0][0], 0);
4788+ selectedSpy.clear();
4789+
4790+ view.locked = false;
4791+ compare(passwordInput.placeholderText, "Tap to unlock");
4792+ tap(passwordInput);
4793+ compare(selectedSpy.count, 0);
4794+ compare(respondedSpy.count, 1);
4795+ compare(respondedSpy.signalArguments[0][0], "");
4796+ }
4797+
4798+ function test_loginListNotCoveredByKeyboard() {
4799+ var loginList = findChild(view, "loginList");
4800+ compare(loginList.height, view.height);
4801+
4802+ // when the vkb shows up, loginList is moved up to remain fully uncovered
4803+
4804+ keyboardVisibleCheckBox.checked = true;
4805+
4806+ tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
4807+ tryCompareFunction( function() {
4808+ var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
4809+ return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
4810+ }, true);
4811+
4812+ // once the vkb goes away, loginList goes back to its full height
4813+
4814+ keyboardVisibleCheckBox.checked = false;
4815+
4816+ tryCompare(loginList, "height", view.height);
4817+ }
4818+ }
4819+}
4820
4821=== modified file 'tests/qmltests/Tutorial/tst_Tutorial.qml'
4822--- tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-05 20:20:28 +0000
4823+++ tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-23 15:44:00 +0000
4824@@ -112,9 +112,9 @@
4825
4826 function init() {
4827 tryCompare(shell, "enabled", true); // enabled by greeter when ready
4828- swipeAwayGreeter();
4829 AccountsService.demoEdges = false;
4830 AccountsService.demoEdges = true;
4831+ swipeAwayGreeter();
4832 }
4833
4834 function cleanup() {
4835@@ -151,13 +151,14 @@
4836 }
4837
4838 function swipeAwayGreeter() {
4839- var greeter = findChild(shell, "greeter");
4840- tryCompare(greeter, "showProgress", 1);
4841+ var coverPage = findChild(shell, "coverPage");
4842+ tryCompare(coverPage, "showProgress", 1);
4843
4844 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
4845
4846 // wait until the animation has finished
4847- tryCompare(greeter, "showProgress", 0);
4848+ var greeter = findChild(shell, "greeter");
4849+ tryCompare(greeter, "required", false);
4850 waitForRendering(greeter);
4851 }
4852
4853
4854=== modified file 'tests/qmltests/tst_Shell.qml'
4855--- tests/qmltests/tst_Shell.qml 2015-02-11 17:12:49 +0000
4856+++ tests/qmltests/tst_Shell.qml 2015-02-23 15:44:00 +0000
4857@@ -19,9 +19,11 @@
4858
4859 import QtQuick 2.0
4860 import QtTest 1.0
4861+import AccountsService 0.1
4862 import GSettings 1.0
4863 import LightDM 0.1 as LightDM
4864 import Ubuntu.Components 1.1
4865+import Ubuntu.Components.ListItems 1.0 as ListItem
4866 import Ubuntu.Telephony 0.1 as Telephony
4867 import Unity.Application 0.1
4868 import Unity.Connectivity 0.1
4869@@ -80,7 +82,7 @@
4870 }
4871
4872 Rectangle {
4873- color: "white"
4874+ color: "darkgrey"
4875 width: units.gu(30)
4876 height: shellLoader.height
4877
4878@@ -98,11 +100,23 @@
4879
4880 var greeter = testCase.findChild(shellLoader.item, "greeter");
4881 if (!greeter.shown) {
4882- greeter.show();
4883+ LightDM.Greeter.showGreeter();
4884 }
4885 }
4886 }
4887 }
4888+ ListItem.ItemSelector {
4889+ anchors { left: parent.left; right: parent.right }
4890+ activeFocusOnPress: false
4891+ text: "LightDM mock mode"
4892+ model: ["single", "single-passphrase", "single-pin"]
4893+ onSelectedIndexChanged: {
4894+ shellLoader.active = false;
4895+ LightDM.Greeter.mockMode = model[selectedIndex];
4896+ LightDM.Users.mockMode = model[selectedIndex];
4897+ shellLoader.active = true;
4898+ }
4899+ }
4900 }
4901 }
4902 }
4903@@ -284,11 +298,13 @@
4904
4905 function test_leftEdgeDrag_data() {
4906 return [
4907- {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
4908- {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
4909- {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false},
4910- {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
4911- {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false}
4912+ {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
4913+ {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
4914+ {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: false},
4915+ {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
4916+ {tag: "small swipe with greeter", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: true},
4917+ {tag: "long swipe with greeter", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: true},
4918+ {tag: "swipe over dash", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false, greeterShown: false},
4919 ];
4920 }
4921
4922@@ -299,6 +315,12 @@
4923 ApplicationManager.focusApplication(data.focusedApp)
4924 waitUntilApplicationWindowIsFullyVisible();
4925
4926+ var greeter = findChild(shell, "greeter");
4927+ if (data.greeterShown) {
4928+ LightDM.Greeter.showGreeter();
4929+ tryCompare(greeter, "fullyShown", true);
4930+ }
4931+
4932 if (data.revealLauncher) {
4933 dragLauncherIntoView();
4934 }
4935@@ -306,8 +328,10 @@
4936 swipeFromLeftEdge(data.swipeLength);
4937 if (data.appHides) {
4938 waitUntilDashIsFocused();
4939+ tryCompare(greeter, "shown", false);
4940 } else {
4941 waitUntilApplicationWindowIsFullyVisible();
4942+ compare(greeter.fullyShown, data.greeterShown);
4943 }
4944
4945 var launcher = findChild(shell, "launcherPanel");
4946@@ -335,20 +359,20 @@
4947 // Suspend while call is active...
4948 callManager.foregroundCall = phoneCall;
4949 Powerd.status = Powerd.Off;
4950- tryCompare(greeter, "showProgress", 0);
4951+ tryCompare(greeter, "shown", false);
4952
4953 // Now try again after ending call
4954 callManager.foregroundCall = null;
4955 Powerd.status = Powerd.On;
4956 Powerd.status = Powerd.Off;
4957- tryCompare(greeter, "showProgress", 1);
4958+ tryCompare(greeter, "fullyShown", true);
4959
4960 tryCompare(ApplicationManager, "suspended", true);
4961 compare(mainApp.state, ApplicationInfoInterface.Suspended);
4962
4963 // And wake up
4964 Powerd.status = Powerd.On;
4965- tryCompare(greeter, "showProgress", 1);
4966+ tryCompare(greeter, "fullyShown", true);
4967
4968 // Swipe away greeter to focus app
4969 swipeAwayGreeter();
4970@@ -359,14 +383,14 @@
4971
4972 function swipeAwayGreeter() {
4973 var greeter = findChild(shell, "greeter");
4974- tryCompare(greeter, "showProgress", 1);
4975+ tryCompare(greeter, "fullyShown", true);
4976
4977 var touchX = shell.width - (shell.edgeSize / 2);
4978 var touchY = shell.height / 2;
4979 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
4980
4981 // wait until the animation has finished
4982- tryCompare(greeter, "showProgress", 0);
4983+ tryCompare(greeter, "shown", false);
4984 waitForRendering(greeter);
4985 }
4986
4987@@ -608,12 +632,12 @@
4988
4989 waitUntilDashIsFocused();
4990
4991- greeter.show();
4992- tryCompare(greeter, "showProgress", 1);
4993+ LightDM.Greeter.showGreeter();
4994+ tryCompare(greeter, "fullyShown", true);
4995
4996 // The main point of this test
4997 ApplicationManager.requestFocusApplication("dialer-app");
4998- tryCompare(greeter, "showProgress", 0);
4999+ tryCompare(greeter, "shown", false);
5000 waitForRendering(greeter);
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches