on bde717c help sign in
unity8/qml/OrientedShell.qml
324 lines 12 kB history raw
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 */
17import QtQuick 2.4
18import QtQuick.Window 2.2
19import Unity.InputInfo 0.1
20import Unity.Session 0.1
21import Unity.Screens 0.1
22import Utils 0.1
23import GSettings 1.0
24import "Components"
25import "Rotation"
26// Workaround https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1473471
27import Ubuntu.Components 1.3
29Item {
30 id: root
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
35 onWidthChanged: calculateUsageMode();
37 DeviceConfiguration {
38 id: deviceConfiguration
39 name: applicationArguments.deviceName
40 }
42 property alias orientations: d.orientations
44 Item {
45 id: d
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
52 primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
53 ? native_ : deviceConfiguration.primaryOrientation
55 landscape: deviceConfiguration.landscapeOrientation
56 invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
57 portrait: deviceConfiguration.portraitOrientation
58 invertedPortrait: deviceConfiguration.invertedPortraitOrientation
59 }
60 }
62 GSettings {
63 id: unity8Settings
64 schema.id: "com.canonical.Unity8"
65 }
67 GSettings {
68 id: oskSettings
69 objectName: "oskSettings"
70 schema.id: "com.canonical.keyboard.maliit"
71 }
73 property int physicalOrientation: Screen.orientation
74 property bool orientationLocked: OrientationLock.enabled
75 property var orientationLock: OrientationLock
77 InputDeviceModel {
78 id: miceModel
79 deviceFilter: InputInfo.Mouse
80 property int oldCount: 0
81 }
83 InputDeviceModel {
84 id: touchPadModel
85 deviceFilter: InputInfo.TouchPad
86 property int oldCount: 0
87 }
89 InputDeviceModel {
90 id: keyboardsModel
91 deviceFilter: InputInfo.Keyboard
92 onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
93 onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
94 }
96 InputDeviceModel {
97 id: touchScreensModel
98 deviceFilter: InputInfo.TouchScreen
99 }
101 Binding {
102 target: QuickUtils
103 property: "keyboardAttached"
104 value: keyboardsModel.count > 0
105 }
107 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
108 onPointerInputDevicesChanged: calculateUsageMode()
110 function calculateUsageMode() {
111 if (unity8Settings.usageMode === undefined)
112 return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
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 }
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 }
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 }
160 Screens {
161 id: screens
162 }
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 }
182 calculateUsageMode();
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 }
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
194 Binding {
195 target: oskSettings
196 property: "disableHeight"
197 value: !shell.oskEnabled || shell.usageScenario == "desktop"
198 }
200 readonly property int supportedOrientations: shell.supportedOrientations
201 & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
202 ? orientations.native_
203 : deviceConfiguration.supportedOrientations)
205 property int acceptedOrientationAngle: {
206 if (orientation & supportedOrientations) {
207 return Screen.angleBetween(orientations.native_, orientation);
208 } else if (shell.orientation & supportedOrientations) {
209 // stay where we are
210 return shell.orientationAngle;
211 } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
212 return shell.mainAppWindowOrientationAngle;
213 } else {
214 // rotate to some supported orientation as we can't stay where we currently are
215 // TODO: Choose the closest to the current one
216 if (supportedOrientations & Qt.PortraitOrientation) {
217 return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
218 } else if (supportedOrientations & Qt.LandscapeOrientation) {
219 return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
220 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
221 return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
222 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
223 return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
224 } else {
225 // if all fails, fallback to primary orientation
226 return Screen.angleBetween(orientations.native_, orientations.primary);
227 }
228 }
229 }
231 function angleToOrientation(angle) {
232 switch (angle) {
233 case 0:
234 return orientations.native_;
235 case 90:
236 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
237 : Qt.PortraitOrientation;
238 case 180:
239 return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
240 : Qt.InvertedLandscapeOrientation;
241 case 270:
242 return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
243 : Qt.InvertedPortraitOrientation;
244 default:
245 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
246 return orientations.primary;
247 }
248 }
250 RotationStates {
251 id: rotationStates
252 objectName: "rotationStates"
253 orientedShell: root
254 shell: shell
255 shellCover: shellCover
256 shellSnapshot: shellSnapshot
257 }
259 Shell {
260 id: shell
261 objectName: "shell"
262 width: root.width
263 height: root.height
264 orientation: root.angleToOrientation(orientationAngle)
265 orientations: root.orientations
266 nativeWidth: root.width
267 nativeHeight: root.height
268 mode: applicationArguments.mode
269 hasMouse: pointerInputDevices > 0
270 hasKeyboard: keyboardsModel.count > 0
271 // TODO: Factor in if the current screen is a touch screen and if the user wants to
272 // have multiple keyboards around. For now we only enable one keyboard at a time
273 // thus hiding it here if there is a physical one around or if we have a second
274 // screen (the virtual touchpad & osk on the phone) attached.
275 oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
276 forceOSKEnabled
278 usageScenario: {
279 if (unity8Settings.usageMode === "Windowed") {
280 return "desktop";
281 } else {
282 if (deviceConfiguration.category === "phone") {
283 return "phone";
284 } else {
285 return "tablet";
286 }
287 }
288 }
290 property real transformRotationAngle
291 property real transformOriginX
292 property real transformOriginY
294 transform: Rotation {
295 origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
296 angle: shell.transformRotationAngle
297 }
298 }
300 Rectangle {
301 id: shellCover
302 color: "black"
303 anchors.fill: parent
304 visible: false
305 }
307 ItemSnapshot {
308 id: shellSnapshot
309 target: shell
310 visible: false
311 width: root.width
312 height: root.height
314 property real transformRotationAngle
315 property real transformOriginX
316 property real transformOriginY
318 transform: Rotation {
319 origin.x: shellSnapshot.transformOriginX; origin.y: shellSnapshot.transformOriginY;
320 axis { x: 0; y: 0; z: 1 }
321 angle: shellSnapshot.transformRotationAngle
322 }
323 }