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