Unity 8
OrientedShell.qml
1 /*
2  * Copyright (C) 2015 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import QtQuick.Window 2.2
19 import Unity.InputInfo 0.1
20 import Unity.Session 0.1
21 import GSettings 1.0
22 import "Components"
23 import "Rotation"
24 // Workaround https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1473471
25 import Ubuntu.Components 1.3
26 
27 Rectangle {
28  id: root
29  color: "black"
30 
31  implicitWidth: units.gu(40)
32  implicitHeight: units.gu(71)
33 
34  DeviceConfiguration {
35  id: deviceConfiguration
36  name: applicationArguments.deviceName
37  }
38 
39  property alias orientations: d.orientations
40 
41  Item {
42  id: d
43 
44  property Orientations orientations: Orientations {
45  id: orientations
46  // NB: native and primary orientations here don't map exactly to their QScreen counterparts
47  native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
48 
49  primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
50  ? native_ : deviceConfiguration.primaryOrientation
51 
52  landscape: deviceConfiguration.landscapeOrientation
53  invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
54  portrait: deviceConfiguration.portraitOrientation
55  invertedPortrait: deviceConfiguration.invertedPortraitOrientation
56  }
57  }
58  // to be overwritten by tests
59  property var unity8Settings: Unity8Settings {}
60  property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
61 
62  property int physicalOrientation: Screen.orientation
63  property bool orientationLocked: OrientationLock.enabled
64  property var orientationLock: OrientationLock
65 
66  InputDeviceModel {
67  id: miceModel
68  deviceFilter: InputInfo.Mouse
69  }
70 
71  InputDeviceModel {
72  id: touchPadModel
73  deviceFilter: InputInfo.TouchPad
74  }
75 
76  InputDeviceModel {
77  id: keyboardsModel
78  deviceFilter: InputInfo.Keyboard
79  }
80 
81  property int orientation
82  onPhysicalOrientationChanged: {
83  if (!orientationLocked) {
84  orientation = physicalOrientation;
85  }
86  }
87  onOrientationLockedChanged: {
88  if (orientationLocked) {
89  orientationLock.savedOrientation = physicalOrientation;
90  } else {
91  orientation = physicalOrientation;
92  }
93  }
94  Component.onCompleted: {
95  if (orientationLocked) {
96  orientation = orientationLock.savedOrientation;
97  }
98  // We need to manually update this on startup as the binding
99  // below doesn't seem to have any effect at that stage
100  oskSettings.stayHidden = keyboardsModel.count > 0
101  oskSettings.disableHeight = shell.usageScenario == "desktop"
102  }
103 
104  // we must rotate to a supported orientation regardless of shell's preference
105  property bool orientationChangesEnabled:
106  (shell.orientation & supportedOrientations) === 0 ? true
107  : shell.orientationChangesEnabled
108 
109 
110  Binding {
111  target: oskSettings
112  property: "stayHidden"
113  value: keyboardsModel.count > 0
114  }
115 
116  Binding {
117  target: oskSettings
118  property: "disableHeight"
119  value: shell.usageScenario == "desktop"
120  }
121 
122  readonly property int supportedOrientations: shell.supportedOrientations
123  & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
124  ? orientations.native_
125  : deviceConfiguration.supportedOrientations)
126 
127  property int acceptedOrientationAngle: {
128  if (orientation & supportedOrientations) {
129  return Screen.angleBetween(orientations.native_, orientation);
130  } else if (shell.orientation & supportedOrientations) {
131  // stay where we are
132  return shell.orientationAngle;
133  } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
134  return shell.mainAppWindowOrientationAngle;
135  } else {
136  // rotate to some supported orientation as we can't stay where we currently are
137  // TODO: Choose the closest to the current one
138  if (supportedOrientations & Qt.PortraitOrientation) {
139  return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
140  } else if (supportedOrientations & Qt.LandscapeOrientation) {
141  return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
142  } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
143  return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
144  } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
145  return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
146  } else {
147  // if all fails, fallback to primary orientation
148  return Screen.angleBetween(orientations.native_, orientations.primary);
149  }
150  }
151  }
152 
153  function angleToOrientation(angle) {
154  switch (angle) {
155  case 0:
156  return orientations.native_;
157  case 90:
158  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
159  : Qt.PortraitOrientation;
160  case 180:
161  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
162  : Qt.InvertedLandscapeOrientation;
163  case 270:
164  return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
165  : Qt.InvertedPortraitOrientation;
166  default:
167  console.warn("angleToOrientation: Invalid orientation angle: " + angle);
168  return orientations.primary;
169  }
170  }
171 
172  RotationStates {
173  id: rotationStates
174  objectName: "rotationStates"
175  orientedShell: root
176  shell: shell
177  shellCover: shellCover
178  windowScreenshot: windowScreenshot
179  }
180 
181  Shell {
182  id: shell
183  objectName: "shell"
184  width: root.width
185  height: root.height
186  orientation: root.angleToOrientation(orientationAngle)
187  orientations: root.orientations
188  nativeWidth: root.width
189  nativeHeight: root.height
190  mode: applicationArguments.mode
191  hasMouse: miceModel.count + touchPadModel.count > 0
192 
193  // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
194  // what's the output device (eg: big TV, desktop monitor, phone display), etc.
195  usageScenario: {
196  if (root.unity8Settings.usageMode === "Windowed") {
197  return "desktop";
198  } else if (root.unity8Settings.usageMode === "Staged") {
199  if (deviceConfiguration.category === "phone") {
200  return "phone";
201  } else {
202  return "tablet";
203  }
204  } else { // automatic
205  if (miceModel.count + touchPadModel.count > 0) {
206  return "desktop";
207  } else {
208  return deviceConfiguration.category;
209  }
210  }
211  }
212 
213  property real transformRotationAngle
214  property real transformOriginX
215  property real transformOriginY
216 
217  transform: Rotation {
218  origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
219  angle: shell.transformRotationAngle
220  }
221  }
222 
223  Rectangle {
224  id: shellCover
225  color: "black"
226  anchors.fill: parent
227  visible: false
228  }
229 
230  WindowScreenshot {
231  id: windowScreenshot
232  visible: false
233  width: root.width
234  height: root.height
235 
236  property real transformRotationAngle
237  property real transformOriginX
238  property real transformOriginY
239 
240  transform: Rotation {
241  origin.x: windowScreenshot.transformOriginX; origin.y: windowScreenshot.transformOriginY;
242  axis { x: 0; y: 0; z: 1 }
243  angle: windowScreenshot.transformRotationAngle
244  }
245  }
246 }