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