on 239fdcc help sign in
unity8/qml/OrientedShell.qml
310 lines 11 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 DeviceConfiguration {
36 id: deviceConfiguration
37 name: applicationArguments.deviceName
38 }
40 property alias orientations: d.orientations
42 Item {
43 id: d
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
50 primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
51 ? native_ : deviceConfiguration.primaryOrientation
53 landscape: deviceConfiguration.landscapeOrientation
54 invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
55 portrait: deviceConfiguration.portraitOrientation
56 invertedPortrait: deviceConfiguration.invertedPortraitOrientation
57 }
58 }
60 GSettings {
61 id: unity8Settings
62 schema.id: "com.canonical.Unity8"
63 }
65 GSettings {
66 id: oskSettings
67 objectName: "oskSettings"
68 schema.id: "com.canonical.keyboard.maliit"
69 }
71 property int physicalOrientation: Screen.orientation
72 property bool orientationLocked: OrientationLock.enabled
73 property var orientationLock: OrientationLock
75 InputDeviceModel {
76 id: miceModel
77 deviceFilter: InputInfo.Mouse
78 property int oldCount: 0
79 }
81 InputDeviceModel {
82 id: touchPadModel
83 deviceFilter: InputInfo.TouchPad
84 property int oldCount: 0
85 }
87 InputDeviceModel {
88 id: keyboardsModel
89 deviceFilter: InputInfo.Keyboard
90 onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
91 onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
92 }
94 InputDeviceModel {
95 id: touchScreensModel
96 deviceFilter: InputInfo.TouchScreen
97 }
99 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
100 onPointerInputDevicesChanged: calculateUsageMode()
102 function calculateUsageMode() {
103 if (unity8Settings.usageMode === undefined)
104 return; // gsettings isn't loaded yet, we'll try again in Component.onCompleted
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 }
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 }
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 }
147 Screens {
148 id: screens
149 }
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 }
169 calculateUsageMode();
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 }
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
181 Binding {
182 target: oskSettings
183 property: "disableHeight"
184 value: !shell.oskEnabled || shell.usageScenario == "desktop"
185 }
187 readonly property int supportedOrientations: shell.supportedOrientations
188 & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
189 ? orientations.native_
190 : deviceConfiguration.supportedOrientations)
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 }
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 }
237 RotationStates {
238 id: rotationStates
239 objectName: "rotationStates"
240 orientedShell: root
241 shell: shell
242 shellCover: shellCover
243 shellSnapshot: shellSnapshot
244 }
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
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 }
276 property real transformRotationAngle
277 property real transformOriginX
278 property real transformOriginY
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 }
286 Rectangle {
287 id: shellCover
288 color: "black"
289 anchors.fill: parent
290 visible: false
291 }
293 ItemSnapshot {
294 id: shellSnapshot
295 target: shell
296 visible: false
297 width: root.width
298 height: root.height
300 property real transformRotationAngle
301 property real transformOriginX
302 property real transformOriginY
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 }