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  Binding {
102  target: QuickUtils
103  property: "keyboardAttached"
104  value: keyboardsModel.count > 0
105  }
106 
107  readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
108  onPointerInputDevicesChanged: calculateUsageMode()
109 
110  function calculateUsageMode() {
111  if (unity8Settings.usageMode === undefined)
112  return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
113 
114  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))
115  if (unity8Settings.usageMode === "Windowed") {
116  if (Math.min(root.width, root.height) > units.gu(60)) {
117  if (pointerInputDevices === 0) {
118  // All pointer devices have been unplugged. Move to staged.
119  unity8Settings.usageMode = "Staged";
120  }
121  } else {
122  // The display is not large enough, use staged.
123  unity8Settings.usageMode = "Staged";
124  }
125  } else {
126  if (Math.min(root.width, root.height) > units.gu(60)) {
127  if (pointerInputDevices > 0 && pointerInputDevices > miceModel.oldCount + touchPadModel.oldCount) {
128  unity8Settings.usageMode = "Windowed";
129  }
130  } else {
131  // Make sure we initialize to something sane
132  unity8Settings.usageMode = "Staged";
133  }
134  }
135  miceModel.oldCount = miceModel.count;
136  touchPadModel.oldCount = touchPadModel.count;
137  }
138 
139  /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
140  * When QInputInfo exposes NameRole to QML, this should be removed.
141  */
142  property bool forceOSKEnabled: false
143  property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
144  UnitySortFilterProxyModel {
145  id: autopilotDevices
146  model: keyboardsModel
147  }
148 
149  function autopilotDevicePresent() {
150  for(var i = 0; i < autopilotDevices.count; i++) {
151  var device = autopilotDevices.get(i);
152  if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
153  console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
154  return true;
155  }
156  }
157  return false;
158  }
159 
160  Screens {
161  id: screens
162  }
163 
164  property int orientation
165  onPhysicalOrientationChanged: {
166  if (!orientationLocked) {
167  orientation = physicalOrientation;
168  }
169  }
170  onOrientationLockedChanged: {
171  if (orientationLocked) {
172  orientationLock.savedOrientation = physicalOrientation;
173  } else {
174  orientation = physicalOrientation;
175  }
176  }
177  Component.onCompleted: {
178  if (orientationLocked) {
179  orientation = orientationLock.savedOrientation;
180  }
181 
182  calculateUsageMode();
183 
184  // We need to manually update this on startup as the binding
185  // below doesn't seem to have any effect at that stage
186  oskSettings.disableHeight = !shell.oskEnabled || shell.usageScenario == "desktop"
187  }
188 
189  // we must rotate to a supported orientation regardless of shell's preference
190  property bool orientationChangesEnabled:
191  (shell.orientation & supportedOrientations) === 0 ? true
192  : shell.orientationChangesEnabled
193 
194  Binding {
195  target: oskSettings
196  property: "disableHeight"
197  value: !shell.oskEnabled || shell.usageScenario == "desktop"
198  }
199 
200  Binding {
201  target: unity8Settings
202  property: "oskSwitchVisible"
203  value: shell.hasKeyboard
204  }
205 
206  readonly property int supportedOrientations: shell.supportedOrientations
207  & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
208  ? orientations.native_
209  : deviceConfiguration.supportedOrientations)
210 
211  property int acceptedOrientationAngle: {
212  if (orientation & supportedOrientations) {
213  return Screen.angleBetween(orientations.native_, orientation);
214  } else if (shell.orientation & supportedOrientations) {
215  // stay where we are
216  return shell.orientationAngle;
217  } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
218  return shell.mainAppWindowOrientationAngle;
219  } else {
220  // rotate to some supported orientation as we can't stay where we currently are
221  // TODO: Choose the closest to the current one
222  if (supportedOrientations & Qt.PortraitOrientation) {
223  return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
224  } else if (supportedOrientations & Qt.LandscapeOrientation) {
225  return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
226  } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
227  return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
228  } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
229  return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
230  } else {
231  // if all fails, fallback to primary orientation
232  return Screen.angleBetween(orientations.native_, orientations.primary);
233  }
234  }
235  }
236 
237  function angleToOrientation(angle) {
238  switch (angle) {
239  case 0:
240  return orientations.native_;
241  case 90:
242  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
243  : Qt.PortraitOrientation;
244  case 180:
245  return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
246  : Qt.InvertedLandscapeOrientation;
247  case 270:
248  return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
249  : Qt.InvertedPortraitOrientation;
250  default:
251  console.warn("angleToOrientation: Invalid orientation angle: " + angle);
252  return orientations.primary;
253  }
254  }
255 
256  RotationStates {
257  id: rotationStates
258  objectName: "rotationStates"
259  orientedShell: root
260  shell: shell
261  shellCover: shellCover
262  shellSnapshot: shellSnapshot
263  }
264 
265  Shell {
266  id: shell
267  objectName: "shell"
268  width: root.width
269  height: root.height
270  orientation: root.angleToOrientation(orientationAngle)
271  orientations: root.orientations
272  nativeWidth: root.width
273  nativeHeight: root.height
274  mode: applicationArguments.mode
275  hasMouse: pointerInputDevices > 0
276  hasKeyboard: keyboardsModel.count > 0
277  hasTouchscreen: touchScreensModel.count > 0
278  oskEnabled: unity8Settings.alwaysShowOsk || !hasKeyboard || forceOSKEnabled
279 
280  usageScenario: {
281  if (unity8Settings.usageMode === "Windowed") {
282  return "desktop";
283  } else {
284  if (deviceConfiguration.category === "phone") {
285  return "phone";
286  } else {
287  return "tablet";
288  }
289  }
290  }
291 
292  property real transformRotationAngle
293  property real transformOriginX
294  property real transformOriginY
295 
296  transform: Rotation {
297  origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
298  angle: shell.transformRotationAngle
299  }
300  }
301 
302  Rectangle {
303  id: shellCover
304  color: "black"
305  anchors.fill: parent
306  visible: false
307  }
308 
309  ItemSnapshot {
310  id: shellSnapshot
311  target: shell
312  visible: false
313  width: root.width
314  height: root.height
315 
316  property real transformRotationAngle
317  property real transformOriginX
318  property real transformOriginY
319 
320  transform: Rotation {
321  origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
322  axis { x: 0; y: 0; z: 1 }
323  angle: shellSnapshot.transformRotationAngle
324  }
325  }
326 }