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