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