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