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 Unity.Screens 0.1
22 import Utils 0.1
23 import GSettings 1.0
24 import "Components"
25 import "Rotation"
26 // Workaround https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1473471
27 import Ubuntu.Components 1.3
28 
29 Rectangle {
30  id: root
31  color: "black"
32 
33  implicitWidth: units.gu(40)
34  implicitHeight: units.gu(71)
35 
36  DeviceConfiguration {
37  id: deviceConfiguration
38  name: applicationArguments.deviceName
39  }
40 
41  property alias orientations: d.orientations
42 
43  Item {
44  id: d
45 
46  property Orientations orientations: Orientations {
47  id: orientations
48  // NB: native and primary orientations here don't map exactly to their QScreen counterparts
49  native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
50 
51  primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
52  ? native_ : deviceConfiguration.primaryOrientation
53 
54  landscape: deviceConfiguration.landscapeOrientation
55  invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
56  portrait: deviceConfiguration.portraitOrientation
57  invertedPortrait: deviceConfiguration.invertedPortraitOrientation
58  }
59  }
60  // to be overwritten by tests
61  property var unity8Settings: Unity8Settings {}
62  property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
63 
64  property int physicalOrientation: Screen.orientation
65  property bool orientationLocked: OrientationLock.enabled
66  property var orientationLock: OrientationLock
67 
68  InputDeviceModel {
69  id: miceModel
70  deviceFilter: InputInfo.Mouse
71  }
72 
73  InputDeviceModel {
74  id: touchPadModel
75  deviceFilter: InputInfo.TouchPad
76  }
77 
78  InputDeviceModel {
79  id: keyboardsModel
80  deviceFilter: InputInfo.Keyboard
81  onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
82  onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
83  }
84 
85  InputDeviceModel {
86  id: touchScreensModel
87  deviceFilter: InputInfo.TouchScreen
88  }
89 
90  /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
91  * When QInputInfo exposes NameRole to QML, this should be removed.
92  */
93  property bool forceOSKEnabled: false
94  property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
95  UnitySortFilterProxyModel {
96  id: autopilotDevices
97  model: keyboardsModel
98  }
99 
100  function autopilotDevicePresent() {
101  for(var i = 0; i < autopilotDevices.count; i++) {
102  var device = autopilotDevices.get(i);
103  if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
104  console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
105  return true;
106  }
107  }
108  return false;
109  }
110 
111  Screens {
112  id: screens
113  }
114 
115  property int orientation
116  onPhysicalOrientationChanged: {
117  if (!orientationLocked) {
118  orientation = physicalOrientation;
119  }
120  }
121  onOrientationLockedChanged: {
122  if (orientationLocked) {
123  orientationLock.savedOrientation = physicalOrientation;
124  } else {
125  orientation = physicalOrientation;
126  }
127  }
128  Component.onCompleted: {
129  if (orientationLocked) {
130  orientation = orientationLock.savedOrientation;
131  }
132  // We need to manually update this on startup as the binding
133  // below doesn't seem to have any effect at that stage
134  oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
135  }
136 
137  // we must rotate to a supported orientation regardless of shell's preference
138  property bool orientationChangesEnabled:
139  (shell.orientation & supportedOrientations) === 0 ? true
140  : shell.orientationChangesEnabled
141 
142  Binding {
143  target: oskSettings
144  property: "disableHeight"
145  value: !shell.oskEnabled || shell.usageScenario == "desktop"
146  }
147 
148  readonly property int supportedOrientations: shell.supportedOrientations
149  & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
150  ? orientations.native_
151  : deviceConfiguration.supportedOrientations)
152 
153  property int acceptedOrientationAngle: {
154  if (orientation & supportedOrientations) {
155  return Screen.angleBetween(orientations.native_, orientation);
156  } else if (shell.orientation & supportedOrientations) {
157  // stay where we are
158  return shell.orientationAngle;
159  } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
160  return shell.mainAppWindowOrientationAngle;
161  } else {
162  // rotate to some supported orientation as we can't stay where we currently are
163  // TODO: Choose the closest to the current one
164  if (supportedOrientations & Qt.PortraitOrientation) {
165  return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
166  } else if (supportedOrientations & Qt.LandscapeOrientation) {
167  return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
168  } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
169  return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
170  } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
171  return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
172  } else {
173  // if all fails, fallback to primary orientation
174  return Screen.angleBetween(orientations.native_, orientations.primary);
175  }
176  }
177  }
178 
179  function angleToOrientation(angle) {
180  switch (angle) {
181  case 0:
182  return orientations.native_;
183  case 90:
184  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
185  : Qt.PortraitOrientation;
186  case 180:
187  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
188  : Qt.InvertedLandscapeOrientation;
189  case 270:
190  return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
191  : Qt.InvertedPortraitOrientation;
192  default:
193  console.warn("angleToOrientation: Invalid orientation angle: " + angle);
194  return orientations.primary;
195  }
196  }
197 
198  RotationStates {
199  id: rotationStates
200  objectName: "rotationStates"
201  orientedShell: root
202  shell: shell
203  shellCover: shellCover
204  windowScreenshot: windowScreenshot
205  }
206 
207  Shell {
208  id: shell
209  objectName: "shell"
210  width: root.width
211  height: root.height
212  orientation: root.angleToOrientation(orientationAngle)
213  orientations: root.orientations
214  nativeWidth: root.width
215  nativeHeight: root.height
216  mode: applicationArguments.mode
217  hasMouse: miceModel.count + touchPadModel.count > 0
218  // TODO: Factor in if the current screen is a touch screen and if the user wants to
219  // have multiple keyboards around. For now we only enable one keyboard at a time
220  // thus hiding it here if there is a physical one around or if we have a second
221  // screen (the virtual touchpad & osk on the phone) attached.
222  oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
223  forceOSKEnabled
224 
225  // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
226  // what's the output device (eg: big TV, desktop monitor, phone display), etc.
227  usageScenario: {
228  if (root.unity8Settings.usageMode === "Windowed") {
229  return "desktop";
230  } else if (root.unity8Settings.usageMode === "Staged") {
231  if (deviceConfiguration.category === "phone") {
232  return "phone";
233  } else {
234  return "tablet";
235  }
236  } else { // automatic
237  var longEdgeWidth = Math.max(root.width, root.height)
238  if (longEdgeWidth > units.gu(120)) {
239  if (keyboardsModel.count + miceModel.count + touchPadModel.count > 0) {
240  return "desktop";
241  }
242  } else if (longEdgeWidth > units.gu(90)){
243  if (miceModel.count + touchPadModel.count > 0) {
244  return "desktop";
245  }
246  }
247 
248  return deviceConfiguration.category;
249  }
250  }
251 
252  property real transformRotationAngle
253  property real transformOriginX
254  property real transformOriginY
255 
256  transform: Rotation {
257  origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
258  angle: shell.transformRotationAngle
259  }
260  }
261 
262  Rectangle {
263  id: shellCover
264  color: "black"
265  anchors.fill: parent
266  visible: false
267  }
268 
269  WindowScreenshot {
270  id: windowScreenshot
271  visible: false
272  width: root.width
273  height: root.height
274 
275  property real transformRotationAngle
276  property real transformOriginX
277  property real transformOriginY
278 
279  transform: Rotation {
280  origin.x: windowScreenshot.transformOriginX; origin.y: windowScreenshot.transformOriginY;
281  axis { x: 0; y: 0; z: 1 }
282  angle: windowScreenshot.transformRotationAngle
283  }
284  }
285 }