@@ -1,566 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−import Ubuntu.Components 0.1
20−import Ubuntu.Gestures 0.1
21−
22−/*
23− Responsible for application switching.
24−
25− Drag from the right edge to switch to the next application.
26− While being dragged, this component will show an animation for the user to
27− visualize that he's changing the stacking of applications (next/new app comes from
28− right edge and current/old app fades out).
29− */
30−Showable {
31− id: stage
32−
33− width: units.gu(40)
34− height: units.gu(71)
35−
36− property int type: ApplicationInfo.MainStage
37− property var applicationManager
38− readonly property var applications:
39− type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
40− : applicationManager.sideStageApplications
41− property bool fullyShown: false
42− property bool fullyHidden: true
43− readonly property var focusedApplication:
44− type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
45− : applicationManager.sideStageFocusedApplication
46− property bool shouldUseScreenshots: true
47−
48− /* Will be true whenever application screenshots are being shown.
49− i.e. during application window transitions */
50− readonly property bool usingScreenshots: {
51− if (newApplicationScreenshot.application != null && oldApplicationScreenshot.application != null) {
52− return newApplicationScreenshot.ready && oldApplicationScreenshot.ready;
53− } else {
54− return newApplicationScreenshot.ready || oldApplicationScreenshot.ready;
55− }
56− }
57−
58− property bool rightEdgeEnabled: true
59− property bool switchingFromFullscreenToFullscreen: false
60−
61− property alias rightEdgeDraggingAreaWidth: rightEdgeDraggingArea.width
62−
63− // Y coordinate for non-fullscreen applications
64− property int normalApplicationY: 0
65−
66− function activateApplication(desktopFile, addDelay) {
67− var application = applicationManager.getApplicationFromDesktopFile(desktopFile, stage.type);
68− if (application == null) {
69− return;
70− } else if (application == stage.focusedApplication) {
71− stage.show();
72− return;
73− }
74−
75− showStartingApplicationAnimation.prepare(application);
76− if (!stage.fullyShown) {
77− // if launching this app reveals the stage, make new application screenshot visible
78− newApplicationScreenshot.visible = true;
79− stage.show();
80− } else {
81− showStartingApplicationAnimation.start();
82− }
83−
84− if (addDelay) {
85− stage.__startingApplicationDesktopFile = desktopFile;
86− }
87− }
88−
89− /* Keep a reference to the focused application so that we can safely
90− unfocus it when the stage is not fully shown and refocus it when the stage
91− is fully shown again.
92− */
93− property var focusedApplicationWhenUsingScreenshots
94− onShouldUseScreenshotsChanged: {
95− if (showStartingApplicationAnimation.running || switchToApplicationAnimation.running) {
96− return;
97− }
98−
99− if (shouldUseScreenshots) {
100− stage.__focusApplicationUsingScreenshots(stage.focusedApplication);
101− } else {
102− if (stage.focusedApplicationWhenUsingScreenshots) {
103− stage.__focusActualApplication(stage.focusedApplicationWhenUsingScreenshots);
104− }
105− }
106− }
107−
108−
109− property Item newApplicationScreenshot: ApplicationScreenshot {
110− parent: stage
111− width: stage.width
112− height: stage.height - y
113− }
114− property Item oldApplicationScreenshot: ApplicationScreenshot {
115− parent: stage
116− width: stage.width
117− height: stage.height - y
118− }
119−
120− function __getApplicationIndex(application) {
121− if (!application)
122− return -1
123− for (var i = 0; i < stage.applications.count; i++ ) {
124− if (stage.applications.get(i).desktopFile == application.desktopFile) {
125− return i
126− }
127− }
128− return -1
129− }
130−
131−
132− property string __startingApplicationDesktopFile
133−
134− Timer {
135− id: delayedHideScreenshots
136− interval: 500 // on Galaxy Nexus, Telephony app is slowest application to draw a frame so needs this delay
137− onTriggered: {
138− __hideScreenshots();
139− }
140− }
141−
142− Timer {
143− id: hideScreenshotsAfterBriefDelay
144− // Since Mir & Qt have independent event loops, Mir often applies a focus request a frame later than when Qt
145− // asks for it. To work around, delay removing the screenshots a touch so Mir has definitely shown the app.
146− interval: 10
147− onTriggered: stage.__hideScreenshots();
148− }
149−
150− function __hideScreenshots() {
151− newApplicationScreenshot.clearApplication();
152− oldApplicationScreenshot.clearApplication();
153− newApplicationScreenshot.visible = false;
154− oldApplicationScreenshot.visible = false;
155− }
156−
157− function __focusApplication(application) {
158− if (!application) return
159− if (shouldUseScreenshots) {
160− stage.__focusApplicationUsingScreenshots(application);
161− } else {
162− stage.__focusActualApplication(application);
163− }
164− }
165−
166−
167− function __focusActualApplication(application) {
168− if (application == null) {
169− return;
170− }
171−
172− if (stage.focusedApplication == application) {
173− /* FIXME: this case is reached when showing the sidestage, we
174− need to refocus the side stage app in order to show it.
175− FIXME: this case is reached when doing a right edge swipe
176− while in the dash to bring back the stage.
177− FIXME: this case is reached when showStartingApplicationAnimation
178− finishes after the newly started application has been focused
179− automatically by the ApplicationManager.
180− */
181− if (stage.type == ApplicationInfo.SideStage) {
182− applicationManager.focusApplication(application);
183− }
184− if (!delayedHideScreenshots.running) {
185− hideScreenshotsAfterBriefDelay.start();
186− }
187− } else {
188− /* FIXME: calling ApplicationManager::focusApplication does not focus
189− the application immediately nor show the application's surface
190− immediately. Delay hiding the screenshots until it has done so.
191− */
192− delayedHideScreenshots.stop();
193− applicationManager.focusApplication(application);
194− }
195− stage.focusedApplicationWhenUsingScreenshots = null;
196− }
197−
198− function __focusApplicationUsingScreenshots(application) {
199− delayedHideScreenshots.stop();
200− if (application == null) {
201− return;
202− }
203−
204− if (newApplicationScreenshot.application != null) {
205− stage.focusedApplicationWhenUsingScreenshots = application;
206− return;
207− }
208−
209− newApplicationScreenshot.setApplication(application);
210− newApplicationScreenshot.scheduleUpdate();
211− newApplicationScreenshot.opacity = 1.0;
212− newApplicationScreenshot.x = 0.0;
213− newApplicationScreenshot.y = __getApplicationY(application);
214− newApplicationScreenshot.scale = 1.0;
215− newApplicationScreenshot.visible = true;
216− stage.focusedApplicationWhenUsingScreenshots = application;
217− stage.switchingFromFullscreenToFullscreen = false;
218− }
219−
220− function __getApplicationY(application) {
221− return Qt.binding( function(){
222− return (application && application.fullscreen
223− && application.stage !== ApplicationInfo.SideStage)
224− ? 0 : normalApplicationY;
225− } )
226− }
227−
228− /* FIXME: should hide the screenshots when the focused application's surface
229− is visible. There is no API in QtUbuntu to support that yet.
230− */
231− Connections {
232− target: stage
233− onFocusedApplicationChanged: {
234− if (stage.focusedApplication != null) {
235− /* Move application to the beginning of the list of running applications */
236− var index = stage.__getApplicationIndex(stage.focusedApplication);
237− applicationManager.moveRunningApplicationStackPosition(index, 0, stage.type);
238−
239− var startingApplication = applicationManager.getApplicationFromDesktopFile(
240− stage.__startingApplicationDesktopFile, stage.type);
241−
242− if (stage.focusedApplication == startingApplication) {
243− stage.__startingApplicationDesktopFile = "";
244−
245− /* Screenshots might still be in use at this point and their
246− use can be stopped before delayedHideScreenshots is complete.
247− Prepare for that case by setting appropriately
248− stage.focusedApplicationWhenUsingScreenshots.
249− */
250− stage.focusedApplicationWhenUsingScreenshots = stage.focusedApplication;
251−
252− /* FIXME: should hide the screenshots when the focused application's window
253− is visible. There is no API in qtubuntu to support that yet. Therefore
254− we delay their hiding to avoid a state where neither the screenshot nor
255− the application's window is visible.
256− */
257− delayedHideScreenshots.start();
258− } else {
259− stage.focusedApplicationWhenUsingScreenshots = null;
260− hideScreenshotsAfterBriefDelay.start();
261− }
262− }
263− }
264− }
265−
266− Connections {
267− target: stage.applications
268− onCountChanged: { __discardObsoleteScreenshots(); }
269− ignoreUnknownSignals: true
270− }
271− onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
272− function __discardObsoleteScreenshots() {
273− if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
274− stage.focusedApplicationWhenUsingScreenshots = undefined;
275− stage.__hideScreenshots();
276− }
277− }
278−
279−
280− SequentialAnimation {
281− id: showStartingApplicationAnimation
282−
283− function prepare(newApplication) {
284− var oldApplication = stage.focusedApplication;
285− newApplicationScreenshot.setApplication(newApplication);
286− newApplicationScreenshot.updateFromCache();
287− oldApplicationScreenshot.setApplication(oldApplication);
288− oldApplicationScreenshot.scheduleUpdate();
289− newApplicationScreenshot.withBackground = true; // FIXME: should be withBackground = newApplication.state == Application.Starting
290− newApplicationScreenshot.y = __getApplicationY(newApplication);
291− oldApplicationScreenshot.y = __getApplicationY(oldApplication);
292− stage.focusedApplicationWhenUsingScreenshots = newApplication;
293− stage.switchingFromFullscreenToFullscreen = false;
294− }
295−
296− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
297− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
298− PropertyAction {target: newApplicationScreenshot; property: "z"; value: 0}
299− PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 1}
300− PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 0.8}
301− PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
302− PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 0.0}
303− PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
304− PropertyAction {target: newApplicationScreenshot; property: "x"; value: 0.0}
305− PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
306−
307− ParallelAnimation {
308− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
309− NumberAnimation {
310− target: newApplicationScreenshot
311− property: "scale"
312− duration: 500
313− easing.type: Easing.OutQuad
314− to: 1.0
315− }
316− NumberAnimation {
317− target: newApplicationScreenshot
318− property: "opacity"
319− duration: 500
320− easing.type: Easing.OutQuad
321− to: 1.0
322− }
323−
324− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
325−
326− NumberAnimation {
327− target: oldApplicationScreenshot
328− property: "x"
329− duration: 400
330− easing.type: Easing.OutQuad
331− to: stage.width
332− }
333− }
334−
335− ScriptAction {
336− script: {
337− // FIXME: only here to support case where application does
338− // not exist yet but is about to
339− if (stage.focusedApplicationWhenUsingScreenshots) {
340− stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
341− }
342− }
343− }
344− }
345−
346−
347− SequentialAnimation {
348− id: switchToApplicationAnimation
349−
350− function prepare(newApplication) {
351− var oldApplication = stage.focusedApplication;
352− newApplicationScreenshot.setApplication(newApplication);
353− newApplicationScreenshot.updateFromCache();
354− oldApplicationScreenshot.setApplication(oldApplication);
355− oldApplicationScreenshot.scheduleUpdate();
356− newApplicationScreenshot.withBackground = false;
357− newApplicationScreenshot.y = __getApplicationY(newApplication);
358− oldApplicationScreenshot.y = __getApplicationY(oldApplication);
359− stage.focusedApplicationWhenUsingScreenshots = newApplication;
360− stage.switchingFromFullscreenToFullscreen = newApplication && newApplication.fullscreen;
361− }
362−
363− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
364− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
365− PropertyAction {target: newApplicationScreenshot; property: "z"; value: 1}
366− PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 0}
367− PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 1.0}
368− PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
369− PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 1.0}
370− PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
371− PropertyAction {target: newApplicationScreenshot; property: "x"; value: stage.width}
372− PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
373− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
374− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
375−
376− ParallelAnimation {
377− NumberAnimation {
378− target: oldApplicationScreenshot
379− property: "scale"
380− duration: 500
381− easing.type: Easing.OutQuad
382− to: 0.8
383− }
384− NumberAnimation {
385− target: oldApplicationScreenshot
386− property: "opacity"
387− duration: 500
388− easing.type: Easing.OutQuad
389− to: 0.0
390− }
391−
392− NumberAnimation {
393− target: newApplicationScreenshot
394− property: "x"
395− duration: 500
396− easing.type: Easing.OutQuad
397− to: 0
398− }
399− }
400−
401− ScriptAction {
402− script: {
403− // move previously focused application to the end of the list of running applications
404− var oldApplication = oldApplicationScreenshot.application;
405− if (oldApplication != null) {
406− var oldApplicationIndex = stage.__getApplicationIndex(oldApplicationScreenshot.application);
407− applicationManager.moveRunningApplicationStackPosition(
408− oldApplicationIndex, stage.applications.count - 1, stage.type);
409− }
410−
411− stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
412− }
413− }
414− }
415−
416−
417− function __dragValueToProgress(dragValue, startValue, applicationsCount) {
418− if (applicationsCount == 0) {
419− return 0;
420− } else if (applicationsCount == 1) {
421− // prevent the current app from moving more than 1/4 of the screen
422− var elasticValue = __elastic(dragValue, startValue/4);
423− return elasticValue / startValue;
424− } else {
425− return dragValue / startValue;
426− }
427− }
428−
429− function __elastic(x, limit) {
430− return limit * ( 1 - Math.pow( ( limit - 1 ) / limit, x ) )
431− }
432−
433− property real __startValue: stage.width
434−
435− AnimationControllerWithSignals {
436− id: switchToApplicationAnimationController
437−
438− onAnimationCompletedAtBeginning: {
439− animation = null;
440− stage.__focusApplication(oldApplicationScreenshot.application);
441− }
442− }
443−
444− EdgeDragEvaluator {
445− id: dragEvaluator
446− trackedPosition: rightEdgeDraggingArea.touchX
447− direction: rightEdgeDraggingArea.direction
448− maxDragDistance: stage.width
449− }
450−
451− EdgeDragArea {
452− id: rightEdgeDraggingArea
453−
454− enabled: {
455− if (status == DirectionalDragArea.WaitingForTouch) {
456− return switchToApplicationAnimationController.completed
457− && stage.applications.count > 0 && stage.rightEdgeEnabled
458− && !delayedHideScreenshots.running
459− } else {
460− return true
461− }
462− }
463− width: units.gu(2)
464− anchors.top: parent.top
465− anchors.bottom: parent.bottom
466− anchors.right: parent.right
467−
468− direction: Direction.Leftwards
469−
470− onDistanceChanged: {
471− if (status !== DirectionalDragArea.Recognized)
472− return
473−
474− var targetProgress = __dragValueToProgress(-distance, __startValue,
475− stage.applications.count)
476− var delta = targetProgress - switchToApplicationAnimationController.progress
477−
478− // When the gesture finally gets recognized, the finger will likely be
479− // reasonably far from the edge. If we made the progress immediately
480− // follow the finger position it would be visually unpleasant as there
481− // would be a jump to meet the finger's current position.
482− //
483− // The trick is not to go all the way (1.0) as it would cause the
484− // aforementioned jump.
485− switchToApplicationAnimationController.progress += 0.4 * delta
486− }
487−
488− property int lastStatus: -1
489−
490− onStatusChanged: {
491− if (status == DirectionalDragArea.Undecided) {
492− onUndecided();
493− } else if (status == DirectionalDragArea.WaitingForTouch) {
494− if (lastStatus == DirectionalDragArea.Undecided) {
495− // got rejected
496− switchToApplicationAnimationController.completeToBeginningWithSignal();
497− } else {
498− onGestureEnded();
499− }
500− }
501− lastStatus = status;
502− }
503−
504− function onUndecided() {
505− dragEvaluator.reset();
506−
507− var nextApplication = null;
508− if (stage.applications.count > 1) {
509− nextApplication = stage.applications.get(1);
510− }
511− switchToApplicationAnimationController.progress = 0
512− switchToApplicationAnimation.prepare(nextApplication);
513− switchToApplicationAnimationController.animation = switchToApplicationAnimation;
514−
515− }
516−
517− function onGestureEnded() {
518− if (stage.applications.count > 1 && dragEvaluator.shouldAutoComplete()) {
519− switchToApplicationAnimationController.completeToEndWithSignal();
520− } else {
521− switchToApplicationAnimationController.completeToBeginningWithSignal();
522− }
523− }
524− }
525−
526−
527− // FIXME: very bad hack to detect when an application was closed or crashed
528− property var __previouslyFocusedApplication
529− Connections {
530− target: stage
531− onFocusedApplicationChanged: {
532− if (stage.focusedApplication == null
533− && __getApplicationIndex(stage.__previouslyFocusedApplication) == -1) {
534− stage.__onFocusedApplicationClosed();
535− }
536− stage.__previouslyFocusedApplication = stage.focusedApplication;
537− }
538− }
539−
540− function __onFocusedApplicationClosed() {
541− if (!stage.shown) {
542− return;
543− }
544−
545− if (stage.applications.count > 0) {
546− // Do not change the focused application from within onFocusedApplicationChanged.
547− // This causes binding loop warnings besides being a bad practice in general
548− // (setting a property from within its "changed" signal handling)
549− // Hence the timer.
550− focusTopmostAppTimer.start();
551− } else {
552− stage.hide();
553− }
554− }
555−
556− Timer {
557− id: focusTopmostAppTimer
558− interval: 1; repeat: false
559− onTriggered: {
560− if (stage.applications.count > 0) {
561− stage.__focusApplication(stage.applications.get(0));
562− }
563− }
564− }
565−
566−}