Merge lp:~dandrader/unity8/noStretchOnResize into lp:unity8

Proposed by Michał Sawicz on 2015-09-18
Status: Superseded
Proposed branch: lp:~dandrader/unity8/noStretchOnResize
Merge into: lp:unity8
Diff against target: 3038 lines (+2180/-189)
41 files modified
CMakeLists.txt (+1/-1)
debian/control (+6/-4)
debian/copyright (+21/-0)
debian/unity8-private.install (+1/-0)
plugins/CMakeLists.txt (+1/-0)
plugins/Cursor/3rd_party/CMakeLists.txt (+1/-0)
plugins/Cursor/3rd_party/xcursor/CMakeLists.txt (+15/-0)
plugins/Cursor/3rd_party/xcursor/xcursor.c (+968/-0)
plugins/Cursor/3rd_party/xcursor/xcursor.h (+65/-0)
plugins/Cursor/CMakeLists.txt (+28/-0)
plugins/Cursor/Cursor.qml (+12/-0)
plugins/Cursor/CursorImageProvider.cpp (+191/-0)
plugins/Cursor/CursorImageProvider.h (+76/-0)
plugins/Cursor/MousePointer.cpp (+125/-0)
plugins/Cursor/MousePointer.h (+59/-0)
plugins/Cursor/plugin.cpp (+39/-0)
plugins/Cursor/plugin.h (+33/-0)
plugins/Cursor/qmldir (+3/-0)
qml/Greeter/Greeter.qml (+1/-1)
qml/Launcher/Launcher.qml (+1/-0)
qml/Panel/Panel.qml (+1/-0)
qml/Shell.qml (+14/-7)
qml/Stages/ApplicationWindow.qml (+27/-1)
qml/Stages/DecoratedWindow.qml (+13/-5)
qml/Stages/DesktopStage.qml (+11/-15)
qml/Stages/SessionContainer.qml (+25/-1)
qml/Stages/SurfaceContainer.qml (+57/-4)
qml/Stages/WindowDecoration.qml (+32/-4)
qml/Stages/WindowResizeArea.qml (+170/-69)
tests/mocks/CMakeLists.txt (+1/-0)
tests/mocks/Cursor/CMakeLists.txt (+1/-0)
tests/mocks/Cursor/Cursor.qml (+20/-0)
tests/mocks/Cursor/qmldir (+2/-0)
tests/mocks/Unity/Application/MirSurface.cpp (+52/-0)
tests/mocks/Unity/Application/MirSurface.h (+16/-0)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+14/-0)
tests/mocks/Unity/Application/MirSurfaceItem.h (+5/-0)
tests/qmltests/CMakeLists.txt (+1/-1)
tests/qmltests/Stages/tst_DesktopStage.qml (+18/-2)
tests/qmltests/Stages/tst_WindowResizeArea.qml (+49/-74)
tests/qmltests/tst_OrientedShell.qml (+4/-0)
To merge this branch: bzr merge lp:~dandrader/unity8/noStretchOnResize
Reviewer Review Type Date Requested Status
Michael Zanetti (community) 2015-09-18 Needs Fixing on 2015-09-23
PS Jenkins bot continuous-integration 2015-09-18 Needs Fixing on 2015-09-18
Review via email: mp+271604@code.launchpad.net

This proposal has been superseded by a proposal from 2015-10-14.

Commit Message

Don't stretch application surfaces when resizing

Description of the Change

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~dandrader/unity-api/surfaceItemFillMode/+merge/271619
https://code.launchpad.net/~dandrader/qtmir/surfaceItemFillMode/+merge/270859

* Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

* Did you make sure that your branch does not contain spurious tags?

Yes

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

Not applicable

* If you changed the UI, has there been a design review?

Not applicable

To post a comment you must log in.
1969. By Launchpad Translations on behalf of unity-team on 2015-09-19

Launchpad automatic translations update.

1970. By Launchpad Translations on behalf of unity-team on 2015-09-22

Launchpad automatic translations update.

1971. By Michael Terry on 2015-09-22

When libusermetrics gives us an empty string, still show the infographic circle rather than hiding it. This is a follow-on to a branch [1] that changes the "no data available" label to the empty string.

[1] https://code.launchpad.net/~mpt/libusermetrics/1286276-no-data-sources/+merge/268042

1972. By Albert Astals Cid on 2015-09-22

Adapt test to code changes

Approved by: Michał Sawicz

1973. By Michał Sawicz on 2015-09-22

Fix integrated LightDM path
Approved by: Albert Astals Cid

1974. By Albert Astals Cid on 2015-09-22

Add DEP-8 test for all our UI and unit tests

Some refactoring was needed:
- added a basic Ubuntu.Web mock
- made plugin path an environment, not a compiled-in bit
- moved Unity.Application resources into .qrc

1975. By Albert Astals Cid on 2015-09-22

Keep the PreviewStack around to avoid mem leaks Fixes: #1495467
Approved by: Michał Sawicz

1976. By CI Train Bot Account on 2015-09-22

Releasing 8.11+15.10.20150922.1-0ubuntu1

Michael Zanetti (mzanetti) wrote :

When resizing I can see the panel and the dropshadow resizing faster than the content. I think it would be better to not resize those either until the surface has caught up.

Other than that, it seems to work fine.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

On 23/09/2015 07:31, Michael Zanetti wrote:
> Review: Needs Fixing
>
> When resizing I can see the panel and the dropshadow resizing faster than the content. I think it would be better to not resize those either until the surface has caught up.
>
> Other than that, it seems to work fine.

It's a design decision: should we show something like a translucent
orang rect that tightly follows the mouse pointer (therefore showing
shell's responsiveness and a smooth animation) or have the window stick
to the surface size (as you prefer, but risking showing a stuttering,
unresponsive, shell)?

I don't have a strong opinion on that, but I do prefer first option. But
I'm ok with your preference as well. Any of those are better than the
current state. We should get design input on that (not sure *how* to do
that)

kevin gunn (kgunn72) wrote :

> On 23/09/2015 07:31, Michael Zanetti wrote:
> > Review: Needs Fixing
> >
> > When resizing I can see the panel and the dropshadow resizing faster than
> the content. I think it would be better to not resize those either until the
> surface has caught up.
> >
> > Other than that, it seems to work fine.
>
> It's a design decision: should we show something like a translucent
> orang rect that tightly follows the mouse pointer (therefore showing
> shell's responsiveness and a smooth animation) or have the window stick
> to the surface size (as you prefer, but risking showing a stuttering,
> unresponsive, shell)?
>
> I don't have a strong opinion on that, but I do prefer first option. But
> I'm ok with your preference as well. Any of those are better than the
> current state. We should get design input on that (not sure *how* to do
> that)

i would vote we land for the speedy soln now, and then log a bug subsequent to this landing outlining the choices between speed vs nice tight animation tween window/shadow

1977. By Launchpad Translations on behalf of unity-team on 2015-09-24

Launchpad automatic translations update.

1978. By Launchpad Translations on behalf of unity-team on 2015-09-25

Launchpad automatic translations update.

1979. By Michał Sawicz on 2015-09-25

Bump application API version
Approved by: Daniel d'Andrada

1980. By Albert Astals Cid on 2015-09-25

Improvements from running clazy over the code

* Use QStringLiterals for literals
* Use QHash on pointers instead of QMap (since the ordering there can't matter)
* Add some QLatin1Strings
* Add some reserve() calls
* Change a QList to QVector since QList is not optimal on big items
* Add some const &

clazy: https://quickgit.kde.org/?p=scratch%2Fsmartins%2Fclazy.git
Ignored tests folder
Approved by: Michael Zanetti

1981. By Lukáš Tinkl on 2015-09-25

Improve the appearance and functionality of datetime and session indicators in Unity8 desktop.
Approved by: Nick Dedekind

1982. By Lukáš Tinkl on 2015-09-25

Using InputInfo, determine if we need swipe or click to dismiss the notifications (e.g. incoming phone call)
Approved by: Michael Zanetti

1983. By Richard Somlói on 2015-09-25

Fix translator comments in time formatter
Approved by: Nick Dedekind

1984. By Daniel d'Andrada on 2015-09-25

PhoneStage: ensure you're left in a consistent state after being reset Fixes: #1476757
Approved by: Michael Zanetti

1985. By Michał Sawicz on 2015-09-25

Skip bluetooth check under wily, use expectFailure to notice when it's back
Approved by: Albert Astals Cid

1986. By Michael Zanetti on 2015-09-25

further development on the Desktop Spread

* Refactor bits to keep the spread separate from the regular states
* Add blurring to background
* make the workspace preview work as per spec
* Improve animations
* fix bugs Fixes: #1488147
Approved by: Lukáš Tinkl

1987. By CI Train Bot Account on 2015-09-25

Releasing 8.11+15.10.20150925-0ubuntu1

1988. By Launchpad Translations on behalf of unity-team on 2015-09-30

Launchpad automatic translations update.

1989. By Michael Zanetti on 2015-09-30

Stabilize Launcher DND test
Approved by: Albert Astals Cid

1990. By Albert Astals Cid on 2015-09-30

Make Launcher::test_quickListMenuOnRMB stable

We need the x to be exactly 0 and compare() does say true for values very close to 0
 Fixes: #1500359
Approved by: Michael Zanetti

1991. By CI Train Bot Account on 2015-09-30

Releasing 8.11+15.10.20150930.1-0ubuntu1

1992. By Alexandros Frantzis on 2015-10-01

Use power state change reason SnapDecision to turn screen on when a SnapDecision arrives

Using this value allows USC to handle proximity correctly for snap decision prompts. Fixes: #1291455
Approved by: Lukáš Tinkl

1993. By CI Train Bot Account on 2015-10-01

Releasing 8.11+15.10.20151001-0ubuntu1

1994. By CI Train Bot Account on 2015-10-02

Resync trunk.

1995. By Lukáš Tinkl on 2015-10-02

Wizard: skip the reporting page if the system is configured not to report crashes Fixes: #1494442
Approved by: Ken VanDine

1996. By CI Train Bot Account on 2015-10-02

Releasing 8.11+15.10.20151002-0ubuntu1

1997. By Launchpad Translations on behalf of unity-team on 2015-10-03

Launchpad automatic translations update.

1998. By Launchpad Translations on behalf of unity-team on 2015-10-06

Launchpad automatic translations update.

1999. By Launchpad Translations on behalf of unity-team on 2015-10-07

Launchpad automatic translations update.

Albert Astals Cid (aacid) wrote :

Text conflict in CMakeLists.txt
1 conflicts encountered.

2000. By Lukáš Tinkl on 2015-10-08

Revert to using plaintext instead of HTML-like markup for the notifications Fixes: #1504256

2001. By CI Train Bot Account on 2015-10-08

Releasing 8.11+15.10.20151008-0ubuntu1

Daniel d'Andrada (dandrader) wrote :

> When resizing I can see the panel and the dropshadow resizing faster than the
> content. I think it would be better to not resize those either until the
> surface has caught up.
>
> Other than that, it seems to work fine.

Now it's exactly what you asked for.

2002. By Paweł Stołowski on 2015-10-09

Store unity8 package version in /var/lib/.../version file for fast retrieval from the shell plugin.
Approved by: Albert Astals Cid

2003. By CI Train Bot Account on 2015-10-09

Releasing 8.11+15.10.20151009-0ubuntu1

2004. By Launchpad Translations on behalf of unity-team on 2015-10-16

Launchpad automatic translations update.

2005. By Daniel d'Andrada on 2015-10-22

Merge lp:~unity-team/unity8/externalMonitor

2006. By Daniel d'Andrada on 2015-10-22

Don't stretch application surfaces when resizing

2007. By Daniel d'Andrada on 2015-10-22

Address review comments

2008. By Daniel d'Andrada on 2015-10-23

Fix resize of top/left borders for slow surfaces

2009. By Daniel d'Andrada on 2015-10-27

Merge trunk

[ Albert Astals Cid ]
* Clazy fixes
* Enable Efficient String Construction by default
[ CI Train Bot ]
* New rebuild forced.
[ Daniel d'Andrada ]
* Have unity8 drawing its own cursor (LP: #1488417)
* Initial multi-monitor support
[ Daniel van Vugt ]
* Disable Qt's stuttering 'touch compression' to fix scrolling
  smoothness (LP: #1486341, #1488327)
[ Lukáš Tinkl ]
* Fix autopilot wizard test skipping the reporting page
* Implement Unity.Platform plugin wrapping org.freedesktop.hostname1
  (LP: #1504318)
* React to window title (aka surface name) changes (LP: #1497092)
* Rotate the screenshots according to the actual orientation
[ Michał Sawicz ]
* Fix application API dependency
* Have unity8 drawing its own cursor (LP: #1488417)
* Initial multi-monitor support
* Rotate the screenshots according to the actual orientation
[ Nick Dedekind ]
* Fixed leak in UnityMenuModelStackTest
* Moved time translation to SDK (LP: #1372061)

2010. By Daniel d'Andrada on 2015-11-06

Merge trunk

2011. By Daniel d'Andrada on 2015-11-06

Fix typo

2012. By Daniel d'Andrada on 2015-11-09

Remove dependency on MirSurfaceItem.fillMode property

2013. By Daniel d'Andrada on 2015-11-20

Merge trunk

[ Albert Astals Cid ]
* Make cardWidth and cardHeight real
* Reset instead of qFatal when removing things from the middle (LP:
  #1238979)
* Warn we're using only the cache when not connected to the interwebs
[ Andrea Cimitan ]
* Add sharing widget to zoomable image and video playback
[ Daniel d'Andrada ]
* Cursor: properly initialize hotspot position (LP: #1510407)
* Update GSettings mock in tst_OrientedShell
[ Lukáš Tinkl ]
* Restore windows when activating from the spread, maintain a focus
  stack
[ Michael Terry ]
* Make a few DBus calls asynchronous, for a smoother UX.
[ Michael Zanetti ]
* Add a warning dialog when disconnecting the external monitor.
* added icon for the dash (LP: #1488146)
* prevent windows to be moved under the panel (LP: #1438465)
* update inputinfo api to the latest upstream snapshot
* use UbuntuNumberAnimations instead of linear ones for window state
  transitions (LP: #1497097)

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-09-30 16:49:52 +0000
3+++ CMakeLists.txt 2015-10-14 19:15:46 +0000
4@@ -57,7 +57,7 @@
5 find_package(Qt5Concurrent 5.2 REQUIRED)
6 find_package(Qt5Sql 5.2 REQUIRED)
7
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application>=8)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
10
11 # Standard install paths
12 include(GNUInstallDirs)
13
14=== modified file 'debian/control'
15--- debian/control 2015-09-23 18:15:40 +0000
16+++ debian/control 2015-10-14 19:15:46 +0000
17@@ -25,10 +25,11 @@
18 libpay2-dev,
19 libpulse-dev,
20 libqmenumodel-dev (>= 0.2.9),
21+ libqt5svg5-dev,
22 libqt5xmlpatterns5-dev,
23 libsystemsettings-dev,
24 libudev-dev,
25- libunity-api-dev (>= 7.100),
26+ libunity-api-dev (>= 7.101),
27 libusermetricsoutput1-dev,
28 libxcb1-dev,
29 pkg-config,
30@@ -87,7 +88,8 @@
31 Package: unity8
32 Architecture: any
33 Provides: indicator-renderer,
34-Depends: gsettings-desktop-schemas,
35+Depends: dmz-cursor-theme,
36+ gsettings-desktop-schemas,
37 libcap2-bin,
38 libglib2.0-bin,
39 qmenumodel-qml (>= 0.2.9),
40@@ -125,7 +127,7 @@
41 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
42 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
43 ubuntu-thumbnailer-impl-0,
44- unity-application-impl-8,
45+ unity-application-impl-9,
46 unity-notifications-impl-3,
47 unity-plugin-scopes | unity-scopes-impl,
48 unity-scopes-impl-7,
49@@ -171,7 +173,7 @@
50 Depends: ${misc:Depends},
51 ${shlibs:Depends},
52 Provides: unity-application-impl,
53- unity-application-impl-8,
54+ unity-application-impl-9,
55 Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
56 Description: Fake environment for running Unity 8 shell
57 Provides fake implementations of some QML modules used by Unity 8 shell
58
59=== modified file 'debian/copyright'
60--- debian/copyright 2014-06-11 15:36:51 +0000
61+++ debian/copyright 2015-10-14 19:15:46 +0000
62@@ -60,3 +60,24 @@
63 packaging of this file. Please review the following information to
64 ensure the GNU General Public License version 3.0 requirements will be
65 met: http://www.gnu.org/copyleft/gpl.html.
66+
67+Files: plugins/Cursor/3rd_party/xcursor/xcursor.*
68+Copyright: 2002 Keith Packard
69+License: Keith Packard
70+ Permission to use, copy, modify, distribute, and sell this software and its
71+ documentation for any purpose is hereby granted without fee, provided that
72+ the above copyright notice appear in all copies and that both that
73+ copyright notice and this permission notice appear in supporting
74+ documentation, and that the name of Keith Packard not be used in
75+ advertising or publicity pertaining to distribution of the software without
76+ specific, written prior permission. Keith Packard makes no
77+ representations about the suitability of this software for any purpose. It
78+ is provided "as is" without express or implied warranty.
79+ .
80+ KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
81+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
82+ EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
83+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
84+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
85+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
86+ PERFORMANCE OF THIS SOFTWARE.
87
88=== modified file 'debian/unity8-private.install'
89--- debian/unity8-private.install 2015-09-02 09:30:32 +0000
90+++ debian/unity8-private.install 2015-10-14 19:15:46 +0000
91@@ -1,6 +1,7 @@
92 usr/lib/*/libunity8-private.*
93 usr/lib/*/unity8/libUbuntuGestures*
94 usr/lib/*/unity8/qml/AccountsService
95+usr/lib/*/unity8/qml/Cursor
96 usr/lib/*/unity8/qml/Dash
97 usr/lib/*/unity8/qml/GlobalShortcut
98 usr/lib/*/unity8/qml/Greeter
99
100=== modified file 'plugins/CMakeLists.txt'
101--- plugins/CMakeLists.txt 2015-09-02 09:30:32 +0000
102+++ plugins/CMakeLists.txt 2015-10-14 19:15:46 +0000
103@@ -12,6 +12,7 @@
104 endmacro()
105
106 add_subdirectory(AccountsService)
107+add_subdirectory(Cursor)
108 add_subdirectory(GlobalShortcut)
109 add_subdirectory(Greeter)
110 add_subdirectory(IntegratedLightDM)
111
112=== added directory 'plugins/Cursor'
113=== added directory 'plugins/Cursor/3rd_party'
114=== added file 'plugins/Cursor/3rd_party/CMakeLists.txt'
115--- plugins/Cursor/3rd_party/CMakeLists.txt 1970-01-01 00:00:00 +0000
116+++ plugins/Cursor/3rd_party/CMakeLists.txt 2015-10-14 19:15:46 +0000
117@@ -0,0 +1,1 @@
118+add_subdirectory(xcursor)
119
120=== added directory 'plugins/Cursor/3rd_party/xcursor'
121=== added file 'plugins/Cursor/3rd_party/xcursor/CMakeLists.txt'
122--- plugins/Cursor/3rd_party/xcursor/CMakeLists.txt 1970-01-01 00:00:00 +0000
123+++ plugins/Cursor/3rd_party/xcursor/CMakeLists.txt 2015-10-14 19:15:46 +0000
124@@ -0,0 +1,15 @@
125+add_definitions(-D_DEFAULT_SOURCE=1)
126+
127+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
128+
129+set(
130+ XCURSOR_SOURCES
131+
132+ xcursor.c
133+)
134+
135+add_library(
136+ xcursorloader-static STATIC
137+
138+ ${XCURSOR_SOURCES}
139+)
140
141=== added file 'plugins/Cursor/3rd_party/xcursor/xcursor.c'
142--- plugins/Cursor/3rd_party/xcursor/xcursor.c 1970-01-01 00:00:00 +0000
143+++ plugins/Cursor/3rd_party/xcursor/xcursor.c 2015-10-14 19:15:46 +0000
144@@ -0,0 +1,968 @@
145+/*
146+ * Copyright © 2002 Keith Packard
147+ *
148+ * Permission to use, copy, modify, distribute, and sell this software and its
149+ * documentation for any purpose is hereby granted without fee, provided that
150+ * the above copyright notice appear in all copies and that both that
151+ * copyright notice and this permission notice appear in supporting
152+ * documentation, and that the name of Keith Packard not be used in
153+ * advertising or publicity pertaining to distribution of the software without
154+ * specific, written prior permission. Keith Packard makes no
155+ * representations about the suitability of this software for any purpose. It
156+ * is provided "as is" without express or implied warranty.
157+ *
158+ * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
159+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
160+ * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
161+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
162+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
163+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
164+ * PERFORMANCE OF THIS SOFTWARE.
165+ */
166+
167+#include "xcursor.h"
168+#include <stdio.h>
169+#include <stdlib.h>
170+#include <string.h>
171+#include <dirent.h>
172+
173+/*
174+ * From libXcursor/include/X11/extensions/Xcursor.h
175+ */
176+
177+#define XcursorTrue 1
178+#define XcursorFalse 0
179+
180+/*
181+ * Cursor files start with a header. The header
182+ * contains a magic number, a version number and a
183+ * table of contents which has type and offset information
184+ * for the remaining tables in the file.
185+ *
186+ * File minor versions increment for compatible changes
187+ * File major versions increment for incompatible changes (never, we hope)
188+ *
189+ * Chunks of the same type are always upward compatible. Incompatible
190+ * changes are made with new chunk types; the old data can remain under
191+ * the old type. Upward compatible changes can add header data as the
192+ * header lengths are specified in the file.
193+ *
194+ * File:
195+ * FileHeader
196+ * LISTofChunk
197+ *
198+ * FileHeader:
199+ * CARD32 magic magic number
200+ * CARD32 header bytes in file header
201+ * CARD32 version file version
202+ * CARD32 ntoc number of toc entries
203+ * LISTofFileToc toc table of contents
204+ *
205+ * FileToc:
206+ * CARD32 type entry type
207+ * CARD32 subtype entry subtype (size for images)
208+ * CARD32 position absolute file position
209+ */
210+
211+#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */
212+
213+/*
214+ * Current Xcursor version number. Will be substituted by configure
215+ * from the version in the libXcursor configure.ac file.
216+ */
217+
218+#define XCURSOR_LIB_MAJOR 1
219+#define XCURSOR_LIB_MINOR 1
220+#define XCURSOR_LIB_REVISION 13
221+#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
222+ (XCURSOR_LIB_MINOR * 100) + \
223+ (XCURSOR_LIB_REVISION))
224+
225+/*
226+ * This version number is stored in cursor files; changes to the
227+ * file format require updating this version number
228+ */
229+#define XCURSOR_FILE_MAJOR 1
230+#define XCURSOR_FILE_MINOR 0
231+#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR))
232+#define XCURSOR_FILE_HEADER_LEN (4 * 4)
233+#define XCURSOR_FILE_TOC_LEN (3 * 4)
234+
235+typedef struct _XcursorFileToc {
236+ XcursorUInt type; /* chunk type */
237+ XcursorUInt subtype; /* subtype (size for images) */
238+ XcursorUInt position; /* absolute position in file */
239+} XcursorFileToc;
240+
241+typedef struct _XcursorFileHeader {
242+ XcursorUInt magic; /* magic number */
243+ XcursorUInt header; /* byte length of header */
244+ XcursorUInt version; /* file version number */
245+ XcursorUInt ntoc; /* number of toc entries */
246+ XcursorFileToc *tocs; /* table of contents */
247+} XcursorFileHeader;
248+
249+/*
250+ * The rest of the file is a list of chunks, each tagged by type
251+ * and version.
252+ *
253+ * Chunk:
254+ * ChunkHeader
255+ * <extra type-specific header fields>
256+ * <type-specific data>
257+ *
258+ * ChunkHeader:
259+ * CARD32 header bytes in chunk header + type header
260+ * CARD32 type chunk type
261+ * CARD32 subtype chunk subtype
262+ * CARD32 version chunk type version
263+ */
264+
265+#define XCURSOR_CHUNK_HEADER_LEN (4 * 4)
266+
267+typedef struct _XcursorChunkHeader {
268+ XcursorUInt header; /* bytes in chunk header */
269+ XcursorUInt type; /* chunk type */
270+ XcursorUInt subtype; /* chunk subtype (size for images) */
271+ XcursorUInt version; /* version of this type */
272+} XcursorChunkHeader;
273+
274+/*
275+ * Here's a list of the known chunk types
276+ */
277+
278+/*
279+ * Comments consist of a 4-byte length field followed by
280+ * UTF-8 encoded text
281+ *
282+ * Comment:
283+ * ChunkHeader header chunk header
284+ * CARD32 length bytes in text
285+ * LISTofCARD8 text UTF-8 encoded text
286+ */
287+
288+#define XCURSOR_COMMENT_TYPE 0xfffe0001
289+#define XCURSOR_COMMENT_VERSION 1
290+#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
291+#define XCURSOR_COMMENT_COPYRIGHT 1
292+#define XCURSOR_COMMENT_LICENSE 2
293+#define XCURSOR_COMMENT_OTHER 3
294+#define XCURSOR_COMMENT_MAX_LEN 0x100000
295+
296+typedef struct _XcursorComment {
297+ XcursorUInt version;
298+ XcursorUInt comment_type;
299+ char *comment;
300+} XcursorComment;
301+
302+/*
303+ * Each cursor image occupies a separate image chunk.
304+ * The length of the image header follows the chunk header
305+ * so that future versions can extend the header without
306+ * breaking older applications
307+ *
308+ * Image:
309+ * ChunkHeader header chunk header
310+ * CARD32 width actual width
311+ * CARD32 height actual height
312+ * CARD32 xhot hot spot x
313+ * CARD32 yhot hot spot y
314+ * CARD32 delay animation delay
315+ * LISTofCARD32 pixels ARGB pixels
316+ */
317+
318+#define XCURSOR_IMAGE_TYPE 0xfffd0002
319+#define XCURSOR_IMAGE_VERSION 1
320+#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4))
321+#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */
322+
323+typedef struct _XcursorFile XcursorFile;
324+
325+struct _XcursorFile {
326+ void *closure;
327+ int (*read) (XcursorFile *file, unsigned char *buf, int len);
328+ int (*write) (XcursorFile *file, unsigned char *buf, int len);
329+ int (*seek) (XcursorFile *file, long offset, int whence);
330+};
331+
332+typedef struct _XcursorComments {
333+ int ncomment; /* number of comments */
334+ XcursorComment **comments; /* array of XcursorComment pointers */
335+} XcursorComments;
336+
337+/*
338+ * From libXcursor/src/file.c
339+ */
340+
341+static XcursorImage *
342+XcursorImageCreate (int width, int height)
343+{
344+ XcursorImage *image;
345+
346+ image = malloc (sizeof (XcursorImage) +
347+ width * height * sizeof (XcursorPixel));
348+ if (!image)
349+ return NULL;
350+ image->version = XCURSOR_IMAGE_VERSION;
351+ image->pixels = (XcursorPixel *) (image + 1);
352+ image->size = width > height ? width : height;
353+ image->width = width;
354+ image->height = height;
355+ image->delay = 0;
356+ return image;
357+}
358+
359+static void
360+XcursorImageDestroy (XcursorImage *image)
361+{
362+ free (image);
363+}
364+
365+static XcursorImages *
366+XcursorImagesCreate (int size)
367+{
368+ XcursorImages *images;
369+
370+ images = malloc (sizeof (XcursorImages) +
371+ size * sizeof (XcursorImage *));
372+ if (!images)
373+ return NULL;
374+ images->nimage = 0;
375+ images->images = (XcursorImage **) (images + 1);
376+ images->name = NULL;
377+ return images;
378+}
379+
380+void
381+XcursorImagesDestroy (XcursorImages *images)
382+{
383+ int n;
384+
385+ if (!images)
386+ return;
387+
388+ for (n = 0; n < images->nimage; n++)
389+ XcursorImageDestroy (images->images[n]);
390+ if (images->name)
391+ free (images->name);
392+ free (images);
393+}
394+
395+static void
396+XcursorImagesSetName (XcursorImages *images, const char *name)
397+{
398+ char *new;
399+
400+ if (!images || !name)
401+ return;
402+
403+ new = malloc (strlen (name) + 1);
404+
405+ if (!new)
406+ return;
407+
408+ strcpy (new, name);
409+ if (images->name)
410+ free (images->name);
411+ images->name = new;
412+}
413+
414+static XcursorBool
415+_XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
416+{
417+ unsigned char bytes[4];
418+
419+ if (!file || !u)
420+ return XcursorFalse;
421+
422+ if ((*file->read) (file, bytes, 4) != 4)
423+ return XcursorFalse;
424+ *u = ((bytes[0] << 0) |
425+ (bytes[1] << 8) |
426+ (bytes[2] << 16) |
427+ (bytes[3] << 24));
428+ return XcursorTrue;
429+}
430+
431+static void
432+_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
433+{
434+ free (fileHeader);
435+}
436+
437+static XcursorFileHeader *
438+_XcursorFileHeaderCreate (int ntoc)
439+{
440+ XcursorFileHeader *fileHeader;
441+
442+ if (ntoc > 0x10000)
443+ return NULL;
444+ fileHeader = malloc (sizeof (XcursorFileHeader) +
445+ ntoc * sizeof (XcursorFileToc));
446+ if (!fileHeader)
447+ return NULL;
448+ fileHeader->magic = XCURSOR_MAGIC;
449+ fileHeader->header = XCURSOR_FILE_HEADER_LEN;
450+ fileHeader->version = XCURSOR_FILE_VERSION;
451+ fileHeader->ntoc = ntoc;
452+ fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
453+ return fileHeader;
454+}
455+
456+static XcursorFileHeader *
457+_XcursorReadFileHeader (XcursorFile *file)
458+{
459+ XcursorFileHeader head, *fileHeader;
460+ XcursorUInt skip;
461+ unsigned int n;
462+
463+ if (!file)
464+ return NULL;
465+
466+ if (!_XcursorReadUInt (file, &head.magic))
467+ return NULL;
468+ if (head.magic != XCURSOR_MAGIC)
469+ return NULL;
470+ if (!_XcursorReadUInt (file, &head.header))
471+ return NULL;
472+ if (!_XcursorReadUInt (file, &head.version))
473+ return NULL;
474+ if (!_XcursorReadUInt (file, &head.ntoc))
475+ return NULL;
476+ skip = head.header - XCURSOR_FILE_HEADER_LEN;
477+ if (skip)
478+ if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
479+ return NULL;
480+ fileHeader = _XcursorFileHeaderCreate (head.ntoc);
481+ if (!fileHeader)
482+ return NULL;
483+ fileHeader->magic = head.magic;
484+ fileHeader->header = head.header;
485+ fileHeader->version = head.version;
486+ fileHeader->ntoc = head.ntoc;
487+ for (n = 0; n < fileHeader->ntoc; n++)
488+ {
489+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
490+ break;
491+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
492+ break;
493+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
494+ break;
495+ }
496+ if (n != fileHeader->ntoc)
497+ {
498+ _XcursorFileHeaderDestroy (fileHeader);
499+ return NULL;
500+ }
501+ return fileHeader;
502+}
503+
504+static XcursorBool
505+_XcursorSeekToToc (XcursorFile *file,
506+ XcursorFileHeader *fileHeader,
507+ int toc)
508+{
509+ if (!file || !fileHeader || \
510+ (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
511+ return XcursorFalse;
512+ return XcursorTrue;
513+}
514+
515+static XcursorBool
516+_XcursorFileReadChunkHeader (XcursorFile *file,
517+ XcursorFileHeader *fileHeader,
518+ int toc,
519+ XcursorChunkHeader *chunkHeader)
520+{
521+ if (!file || !fileHeader || !chunkHeader)
522+ return XcursorFalse;
523+ if (!_XcursorSeekToToc (file, fileHeader, toc))
524+ return XcursorFalse;
525+ if (!_XcursorReadUInt (file, &chunkHeader->header))
526+ return XcursorFalse;
527+ if (!_XcursorReadUInt (file, &chunkHeader->type))
528+ return XcursorFalse;
529+ if (!_XcursorReadUInt (file, &chunkHeader->subtype))
530+ return XcursorFalse;
531+ if (!_XcursorReadUInt (file, &chunkHeader->version))
532+ return XcursorFalse;
533+ /* sanity check */
534+ if (chunkHeader->type != fileHeader->tocs[toc].type ||
535+ chunkHeader->subtype != fileHeader->tocs[toc].subtype)
536+ return XcursorFalse;
537+ return XcursorTrue;
538+}
539+
540+#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
541+
542+static XcursorDim
543+_XcursorFindBestSize (XcursorFileHeader *fileHeader,
544+ XcursorDim size,
545+ int *nsizesp)
546+{
547+ unsigned int n;
548+ int nsizes = 0;
549+ XcursorDim bestSize = 0;
550+ XcursorDim thisSize;
551+
552+ if (!fileHeader || !nsizesp)
553+ return 0;
554+
555+ for (n = 0; n < fileHeader->ntoc; n++)
556+ {
557+ if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
558+ continue;
559+ thisSize = fileHeader->tocs[n].subtype;
560+ if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
561+ {
562+ bestSize = thisSize;
563+ nsizes = 1;
564+ }
565+ else if (thisSize == bestSize)
566+ nsizes++;
567+ }
568+ *nsizesp = nsizes;
569+ return bestSize;
570+}
571+
572+static int
573+_XcursorFindImageToc (XcursorFileHeader *fileHeader,
574+ XcursorDim size,
575+ int count)
576+{
577+ unsigned int toc;
578+ XcursorDim thisSize;
579+
580+ if (!fileHeader)
581+ return 0;
582+
583+ for (toc = 0; toc < fileHeader->ntoc; toc++)
584+ {
585+ if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
586+ continue;
587+ thisSize = fileHeader->tocs[toc].subtype;
588+ if (thisSize != size)
589+ continue;
590+ if (!count)
591+ break;
592+ count--;
593+ }
594+ if (toc == fileHeader->ntoc)
595+ return -1;
596+ return toc;
597+}
598+
599+static XcursorImage *
600+_XcursorReadImage (XcursorFile *file,
601+ XcursorFileHeader *fileHeader,
602+ int toc)
603+{
604+ XcursorChunkHeader chunkHeader;
605+ XcursorImage head;
606+ XcursorImage *image;
607+ int n;
608+ XcursorPixel *p;
609+
610+ if (!file || !fileHeader)
611+ return NULL;
612+
613+ if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
614+ return NULL;
615+ if (!_XcursorReadUInt (file, &head.width))
616+ return NULL;
617+ if (!_XcursorReadUInt (file, &head.height))
618+ return NULL;
619+ if (!_XcursorReadUInt (file, &head.xhot))
620+ return NULL;
621+ if (!_XcursorReadUInt (file, &head.yhot))
622+ return NULL;
623+ if (!_XcursorReadUInt (file, &head.delay))
624+ return NULL;
625+ /* sanity check data */
626+ if (head.width >= 0x10000 || head.height > 0x10000)
627+ return NULL;
628+ if (head.width == 0 || head.height == 0)
629+ return NULL;
630+ if (head.xhot > head.width || head.yhot > head.height)
631+ return NULL;
632+
633+ /* Create the image and initialize it */
634+ image = XcursorImageCreate (head.width, head.height);
635+ if (image == NULL)
636+ return NULL;
637+ if (chunkHeader.version < image->version)
638+ image->version = chunkHeader.version;
639+ image->size = chunkHeader.subtype;
640+ image->xhot = head.xhot;
641+ image->yhot = head.yhot;
642+ image->delay = head.delay;
643+ n = image->width * image->height;
644+ p = image->pixels;
645+ while (n--)
646+ {
647+ if (!_XcursorReadUInt (file, p))
648+ {
649+ XcursorImageDestroy (image);
650+ return NULL;
651+ }
652+ p++;
653+ }
654+ return image;
655+}
656+
657+static XcursorImages *
658+XcursorXcFileLoadImages (XcursorFile *file, int size)
659+{
660+ XcursorFileHeader *fileHeader;
661+ XcursorDim bestSize;
662+ int nsize;
663+ XcursorImages *images;
664+ int n;
665+ int toc;
666+
667+ if (!file || size < 0)
668+ return NULL;
669+ fileHeader = _XcursorReadFileHeader (file);
670+ if (!fileHeader)
671+ return NULL;
672+ bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
673+ if (!bestSize)
674+ {
675+ _XcursorFileHeaderDestroy (fileHeader);
676+ return NULL;
677+ }
678+ images = XcursorImagesCreate (nsize);
679+ if (!images)
680+ {
681+ _XcursorFileHeaderDestroy (fileHeader);
682+ return NULL;
683+ }
684+ for (n = 0; n < nsize; n++)
685+ {
686+ toc = _XcursorFindImageToc (fileHeader, bestSize, n);
687+ if (toc < 0)
688+ break;
689+ images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
690+ toc);
691+ if (!images->images[images->nimage])
692+ break;
693+ images->nimage++;
694+ }
695+ _XcursorFileHeaderDestroy (fileHeader);
696+ if (images->nimage != nsize)
697+ {
698+ XcursorImagesDestroy (images);
699+ images = NULL;
700+ }
701+ return images;
702+}
703+
704+static int
705+_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
706+{
707+ FILE *f = file->closure;
708+ return fread (buf, 1, len, f);
709+}
710+
711+static int
712+_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
713+{
714+ FILE *f = file->closure;
715+ return fwrite (buf, 1, len, f);
716+}
717+
718+static int
719+_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
720+{
721+ FILE *f = file->closure;
722+ return fseek (f, offset, whence);
723+}
724+
725+static void
726+_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
727+{
728+ file->closure = stdfile;
729+ file->read = _XcursorStdioFileRead;
730+ file->write = _XcursorStdioFileWrite;
731+ file->seek = _XcursorStdioFileSeek;
732+}
733+
734+static XcursorImages *
735+XcursorFileLoadImages (FILE *file, int size)
736+{
737+ XcursorFile f;
738+
739+ if (!file)
740+ return NULL;
741+
742+ _XcursorStdioFileInitialize (file, &f);
743+ return XcursorXcFileLoadImages (&f, size);
744+}
745+
746+/*
747+ * From libXcursor/src/library.c
748+ */
749+
750+#ifndef ICONDIR
751+#define ICONDIR "/usr/X11R6/lib/X11/icons"
752+#endif
753+
754+#ifndef XCURSORPATH
755+#define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR
756+#endif
757+
758+static const char *
759+XcursorLibraryPath (void)
760+{
761+ static const char *path;
762+
763+ if (!path)
764+ {
765+ path = getenv ("XCURSOR_PATH");
766+ if (!path)
767+ path = XCURSORPATH;
768+ }
769+ return path;
770+}
771+
772+static void
773+_XcursorAddPathElt (char *path, const char *elt, int len)
774+{
775+ int pathlen = strlen (path);
776+
777+ /* append / if the path doesn't currently have one */
778+ if (path[0] == '\0' || path[pathlen - 1] != '/')
779+ {
780+ strcat (path, "/");
781+ pathlen++;
782+ }
783+ if (len == -1)
784+ len = strlen (elt);
785+ /* strip leading slashes */
786+ while (len && elt[0] == '/')
787+ {
788+ elt++;
789+ len--;
790+ }
791+ strncpy (path + pathlen, elt, len);
792+ path[pathlen + len] = '\0';
793+}
794+
795+static char *
796+_XcursorBuildThemeDir (const char *dir, const char *theme)
797+{
798+ const char *colon;
799+ const char *tcolon;
800+ char *full;
801+ char *home;
802+ int dirlen;
803+ int homelen;
804+ int themelen;
805+ int len;
806+
807+ if (!dir || !theme)
808+ return NULL;
809+
810+ colon = strchr (dir, ':');
811+ if (!colon)
812+ colon = dir + strlen (dir);
813+
814+ dirlen = colon - dir;
815+
816+ tcolon = strchr (theme, ':');
817+ if (!tcolon)
818+ tcolon = theme + strlen (theme);
819+
820+ themelen = tcolon - theme;
821+
822+ home = NULL;
823+ homelen = 0;
824+ if (*dir == '~')
825+ {
826+ home = getenv ("HOME");
827+ if (!home)
828+ return NULL;
829+ homelen = strlen (home);
830+ dir++;
831+ dirlen--;
832+ }
833+
834+ /*
835+ * add space for any needed directory separators, one per component,
836+ * and one for the trailing null
837+ */
838+ len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
839+
840+ full = malloc (len);
841+ if (!full)
842+ return NULL;
843+ full[0] = '\0';
844+
845+ if (home)
846+ _XcursorAddPathElt (full, home, -1);
847+ _XcursorAddPathElt (full, dir, dirlen);
848+ _XcursorAddPathElt (full, theme, themelen);
849+ return full;
850+}
851+
852+static char *
853+_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
854+{
855+ char *full;
856+
857+ if (!dir || !subdir || !file)
858+ return NULL;
859+
860+ full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
861+ if (!full)
862+ return NULL;
863+ full[0] = '\0';
864+ _XcursorAddPathElt (full, dir, -1);
865+ _XcursorAddPathElt (full, subdir, -1);
866+ _XcursorAddPathElt (full, file, -1);
867+ return full;
868+}
869+
870+static const char *
871+_XcursorNextPath (const char *path)
872+{
873+ char *colon = strchr (path, ':');
874+
875+ if (!colon)
876+ return NULL;
877+ return colon + 1;
878+}
879+
880+#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
881+#define XcursorSep(c) ((c) == ';' || (c) == ',')
882+
883+static char *
884+_XcursorThemeInherits (const char *full)
885+{
886+ char line[8192];
887+ char *result = NULL;
888+ FILE *f;
889+
890+ if (!full)
891+ return NULL;
892+
893+ f = fopen (full, "r");
894+ if (f)
895+ {
896+ while (fgets (line, sizeof (line), f))
897+ {
898+ if (!strncmp (line, "Inherits", 8))
899+ {
900+ char *l = line + 8;
901+ char *r;
902+ while (*l == ' ') l++;
903+ if (*l != '=') continue;
904+ l++;
905+ while (*l == ' ') l++;
906+ result = malloc (strlen (l) + 1);
907+ if (result)
908+ {
909+ r = result;
910+ while (*l)
911+ {
912+ while (XcursorSep(*l) || XcursorWhite (*l)) l++;
913+ if (!*l)
914+ break;
915+ if (r != result)
916+ *r++ = ':';
917+ while (*l && !XcursorWhite(*l) &&
918+ !XcursorSep(*l))
919+ *r++ = *l++;
920+ }
921+ *r++ = '\0';
922+ }
923+ break;
924+ }
925+ }
926+ fclose (f);
927+ }
928+ return result;
929+}
930+
931+static FILE *
932+XcursorScanTheme (const char *theme, const char *name)
933+{
934+ FILE *f = NULL;
935+ char *full;
936+ char *dir;
937+ const char *path;
938+ char *inherits = NULL;
939+ const char *i;
940+
941+ if (!theme || !name)
942+ return NULL;
943+
944+ /*
945+ * Scan this theme
946+ */
947+ for (path = XcursorLibraryPath ();
948+ path && f == NULL;
949+ path = _XcursorNextPath (path))
950+ {
951+ dir = _XcursorBuildThemeDir (path, theme);
952+ if (dir)
953+ {
954+ full = _XcursorBuildFullname (dir, "cursors", name);
955+ if (full)
956+ {
957+ f = fopen (full, "r");
958+ free (full);
959+ }
960+ if (!f && !inherits)
961+ {
962+ full = _XcursorBuildFullname (dir, "", "index.theme");
963+ if (full)
964+ {
965+ inherits = _XcursorThemeInherits (full);
966+ free (full);
967+ }
968+ }
969+ free (dir);
970+ }
971+ }
972+ /*
973+ * Recurse to scan inherited themes
974+ */
975+ for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
976+ f = XcursorScanTheme (i, name);
977+ if (inherits != NULL)
978+ free (inherits);
979+ return f;
980+}
981+
982+XcursorImages *
983+XcursorLibraryLoadImages (const char *file, const char *theme, int size)
984+{
985+ FILE *f = NULL;
986+ XcursorImages *images = NULL;
987+
988+ if (!file)
989+ return NULL;
990+
991+ if (theme)
992+ f = XcursorScanTheme (theme, file);
993+ if (!f)
994+ f = XcursorScanTheme ("default", file);
995+ if (f)
996+ {
997+ images = XcursorFileLoadImages (f, size);
998+ if (images)
999+ XcursorImagesSetName (images, file);
1000+ fclose (f);
1001+ }
1002+ return images;
1003+}
1004+
1005+static void
1006+load_all_cursors_from_dir(const char *path, int size,
1007+ void (*load_callback)(XcursorImages *, void *),
1008+ void *user_data)
1009+{
1010+ FILE *f;
1011+ DIR *dir = opendir(path);
1012+ struct dirent *ent;
1013+ char *full;
1014+ XcursorImages *images;
1015+
1016+ if (!dir)
1017+ return;
1018+
1019+ for(ent = readdir(dir); ent; ent = readdir(dir)) {
1020+#ifdef _DIRENT_HAVE_D_TYPE
1021+ if (ent->d_type != DT_UNKNOWN &&
1022+ (ent->d_type != DT_REG && ent->d_type != DT_LNK))
1023+ continue;
1024+#endif
1025+
1026+ full = _XcursorBuildFullname(path, "", ent->d_name);
1027+ if (!full)
1028+ continue;
1029+
1030+ f = fopen(full, "r");
1031+ if (!f) {
1032+ free(full);
1033+ continue;
1034+ }
1035+
1036+ images = XcursorFileLoadImages(f, size);
1037+
1038+ if (images) {
1039+ XcursorImagesSetName(images, ent->d_name);
1040+ load_callback(images, user_data);
1041+ }
1042+
1043+ fclose (f);
1044+ free(full);
1045+ }
1046+
1047+ closedir(dir);
1048+}
1049+
1050+/** Load all the cursor of a theme
1051+ *
1052+ * This function loads all the cursor images of a given theme and its
1053+ * inherited themes. Each cursor is loaded into an XcursorImages object
1054+ * which is passed to the caller's load callback. If a cursor appears
1055+ * more than once across all the inherited themes, the load callback
1056+ * will be called multiple times, with possibly different XcursorImages
1057+ * object which have the same name. The user is expected to destroy the
1058+ * XcursorImages objects passed to the callback with
1059+ * XcursorImagesDestroy().
1060+ *
1061+ * \param theme The name of theme that should be loaded
1062+ * \param size The desired size of the cursor images
1063+ * \param load_callback A callback function that will be called
1064+ * for each cursor loaded. The first parameter is the XcursorImages
1065+ * object representing the loaded cursor and the second is a pointer
1066+ * to data provided by the user.
1067+ * \param user_data The data that should be passed to the load callback
1068+ */
1069+void
1070+xcursor_load_theme(const char *theme, int size,
1071+ void (*load_callback)(XcursorImages *, void *),
1072+ void *user_data)
1073+{
1074+ char *full, *dir;
1075+ char *inherits = NULL;
1076+ const char *path, *i;
1077+
1078+ if (!theme)
1079+ theme = "default";
1080+
1081+ for (path = XcursorLibraryPath();
1082+ path;
1083+ path = _XcursorNextPath(path)) {
1084+ dir = _XcursorBuildThemeDir(path, theme);
1085+ if (!dir)
1086+ continue;
1087+
1088+ full = _XcursorBuildFullname(dir, "cursors", "");
1089+
1090+ if (full) {
1091+ load_all_cursors_from_dir(full, size, load_callback,
1092+ user_data);
1093+ free(full);
1094+ }
1095+
1096+ if (!inherits) {
1097+ full = _XcursorBuildFullname(dir, "", "index.theme");
1098+ if (full) {
1099+ inherits = _XcursorThemeInherits(full);
1100+ free(full);
1101+ }
1102+ }
1103+
1104+ free(dir);
1105+ }
1106+
1107+ for (i = inherits; i; i = _XcursorNextPath(i))
1108+ xcursor_load_theme(i, size, load_callback, user_data);
1109+
1110+ if (inherits)
1111+ free(inherits);
1112+}
1113
1114=== added file 'plugins/Cursor/3rd_party/xcursor/xcursor.h'
1115--- plugins/Cursor/3rd_party/xcursor/xcursor.h 1970-01-01 00:00:00 +0000
1116+++ plugins/Cursor/3rd_party/xcursor/xcursor.h 2015-10-14 19:15:46 +0000
1117@@ -0,0 +1,65 @@
1118+/*
1119+ * Copyright © 2002 Keith Packard
1120+ *
1121+ * Permission to use, copy, modify, distribute, and sell this software and its
1122+ * documentation for any purpose is hereby granted without fee, provided that
1123+ * the above copyright notice appear in all copies and that both that
1124+ * copyright notice and this permission notice appear in supporting
1125+ * documentation, and that the name of Keith Packard not be used in
1126+ * advertising or publicity pertaining to distribution of the software without
1127+ * specific, written prior permission. Keith Packard makes no
1128+ * representations about the suitability of this software for any purpose. It
1129+ * is provided "as is" without express or implied warranty.
1130+ *
1131+ * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
1132+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
1133+ * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
1134+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
1135+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
1136+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1137+ * PERFORMANCE OF THIS SOFTWARE.
1138+ */
1139+
1140+#ifndef XCURSOR_H
1141+#define XCURSOR_H
1142+
1143+#include <stdint.h>
1144+
1145+
1146+typedef int XcursorBool;
1147+typedef uint32_t XcursorUInt;
1148+
1149+typedef XcursorUInt XcursorDim;
1150+typedef XcursorUInt XcursorPixel;
1151+
1152+typedef struct _XcursorImage {
1153+ XcursorUInt version; /* version of the image data */
1154+ XcursorDim size; /* nominal size for matching */
1155+ XcursorDim width; /* actual width */
1156+ XcursorDim height; /* actual height */
1157+ XcursorDim xhot; /* hot spot x (must be inside image) */
1158+ XcursorDim yhot; /* hot spot y (must be inside image) */
1159+ XcursorUInt delay; /* animation delay to next frame (ms) */
1160+ XcursorPixel *pixels; /* pointer to pixels */
1161+} XcursorImage;
1162+
1163+/*
1164+ * Other data structures exposed by the library API
1165+ */
1166+typedef struct _XcursorImages {
1167+ int nimage; /* number of images */
1168+ XcursorImage **images; /* array of XcursorImage pointers */
1169+ char *name; /* name used to load images */
1170+} XcursorImages;
1171+
1172+XcursorImages *
1173+XcursorLibraryLoadImages (const char *file, const char *theme, int size);
1174+
1175+void
1176+XcursorImagesDestroy (XcursorImages *images);
1177+
1178+void
1179+xcursor_load_theme(const char *theme, int size,
1180+ void (*load_callback)(XcursorImages *, void *),
1181+ void *user_data);
1182+#endif
1183
1184=== added file 'plugins/Cursor/CMakeLists.txt'
1185--- plugins/Cursor/CMakeLists.txt 1970-01-01 00:00:00 +0000
1186+++ plugins/Cursor/CMakeLists.txt 2015-10-14 19:15:46 +0000
1187@@ -0,0 +1,28 @@
1188+add_subdirectory(3rd_party)
1189+
1190+include_directories(
1191+ ${CMAKE_CURRENT_SOURCE_DIR}
1192+ ${CMAKE_CURRENT_SOURCE_DIR}/3rd_party/xcursor
1193+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
1194+)
1195+
1196+set(QMLPLUGIN_SRC
1197+ plugin.cpp
1198+ MousePointer.cpp
1199+ CursorImageProvider.cpp
1200+ # We need to run moc on this header
1201+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirMousePointerInterface.h
1202+ )
1203+
1204+add_library(Cursor-qml SHARED
1205+ ${QMLPLUGIN_SRC}
1206+ )
1207+
1208+target_link_libraries(Cursor-qml
1209+ xcursorloader-static
1210+ ${QT5PLATFORM_SUPPORT_LDFLAGS}
1211+)
1212+
1213+qt5_use_modules(Cursor-qml Qml Quick DBus Network Gui Sql Concurrent Svg)
1214+
1215+add_unity8_plugin(Cursor 1.0 Cursor TARGETS Cursor-qml)
1216
1217=== added file 'plugins/Cursor/Cursor.qml'
1218--- plugins/Cursor/Cursor.qml 1970-01-01 00:00:00 +0000
1219+++ plugins/Cursor/Cursor.qml 2015-10-14 19:15:46 +0000
1220@@ -0,0 +1,12 @@
1221+import QtQuick 2.4
1222+import Cursor 1.0 // For MousePointer
1223+
1224+MousePointer {
1225+ id: mousePointer
1226+
1227+ Image {
1228+ x: -mousePointer.hotspotX
1229+ y: -mousePointer.hotspotY
1230+ source: "image://cursor/" + mousePointer.themeName + "/" + mousePointer.cursorName
1231+ }
1232+}
1233
1234=== added file 'plugins/Cursor/CursorImageProvider.cpp'
1235--- plugins/Cursor/CursorImageProvider.cpp 1970-01-01 00:00:00 +0000
1236+++ plugins/Cursor/CursorImageProvider.cpp 2015-10-14 19:15:46 +0000
1237@@ -0,0 +1,191 @@
1238+/*
1239+ * Copyright (C) 2015 Canonical, Ltd.
1240+ *
1241+ * This program is free software: you can redistribute it and/or modify it under
1242+ * the terms of the GNU Lesser General Public License version 3, as published by
1243+ * the Free Software Foundation.
1244+ *
1245+ * This program is distributed in the hope that it will be useful, but WITHOUT
1246+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1247+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1248+ * Lesser General Public License for more details.
1249+ *
1250+ * You should have received a copy of the GNU Lesser General Public License
1251+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1252+ */
1253+
1254+#include "CursorImageProvider.h"
1255+
1256+#include <QDebug>
1257+#include <QFile>
1258+#include <QPainter>
1259+#include <QSvgRenderer>
1260+
1261+CursorImageProvider *CursorImageProvider::m_instance = nullptr;
1262+
1263+/////
1264+// BuiltInCursorImage
1265+
1266+BuiltInCursorImage::BuiltInCursorImage()
1267+{
1268+ const char *svgString =
1269+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
1270+ "<svg"
1271+ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
1272+ " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
1273+ " xmlns:svg=\"http://www.w3.org/2000/svg\""
1274+ " xmlns=\"http://www.w3.org/2000/svg\""
1275+ " version=\"1.1\">"
1276+ " <path"
1277+ " style=\"fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:40;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\""
1278+ " d=\"M 20.504,50.94931 460.42533,518.14486 266.47603,515.61948 366.48114,719.16522 274.05218,770.68296 172.53185,559.56112 20.504,716.13476 Z\" />"
1279+ "</svg>";
1280+
1281+ qimage = QImage(20, 32, QImage::Format_ARGB32);
1282+ QPainter imagePainter(&qimage);
1283+
1284+ QSvgRenderer *svgRenderer = new QSvgRenderer(QByteArray(svgString));
1285+ svgRenderer->render(&imagePainter);
1286+ delete svgRenderer;
1287+}
1288+
1289+/////
1290+// XCursorImage
1291+
1292+XCursorImage::XCursorImage(const QString &theme, const QString &file)
1293+ : xcursorImages(nullptr)
1294+{
1295+ xcursorImages = XcursorLibraryLoadImages(QFile::encodeName(file), QFile::encodeName(theme), 32);
1296+ if (!xcursorImages) {
1297+ return;
1298+ }
1299+
1300+ bool loaded = false;
1301+ for (int i = 0; i < xcursorImages->nimage && !loaded; ++i) {
1302+ XcursorImage *xcursorImage = xcursorImages->images[i];
1303+ if (xcursorImage->size == 32) {
1304+
1305+ qimage = QImage((uchar*)xcursorImage->pixels,
1306+ xcursorImage->width, xcursorImage->height, QImage::Format_ARGB32);
1307+
1308+ hotspot.setX(xcursorImage->xhot);
1309+ hotspot.setY(xcursorImage->yhot);
1310+
1311+ loaded = true;
1312+ }
1313+ }
1314+}
1315+
1316+XCursorImage::~XCursorImage()
1317+{
1318+ XcursorImagesDestroy(xcursorImages);
1319+}
1320+
1321+/////
1322+// CursorImageProvider
1323+
1324+CursorImageProvider::CursorImageProvider()
1325+ : QQuickImageProvider(QQuickImageProvider::Image)
1326+{
1327+ if (m_instance) {
1328+ qFatal("Cannot have multiple CursorImageProvider instances");
1329+ }
1330+ m_instance = this;
1331+}
1332+
1333+CursorImageProvider::~CursorImageProvider()
1334+{
1335+ {
1336+ QList< QMap<QString, CursorImage*> > cursorList = m_cursors.values();
1337+
1338+ for (int i = 0; i < cursorList.count(); ++i) {
1339+ QList<CursorImage*> cursorImageList = cursorList[i].values();
1340+ for (int j = 0; j < cursorImageList.count(); ++j) {
1341+ delete cursorImageList[j];
1342+ }
1343+ }
1344+ }
1345+
1346+ m_cursors.clear();
1347+ m_instance = nullptr;
1348+}
1349+
1350+QImage CursorImageProvider::requestImage(const QString &cursorThemeAndName, QSize *size, const QSize & /*requestedSize*/)
1351+{
1352+ CursorImage *cursorImage = fetchCursor(cursorThemeAndName);
1353+ size->setWidth(cursorImage->qimage.width());
1354+ size->setHeight(cursorImage->qimage.height());
1355+
1356+ return cursorImage->qimage;
1357+}
1358+
1359+QPoint CursorImageProvider::hotspot(const QString &themeName, const QString &cursorName)
1360+{
1361+ CursorImage *cursorImage = fetchCursor(themeName, cursorName);
1362+ if (cursorImage) {
1363+ return cursorImage->hotspot;
1364+ } else {
1365+ return QPoint(0,0);
1366+ }
1367+}
1368+
1369+CursorImage *CursorImageProvider::fetchCursor(const QString &cursorThemeAndName)
1370+{
1371+ QString themeName;
1372+ QString cursorName;
1373+ {
1374+ QStringList themeAndNameList = cursorThemeAndName.split("/");
1375+ if (themeAndNameList.size() != 2) {
1376+ return nullptr;
1377+ }
1378+ themeName = themeAndNameList[0];
1379+ cursorName = themeAndNameList[1];
1380+ }
1381+
1382+ return fetchCursor(themeName, cursorName);
1383+}
1384+
1385+CursorImage *CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName)
1386+{
1387+ CursorImage *cursorImage = fetchCursorHelper(themeName, cursorName);
1388+
1389+ // Try some fallbacks
1390+ if (cursorImage->qimage.isNull()) {
1391+ if (cursorName == "ibeam") {
1392+ qDebug() << "CursorImageProvider: \"ibeam\" not found, falling back to \"xterm\"";
1393+ cursorImage = fetchCursorHelper(themeName, "xterm");
1394+ } else if (cursorName == "xterm") {
1395+ qDebug() << "CursorImageProvider: \"xterm\" not found, falling back to \"ibeam\"";
1396+ cursorImage = fetchCursorHelper(themeName, "ibeam");
1397+ }
1398+ }
1399+
1400+ // if it all fails, there must be at least a left_ptr
1401+ if (cursorImage->qimage.isNull() && cursorName != "left_ptr") {
1402+ qDebug() << "CursorImageProvider:" << cursorName
1403+ << "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
1404+ cursorImage = fetchCursorHelper(themeName, "left_ptr");
1405+ }
1406+
1407+ if (cursorImage->qimage.isNull()) {
1408+ // finally, go for the built-in cursor
1409+ qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
1410+ if (!m_builtInCursorImage) {
1411+ m_builtInCursorImage.reset(new BuiltInCursorImage);
1412+ }
1413+ cursorImage = m_builtInCursorImage.data();
1414+ }
1415+
1416+ return cursorImage;
1417+}
1418+
1419+CursorImage *CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName)
1420+{
1421+ QMap<QString, CursorImage*> &themeCursors = m_cursors[themeName];
1422+
1423+ if (!themeCursors.contains(cursorName)) {
1424+ themeCursors[cursorName] = new XCursorImage(themeName, cursorName);
1425+ }
1426+
1427+ return themeCursors[cursorName];
1428+}
1429
1430=== added file 'plugins/Cursor/CursorImageProvider.h'
1431--- plugins/Cursor/CursorImageProvider.h 1970-01-01 00:00:00 +0000
1432+++ plugins/Cursor/CursorImageProvider.h 2015-10-14 19:15:46 +0000
1433@@ -0,0 +1,76 @@
1434+/*
1435+ * Copyright (C) 2015 Canonical, Ltd.
1436+ *
1437+ * This program is free software: you can redistribute it and/or modify it under
1438+ * the terms of the GNU Lesser General Public License version 3, as published by
1439+ * the Free Software Foundation.
1440+ *
1441+ * This program is distributed in the hope that it will be useful, but WITHOUT
1442+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1443+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1444+ * Lesser General Public License for more details.
1445+ *
1446+ * You should have received a copy of the GNU Lesser General Public License
1447+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1448+ */
1449+
1450+#ifndef CURSORIMAGEPROVIDER_H
1451+#define CURSORIMAGEPROVIDER_H
1452+
1453+#include <QQuickImageProvider>
1454+#include <QScopedPointer>
1455+
1456+// xcursor static lib
1457+extern "C"
1458+{
1459+#include <xcursor.h>
1460+}
1461+
1462+class CursorImage {
1463+public:
1464+ virtual ~CursorImage() {}
1465+
1466+ QImage qimage;
1467+ QPoint hotspot;
1468+};
1469+
1470+class XCursorImage : public CursorImage {
1471+public:
1472+ XCursorImage(const QString &theme, const QString &file);
1473+ virtual ~XCursorImage();
1474+
1475+ XcursorImages *xcursorImages;
1476+};
1477+
1478+class BuiltInCursorImage : public CursorImage {
1479+public:
1480+ BuiltInCursorImage();
1481+};
1482+
1483+class CursorImageProvider : public QQuickImageProvider
1484+{
1485+public:
1486+ CursorImageProvider();
1487+ virtual ~CursorImageProvider();
1488+
1489+ static CursorImageProvider *instance() { return m_instance; }
1490+
1491+
1492+ QImage requestImage(const QString &cursorName, QSize *size, const QSize &requestedSize) override;
1493+
1494+ QPoint hotspot(const QString &themeName, const QString &cursorName);
1495+
1496+private:
1497+ CursorImage *fetchCursor(const QString &cursorThemeAndName);
1498+ CursorImage *fetchCursor(const QString &themeName, const QString &cursorName);
1499+ CursorImage *fetchCursorHelper(const QString &themeName, const QString &cursorName);
1500+
1501+ // themeName -> (cursorName -> cursorImage)
1502+ QMap<QString, QMap<QString, CursorImage*> > m_cursors;
1503+
1504+ QScopedPointer<CursorImage> m_builtInCursorImage;
1505+
1506+ static CursorImageProvider *m_instance;
1507+};
1508+
1509+#endif // CURSORIMAGEPROVIDER_H
1510
1511=== added file 'plugins/Cursor/MousePointer.cpp'
1512--- plugins/Cursor/MousePointer.cpp 1970-01-01 00:00:00 +0000
1513+++ plugins/Cursor/MousePointer.cpp 2015-10-14 19:15:46 +0000
1514@@ -0,0 +1,125 @@
1515+/*
1516+ * Copyright (C) 2015 Canonical, Ltd.
1517+ *
1518+ * This program is free software: you can redistribute it and/or modify it under
1519+ * the terms of the GNU Lesser General Public License version 3, as published by
1520+ * the Free Software Foundation.
1521+ *
1522+ * This program is distributed in the hope that it will be useful, but WITHOUT
1523+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1524+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1525+ * Lesser General Public License for more details.
1526+ *
1527+ * You should have received a copy of the GNU Lesser General Public License
1528+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1529+ */
1530+
1531+#include "MousePointer.h"
1532+#include "CursorImageProvider.h"
1533+
1534+// Unity API
1535+#include <unity/shell/application/MirPlatformCursor.h>
1536+
1537+#include <QQuickWindow>
1538+#include <QGuiApplication>
1539+
1540+#include <qpa/qwindowsysteminterface.h>
1541+
1542+MousePointer::MousePointer(QQuickItem *parent)
1543+ : MirMousePointerInterface(parent)
1544+ , m_cursorName("left_ptr")
1545+ , m_themeName("default")
1546+ , m_hotspotX(0)
1547+ , m_hotspotY(0)
1548+{
1549+}
1550+
1551+void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
1552+ Qt::KeyboardModifiers modifiers)
1553+{
1554+ if (!parentItem()) {
1555+ return;
1556+ }
1557+
1558+ qreal newX = x() + movement.x();
1559+ if (newX < 0) {
1560+ newX = 0;
1561+ } else if (newX > parentItem()->width()) {
1562+ newX = parentItem()->width();
1563+ }
1564+ setX(newX);
1565+
1566+ qreal newY = y() + movement.y();
1567+ if (newY < 0) {
1568+ newY = 0;
1569+ } else if (newY > parentItem()->height()) {
1570+ newY = parentItem()->height();
1571+ }
1572+ setY(newY);
1573+
1574+ QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
1575+ QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
1576+ buttons, modifiers);
1577+}
1578+
1579+void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
1580+{
1581+ if (change == ItemSceneChange) {
1582+ registerWindow(value.window);
1583+ }
1584+}
1585+
1586+void MousePointer::registerWindow(QWindow *window)
1587+{
1588+ if (m_registeredWindow && window != m_registeredWindow) {
1589+ auto previousCursor = dynamic_cast<MirPlatformCursor*>(m_registeredWindow->screen()->handle()->cursor());
1590+ if (previousCursor) {
1591+ previousCursor->setMousePointer(nullptr);
1592+ } else {
1593+ qCritical("QPlatformCursor is not a MirPlatformCursor! Cursor module only works in a Mir server.");
1594+ }
1595+ }
1596+
1597+ m_registeredWindow = window;
1598+
1599+ if (m_registeredWindow) {
1600+ auto cursor = dynamic_cast<MirPlatformCursor*>(window->screen()->handle()->cursor());
1601+ if (cursor) {
1602+ cursor->setMousePointer(this);
1603+ } else {
1604+ qCritical("QPlaformCursor is not a MirPlatformCursor! Cursor module only works in Mir.");
1605+ }
1606+ }
1607+}
1608+
1609+void MousePointer::setCursorName(const QString &cursorName)
1610+{
1611+ if (cursorName != m_cursorName) {
1612+ m_cursorName = cursorName;
1613+ Q_EMIT cursorNameChanged(m_cursorName);
1614+ updateHotspot();
1615+ }
1616+}
1617+
1618+void MousePointer::updateHotspot()
1619+{
1620+ QPoint newHotspot = CursorImageProvider::instance()->hotspot(m_themeName, m_cursorName);
1621+
1622+ if (m_hotspotX != newHotspot.x()) {
1623+ m_hotspotX = newHotspot.x();
1624+ Q_EMIT hotspotXChanged(m_hotspotX);
1625+ }
1626+
1627+ if (m_hotspotY != newHotspot.y()) {
1628+ m_hotspotY = newHotspot.y();
1629+ Q_EMIT hotspotYChanged(m_hotspotY);
1630+ }
1631+}
1632+
1633+void MousePointer::setThemeName(const QString &themeName)
1634+{
1635+ if (m_themeName != themeName) {
1636+ m_themeName = themeName;
1637+ Q_EMIT themeNameChanged(m_themeName);
1638+ }
1639+}
1640
1641=== added file 'plugins/Cursor/MousePointer.h'
1642--- plugins/Cursor/MousePointer.h 1970-01-01 00:00:00 +0000
1643+++ plugins/Cursor/MousePointer.h 2015-10-14 19:15:46 +0000
1644@@ -0,0 +1,59 @@
1645+/*
1646+ * Copyright (C) 2015 Canonical, Ltd.
1647+ *
1648+ * This program is free software: you can redistribute it and/or modify it under
1649+ * the terms of the GNU Lesser General Public License version 3, as published by
1650+ * the Free Software Foundation.
1651+ *
1652+ * This program is distributed in the hope that it will be useful, but WITHOUT
1653+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1654+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1655+ * Lesser General Public License for more details.
1656+ *
1657+ * You should have received a copy of the GNU Lesser General Public License
1658+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1659+ */
1660+
1661+#ifndef MOUSEPOINTER_H
1662+#define MOUSEPOINTER_H
1663+
1664+// Qt
1665+#include <QPointer>
1666+#include <QWindow>
1667+
1668+// Unity API
1669+#include <unity/shell/application/MirMousePointerInterface.h>
1670+
1671+class MousePointer : public MirMousePointerInterface {
1672+ Q_OBJECT
1673+public:
1674+ MousePointer(QQuickItem *parent = nullptr);
1675+
1676+ void setCursorName(const QString &qtCursorName) override;
1677+ QString cursorName() const override { return m_cursorName; }
1678+
1679+ void setThemeName(const QString &themeName) override;
1680+ QString themeName() const override { return m_themeName; }
1681+
1682+ qreal hotspotX() const override { return m_hotspotX; }
1683+ qreal hotspotY() const override { return m_hotspotY; }
1684+
1685+public Q_SLOTS:
1686+ void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
1687+ Qt::KeyboardModifiers modifiers) override;
1688+
1689+protected:
1690+ void itemChange(ItemChange change, const ItemChangeData &value) override;
1691+
1692+private:
1693+ void registerWindow(QWindow *window);
1694+ void updateHotspot();
1695+
1696+ QPointer<QWindow> m_registeredWindow;
1697+ QString m_cursorName;
1698+ QString m_themeName;
1699+ int m_hotspotX;
1700+ int m_hotspotY;
1701+};
1702+
1703+#endif // MOUSEPOINTER_H
1704
1705=== added file 'plugins/Cursor/plugin.cpp'
1706--- plugins/Cursor/plugin.cpp 1970-01-01 00:00:00 +0000
1707+++ plugins/Cursor/plugin.cpp 2015-10-14 19:15:46 +0000
1708@@ -0,0 +1,39 @@
1709+/*
1710+ * Copyright (C) 2015 Canonical, Ltd.
1711+ *
1712+ * This program is free software; you can redistribute it and/or modify
1713+ * it under the terms of the GNU General Public License as published by
1714+ * the Free Software Foundation; version 3.
1715+ *
1716+ * This program is distributed in the hope that it will be useful,
1717+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1718+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1719+ * GNU General Public License for more details.
1720+ *
1721+ * You should have received a copy of the GNU General Public License
1722+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1723+ */
1724+
1725+// Qt
1726+#include <QtQml/qqml.h>
1727+#include <QQmlContext>
1728+
1729+// self
1730+#include "plugin.h"
1731+
1732+// local
1733+#include "CursorImageProvider.h"
1734+#include "MousePointer.h"
1735+
1736+void CursorPlugin::registerTypes(const char *uri)
1737+{
1738+ Q_ASSERT(uri == QLatin1String("Cursor"));
1739+ qmlRegisterType<MousePointer>(uri, 1, 0, "MousePointer");
1740+}
1741+
1742+void CursorPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
1743+{
1744+ QQmlExtensionPlugin::initializeEngine(engine, uri);
1745+
1746+ engine->addImageProvider(QLatin1String("cursor"), new CursorImageProvider());
1747+}
1748
1749=== added file 'plugins/Cursor/plugin.h'
1750--- plugins/Cursor/plugin.h 1970-01-01 00:00:00 +0000
1751+++ plugins/Cursor/plugin.h 2015-10-14 19:15:46 +0000
1752@@ -0,0 +1,33 @@
1753+/*
1754+ * Copyright (C) 2015 Canonical, Ltd.
1755+ *
1756+ * This program is free software; you can redistribute it and/or modify
1757+ * it under the terms of the GNU General Public License as published by
1758+ * the Free Software Foundation; version 3.
1759+ *
1760+ * This program is distributed in the hope that it will be useful,
1761+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1762+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1763+ * GNU General Public License for more details.
1764+ *
1765+ * You should have received a copy of the GNU General Public License
1766+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1767+ */
1768+
1769+#ifndef CURSOR_PLUGIN_H
1770+#define CURSOR_PLUGIN_H
1771+
1772+#include <QtQml/QQmlEngine>
1773+#include <QtQml/QQmlExtensionPlugin>
1774+
1775+class CursorPlugin : public QQmlExtensionPlugin
1776+{
1777+ Q_OBJECT
1778+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
1779+
1780+public:
1781+ void registerTypes(const char *uri) override;
1782+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
1783+};
1784+
1785+#endif // CURSOR_PLUGIN_H
1786
1787=== added file 'plugins/Cursor/qmldir'
1788--- plugins/Cursor/qmldir 1970-01-01 00:00:00 +0000
1789+++ plugins/Cursor/qmldir 2015-10-14 19:15:46 +0000
1790@@ -0,0 +1,3 @@
1791+module Cursor
1792+plugin Cursor-qml
1793+Cursor 1.0 Cursor.qml
1794
1795=== modified file 'qml/Greeter/Greeter.qml'
1796--- qml/Greeter/Greeter.qml 2015-09-02 13:06:56 +0000
1797+++ qml/Greeter/Greeter.qml 2015-10-14 19:15:46 +0000
1798@@ -251,7 +251,7 @@
1799
1800 // event eater
1801 // Nothing should leak to items behind the greeter
1802- MouseArea { anchors.fill: parent }
1803+ MouseArea { anchors.fill: parent; hoverEnabled: true }
1804
1805 Loader {
1806 id: loader
1807
1808=== modified file 'qml/Launcher/Launcher.qml'
1809--- qml/Launcher/Launcher.qml 2015-09-02 14:18:40 +0000
1810+++ qml/Launcher/Launcher.qml 2015-10-14 19:15:46 +0000
1811@@ -188,6 +188,7 @@
1812 bottom: parent.bottom
1813 }
1814 enabled: root.shadeBackground && root.state == "visible"
1815+ visible: enabled // otherwise it will get in the way of cursor selection for some reason
1816 onPressed: {
1817 root.state = ""
1818 }
1819
1820=== modified file 'qml/Panel/Panel.qml'
1821--- qml/Panel/Panel.qml 2015-06-29 03:58:22 +0000
1822+++ qml/Panel/Panel.qml 2015-10-14 19:15:46 +0000
1823@@ -48,6 +48,7 @@
1824 MouseArea {
1825 anchors.fill: parent
1826 onClicked: if (indicators.fullyOpened) indicators.hide();
1827+ hoverEnabled: true // should also eat hover events, otherwise they will pass through
1828 }
1829 }
1830
1831
1832=== modified file 'qml/Shell.qml'
1833--- qml/Shell.qml 2015-09-22 14:23:44 +0000
1834+++ qml/Shell.qml 2015-10-14 19:15:46 +0000
1835@@ -41,6 +41,7 @@
1836 import Unity.Session 0.1
1837 import Unity.DashCommunicator 0.1
1838 import Unity.Indicators 0.1 as Indicators
1839+import Cursor 1.0
1840
1841
1842 Item {
1843@@ -63,7 +64,7 @@
1844 function updateFocusedAppOrientationAnimated() {
1845 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
1846 }
1847- property bool hasMouse
1848+ property bool hasMouse: false
1849
1850 // to be read from outside
1851 readonly property int mainAppWindowOrientationAngle:
1852@@ -190,11 +191,6 @@
1853 onScreenshotTriggered: screenGrabber.capture();
1854 }
1855
1856- ScreenGrabber {
1857- id: screenGrabber
1858- z: dialogs.z + 10
1859- }
1860-
1861 GlobalShortcut {
1862 // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
1863 }
1864@@ -684,9 +680,20 @@
1865 onShowHome: showHome()
1866 }
1867
1868+ ScreenGrabber {
1869+ id: screenGrabber
1870+ z: dialogs.z + 10
1871+ }
1872+
1873+ Cursor {
1874+ id: cursor
1875+ visible: shell.hasMouse
1876+ z: screenGrabber.z + 1
1877+ }
1878+
1879 Rectangle {
1880 id: shutdownFadeOutRectangle
1881- z: screenGrabber.z + 10
1882+ z: cursor.z + 1
1883 enabled: false
1884 visible: false
1885 color: "black"
1886
1887=== modified file 'qml/Stages/ApplicationWindow.qml'
1888--- qml/Stages/ApplicationWindow.qml 2015-09-17 12:25:29 +0000
1889+++ qml/Stages/ApplicationWindow.qml 2015-10-14 19:15:46 +0000
1890@@ -32,6 +32,8 @@
1891 property QtObject application
1892 property int surfaceOrientationAngle
1893 property alias resizeSurface: sessionContainer.resizeSurface
1894+ property int requestedWidth: -1
1895+ property int requestedHeight: -1
1896
1897 QtObject {
1898 id: d
1899@@ -133,7 +135,9 @@
1900 id: sessionContainer
1901 // A fake application might not even have a session property.
1902 session: application && application.session ? application.session : null
1903- anchors.fill: parent
1904+
1905+ requestedWidth: root.requestedWidth
1906+ requestedHeight: root.requestedHeight
1907
1908 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
1909
1910@@ -148,6 +152,28 @@
1911 focus: true
1912 }
1913
1914+ // SessionContainer size drives ApplicationWindow size
1915+ Binding {
1916+ target: root; property: "width"
1917+ value: stateGroup.state === "surface" ? sessionContainer.width : root.requestedWidth
1918+ when: root.requestedWidth >= 0
1919+ }
1920+ Binding {
1921+ target: root; property: "height"
1922+ value: stateGroup.state === "surface" ? sessionContainer.height : root.requestedHeight
1923+ when: root.requestedHeight >= 0
1924+ }
1925+
1926+ // ApplicationWindow size drives SessionContainer size
1927+ Binding {
1928+ target: sessionContainer; property: "width"; value: root.width
1929+ when: root.requestedWidth < 0
1930+ }
1931+ Binding {
1932+ target: sessionContainer; property: "height"; value: root.height
1933+ when: root.requestedHeight < 0
1934+ }
1935+
1936 StateGroup {
1937 id: stateGroup
1938 objectName: "applicationWindowStateGroup"
1939
1940=== modified file 'qml/Stages/DecoratedWindow.qml'
1941--- qml/Stages/DecoratedWindow.qml 2015-09-08 10:32:28 +0000
1942+++ qml/Stages/DecoratedWindow.qml 2015-10-14 19:15:46 +0000
1943@@ -23,6 +23,9 @@
1944 FocusScope {
1945 id: root
1946
1947+ width: applicationWindow.width
1948+ height: (root.decorationShown ? decoration.height : 0) + applicationWindow.height
1949+
1950 property alias window: applicationWindow
1951 property alias application: applicationWindow.application
1952 property alias active: decoration.active
1953@@ -31,9 +34,13 @@
1954 property bool highlightShown: false
1955 property real shadowOpacity: 1
1956
1957- signal close();
1958- signal maximize();
1959- signal minimize();
1960+ property alias requestedWidth: applicationWindow.requestedWidth
1961+ property real requestedHeight
1962+
1963+ signal close()
1964+ signal maximize()
1965+ signal minimize()
1966+ signal decorationPressed()
1967
1968 BorderImage {
1969 anchors {
1970@@ -61,6 +68,7 @@
1971
1972 WindowDecoration {
1973 id: decoration
1974+ target: root.parent
1975 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
1976 anchors { left: parent.left; top: parent.top; right: parent.right }
1977 height: units.gu(3)
1978@@ -68,6 +76,7 @@
1979 onClose: root.close();
1980 onMaximize: root.maximize();
1981 onMinimize: root.minimize();
1982+ onPressed: root.decorationPressed();
1983 visible: decorationShown
1984 }
1985
1986@@ -77,8 +86,7 @@
1987 anchors.top: parent.top
1988 anchors.topMargin: decoration.height
1989 anchors.left: parent.left
1990- width: root.width
1991- height: root.height - decoration.height
1992+ requestedHeight: root.requestedHeight - (root.decorationShown ? decoration.height : 0)
1993 interactive: true
1994 focus: true
1995 }
1996
1997=== modified file 'qml/Stages/DesktopStage.qml'
1998--- qml/Stages/DesktopStage.qml 2015-09-18 15:28:07 +0000
1999+++ qml/Stages/DesktopStage.qml 2015-10-14 19:15:46 +0000
2000@@ -134,13 +134,12 @@
2001 id: appDelegate
2002 z: ApplicationManager.count - index
2003 y: units.gu(3)
2004- width: units.gu(60)
2005- height: units.gu(50)
2006+ width: decoratedWindow.width
2007+ height: decoratedWindow.height
2008+ property alias requestedWidth: decoratedWindow.requestedWidth
2009+ property alias requestedHeight: decoratedWindow.requestedHeight
2010 focus: model.appId === priv.focusedAppId
2011
2012- readonly property int minWidth: units.gu(10)
2013- readonly property int minHeight: units.gu(10)
2014-
2015 property bool maximized: false
2016 property bool minimized: false
2017
2018@@ -180,7 +179,7 @@
2019 },
2020 State {
2021 name: "maximized"; when: appDelegate.maximized
2022- PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
2023+ PropertyChanges { target: appDelegate; x: 0; y: 0; requestedWidth: root.width; requestedHeight: root.height }
2024 },
2025 State {
2026 name: "minimized"; when: appDelegate.minimized
2027@@ -191,7 +190,7 @@
2028 Transition {
2029 from: "maximized,minimized,normal,"
2030 to: "maximized,minimized,normal,"
2031- PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
2032+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale" }
2033 },
2034 Transition {
2035 from: ""
2036@@ -215,12 +214,11 @@
2037 when: index == spread.highlightedIndex && blurLayer.ready
2038 }
2039
2040- WindowMoveResizeArea {
2041- id: windowMoveResizeArea
2042+ WindowResizeArea {
2043 target: appDelegate
2044- minWidth: appDelegate.minWidth
2045- minHeight: appDelegate.minHeight
2046- resizeHandleWidth: units.gu(2)
2047+ minWidth: units.gu(10)
2048+ minHeight: units.gu(10)
2049+ borderThickness: units.gu(2)
2050 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
2051
2052 onPressed: { ApplicationManager.focusApplication(model.appId) }
2053@@ -231,8 +229,6 @@
2054 objectName: "decoratedWindow"
2055 anchors.left: appDelegate.left
2056 anchors.top: appDelegate.top
2057- width: appDelegate.width
2058- height: appDelegate.height
2059 application: ApplicationManager.get(index)
2060 active: ApplicationManager.focusedApplicationId === model.appId
2061 focus: true
2062@@ -272,5 +268,5 @@
2063 anchors.fill: parent
2064 workspace: appContainer
2065 focus: state == "altTab"
2066- }
2067+ }
2068 }
2069
2070=== modified file 'qml/Stages/SessionContainer.qml'
2071--- qml/Stages/SessionContainer.qml 2015-09-09 13:44:12 +0000
2072+++ qml/Stages/SessionContainer.qml 2015-10-14 19:15:46 +0000
2073@@ -29,13 +29,37 @@
2074 property alias surfaceOrientationAngle: _surfaceContainer.surfaceOrientationAngle
2075 property alias resizeSurface: _surfaceContainer.resizeSurface
2076
2077+ property int requestedWidth: -1
2078+ property int requestedHeight: -1
2079+
2080 readonly property alias surfaceContainer: _surfaceContainer
2081 SurfaceContainer {
2082 id: _surfaceContainer
2083- anchors.fill: parent
2084+ requestedWidth: root.requestedWidth
2085+ requestedHeight: root.requestedHeight
2086 surface: session ? session.surface : null
2087 }
2088
2089+ // SurfaceContainer size drives SessionContainer size
2090+ Binding {
2091+ target: root; property: "width"; value: _surfaceContainer.width
2092+ when: root.requestedWidth >= 0
2093+ }
2094+ Binding {
2095+ target: root; property: "height"; value: _surfaceContainer.height
2096+ when: root.requestedHeight >= 0
2097+ }
2098+
2099+ // SessionContainer size drives SurfaceContainer size
2100+ Binding {
2101+ target: _surfaceContainer; property: "width"; value: root.width
2102+ when: root.requestedWidth < 0
2103+ }
2104+ Binding {
2105+ target: _surfaceContainer; property: "height"; value: root.height
2106+ when: root.requestedHeight < 0
2107+ }
2108+
2109 Repeater {
2110 id: childSessionsRepeater
2111 model: root.childSessions
2112
2113=== modified file 'qml/Stages/SurfaceContainer.qml'
2114--- qml/Stages/SurfaceContainer.qml 2015-09-09 13:44:12 +0000
2115+++ qml/Stages/SurfaceContainer.qml 2015-10-14 19:15:46 +0000
2116@@ -30,6 +30,9 @@
2117 property int surfaceOrientationAngle: 0
2118 property bool resizeSurface: true
2119
2120+ property int requestedWidth: -1
2121+ property int requestedHeight: -1
2122+
2123 onSurfaceChanged: {
2124 if (surface) {
2125 surfaceItem.surface = surface;
2126@@ -53,16 +56,66 @@
2127
2128 consumesInput: true
2129
2130- surfaceWidth: root.resizeSurface ? width : -1
2131- surfaceHeight: root.resizeSurface ? height : -1
2132-
2133- anchors.fill: root
2134+ surfaceWidth: {
2135+ if (root.resizeSurface) {
2136+ if (root.requestedWidth >= 0) {
2137+ return root.requestedWidth;
2138+ } else {
2139+ return width;
2140+ }
2141+ } else {
2142+ return -1;
2143+ }
2144+ }
2145+
2146+ surfaceHeight: {
2147+ if (root.resizeSurface) {
2148+ if (root.requestedHeight >= 0) {
2149+ return root.requestedHeight;
2150+ } else {
2151+ return height;
2152+ }
2153+ } else {
2154+ return -1;
2155+ }
2156+ }
2157+
2158+ fillMode: root.resizeSurface ? MirSurfaceItem.PadOrCrop : MirSurfaceItem.Stretch
2159 enabled: root.interactive
2160 focus: true
2161 antialiasing: !root.interactive
2162 orientationAngle: root.surfaceOrientationAngle
2163 }
2164
2165+ // MirSurface size drives SurfaceContainer size
2166+ Binding {
2167+ target: surfaceItem; property: "width"; value: root.surface ? root.surface.size.width : 0
2168+ when: root.requestedWidth >= 0 && root.surface
2169+ }
2170+ Binding {
2171+ target: surfaceItem; property: "height"; value: root.surface ? root.surface.size.height : 0
2172+ when: root.requestedHeight >= 0 && root.surface
2173+ }
2174+ Binding {
2175+ target: root; property: "width"; value: surfaceItem.width
2176+ when: root.requestedWidth >= 0
2177+ }
2178+ Binding {
2179+ target: root; property: "height"; value: surfaceItem.height
2180+ when: root.requestedHeight >= 0
2181+ }
2182+
2183+ // SurfaceContainer size drives MirSurface size
2184+ Binding {
2185+ target: surfaceItem; property: "width"; value: root.width
2186+ when: root.requestedWidth < 0
2187+ }
2188+ Binding {
2189+ target: surfaceItem; property: "height"; value: root.height
2190+ when: root.requestedHeight < 0
2191+ }
2192+
2193+
2194 TouchGate {
2195 targetItem: surfaceItem
2196 anchors.fill: root
2197
2198=== modified file 'qml/Stages/WindowDecoration.qml'
2199--- qml/Stages/WindowDecoration.qml 2015-03-13 19:18:35 +0000
2200+++ qml/Stages/WindowDecoration.qml 2015-10-14 19:15:46 +0000
2201@@ -1,5 +1,5 @@
2202 /*
2203- * Copyright (C) 2014 Canonical, Ltd.
2204+ * Copyright (C) 2014-2015 Canonical, Ltd.
2205 *
2206 * This program is free software; you can redistribute it and/or modify
2207 * it under the terms of the GNU General Public License as published by
2208@@ -12,25 +12,53 @@
2209 *
2210 * You should have received a copy of the GNU General Public License
2211 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2212- *
2213- * Authors: Michael Zanetti <michael.zanetti@canonical.com>
2214 */
2215
2216 import QtQuick 2.3
2217+import Unity.Application 0.1 // For Mir singleton
2218 import Ubuntu.Components 1.1
2219 import "../Components"
2220
2221-Item {
2222+MouseArea {
2223 id: root
2224 clip: true
2225
2226+ property Item target
2227 property alias title: titleLabel.text
2228 property bool active: false
2229+ hoverEnabled: true
2230
2231 signal close()
2232 signal minimize()
2233 signal maximize()
2234
2235+ QtObject {
2236+ id: priv
2237+ property real distanceX
2238+ property real distanceY
2239+ property bool dragging
2240+ }
2241+
2242+ onPressedChanged: {
2243+ if (pressed) {
2244+ var pos = mapToItem(root.target, mouseX, mouseY);
2245+ priv.distanceX = pos.x;
2246+ priv.distanceY = pos.y;
2247+ priv.dragging = true;
2248+ Mir.cursorName = "grabbing";
2249+ } else {
2250+ priv.dragging = false;
2251+ Mir.cursorName = "";
2252+ }
2253+ }
2254+ onMouseXChanged: {
2255+ if (priv.dragging) {
2256+ var pos = mapToItem(root.target.parent, mouseX, mouseY);
2257+ root.target.x = pos.x - priv.distanceX;
2258+ root.target.y = pos.y - priv.distanceY;
2259+ }
2260+ }
2261+
2262 Rectangle {
2263 anchors.fill: parent
2264 anchors.bottomMargin: -radius
2265
2266=== renamed file 'qml/Stages/WindowMoveResizeArea.qml' => 'qml/Stages/WindowResizeArea.qml'
2267--- qml/Stages/WindowMoveResizeArea.qml 2015-09-08 10:32:28 +0000
2268+++ qml/Stages/WindowResizeArea.qml 2015-10-14 19:15:46 +0000
2269@@ -12,18 +12,19 @@
2270 *
2271 * You should have received a copy of the GNU General Public License
2272 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2273- *
2274- * Authors: Michael Zanetti <michael.zanetti@canonical.com>
2275 */
2276
2277 import QtQuick 2.3
2278 import Ubuntu.Components 1.1
2279 import Utils 0.1
2280+import Unity.Application 0.1 // for Mir.cursorName
2281
2282 MouseArea {
2283 id: root
2284 anchors.fill: target
2285- anchors.margins: -resizeHandleWidth
2286+ anchors.margins: -borderThickness
2287+
2288+ hoverEnabled: true
2289
2290 property var windowStateStorage: WindowStateStorage
2291
2292@@ -31,79 +32,179 @@
2293 // The area will anchor to it and manage move and resize events
2294 property Item target: null
2295 property string windowId: ""
2296- property int resizeHandleWidth: 0
2297+ property int borderThickness: 0
2298 property int minWidth: 0
2299 property int minHeight: 0
2300-
2301- QtObject {
2302- id: priv
2303- readonly property int windowWidth: root.width - root.resizeHandleWidth * 2
2304- readonly property int windowHeight: root.height - resizeHandleWidth * 2
2305-
2306- property var startPoint
2307-
2308- property bool resizeTop: false
2309- property bool resizeBottom: false
2310- property bool resizeLeft: false
2311- property bool resizeRight: false
2312-
2313- }
2314+ property int defaultWidth: units.gu(60)
2315+ property int defaultHeight: units.gu(50)
2316
2317 Component.onCompleted: {
2318- var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
2319- if (windowState !== undefined) {
2320- target.x = windowState.x
2321- target.y = windowState.y
2322- target.width = windowState.width
2323- target.height = windowState.height
2324- }
2325- }
2326-
2327- onPressed: {
2328- priv.startPoint = Qt.point(mouse.x, mouse.y);
2329- priv.resizeTop = mouseY < root.resizeHandleWidth;
2330- priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth);
2331- priv.resizeLeft = mouseX < root.resizeHandleWidth;
2332- priv.resizeRight = mouseX > (root.width - root.resizeHandleWidth);
2333- }
2334-
2335- onPositionChanged: {
2336- var currentPoint = Qt.point(mouse.x, mouse.y);
2337- var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y);
2338- var moveDiff = Qt.point(0, 0);
2339- var sizeDiff = Qt.point(0, 0);
2340- var maxSizeDiff = Qt.point(root.minWidth - root.target.width, root.minHeight - root.target.height)
2341-
2342- if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) {
2343- if (priv.resizeTop) {
2344- sizeDiff.y = Math.max(maxSizeDiff.y, -currentPoint.y + priv.startPoint.y)
2345- moveDiff.y = -sizeDiff.y
2346- }
2347- if (priv.resizeBottom) {
2348- sizeDiff.y = Math.max(maxSizeDiff.y, currentPoint.y - priv.startPoint.y)
2349- priv.startPoint.y += sizeDiff.y
2350- }
2351- if (priv.resizeLeft) {
2352- sizeDiff.x = Math.max(maxSizeDiff.x, -currentPoint.x + priv.startPoint.x)
2353- moveDiff.x = -sizeDiff.x
2354- }
2355- if (priv.resizeRight) {
2356- sizeDiff.x = Math.max(maxSizeDiff.x, currentPoint.x - priv.startPoint.x)
2357- priv.startPoint.x += sizeDiff.x
2358- }
2359-
2360- target.x += moveDiff.x;
2361- target.y += moveDiff.y;
2362- target.width += sizeDiff.x;
2363- target.height += sizeDiff.y;
2364- } else {
2365- target.x += mouseDiff.x;
2366- target.y += mouseDiff.y;
2367- }
2368-
2369+ var windowState = windowStateStorage.getGeometry(root.windowId,
2370+ Qt.rect(target.x, target.y, defaultWidth, defaultHeight) /* default geometry */);
2371+
2372+ target.x = windowState.x;
2373+ target.y = windowState.y;
2374+
2375+ if (windowState.width >= minWidth) {
2376+ target.requestedWidth = windowState.width;
2377+ } else {
2378+ target.requestedWidth = minWidth;
2379+ }
2380+
2381+ if (windowState.height >= minHeight) {
2382+ target.requestedHeight = windowState.height;
2383+ } else {
2384+ target.requestedHeight = minHeight;
2385+ }
2386 }
2387
2388 Component.onDestruction: {
2389 windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
2390 }
2391+
2392+ QtObject {
2393+ id: d
2394+ property bool leftBorder: false
2395+ property bool rightBorder: false
2396+ property bool topBorder: false
2397+ property bool bottomBorder: false
2398+
2399+ property bool dragging: false
2400+ property real startMousePosX
2401+ property real startMousePosY
2402+ property real startX
2403+ property real startY
2404+ property real startWidth
2405+ property real startHeight
2406+ property real currentWidth
2407+ property real currentHeight
2408+
2409+ property string cursorName: {
2410+ if (root.containsMouse || root.pressed) {
2411+ if (leftBorder && !topBorder && !bottomBorder) {
2412+ return "left_side";
2413+ } else if (rightBorder && !topBorder && !bottomBorder) {
2414+ return "right_side";
2415+ } else if (topBorder && !leftBorder && !rightBorder) {
2416+ return "top_side";
2417+ } else if (bottomBorder && !leftBorder && !rightBorder) {
2418+ return "bottom_side";
2419+ } else if (leftBorder && topBorder) {
2420+ return "top_left_corner";
2421+ } else if (leftBorder && bottomBorder) {
2422+ return "bottom_left_corner";
2423+ } else if (rightBorder && topBorder) {
2424+ return "top_right_corner";
2425+ } else if (rightBorder && bottomBorder) {
2426+ return "bottom_right_corner";
2427+ } else {
2428+ return "";
2429+ }
2430+ } else {
2431+ return "";
2432+ }
2433+ }
2434+ onCursorNameChanged: {
2435+ Mir.cursorName = cursorName;
2436+ }
2437+
2438+ function updateBorders() {
2439+ leftBorder = mouseX <= borderThickness;
2440+ rightBorder = mouseX >= width - borderThickness;
2441+ topBorder = mouseY <= borderThickness;
2442+ bottomBorder = mouseY >= height - borderThickness;
2443+ }
2444+ }
2445+
2446+ onPressedChanged: {
2447+ var pos = mapToItem(target.parent, mouseX, mouseY);
2448+
2449+ if (pressed) {
2450+ d.updateBorders();
2451+ var pos = mapToItem(root.target.parent, mouseX, mouseY);
2452+ d.startMousePosX = pos.x;
2453+ d.startMousePosY = pos.y;
2454+ d.startX = target.x;
2455+ d.startY = target.y;
2456+ d.startWidth = target.width;
2457+ d.startHeight = target.height;
2458+ d.currentWidth = target.width;
2459+ d.currentHeight = target.height;
2460+ d.dragging = true;
2461+ } else {
2462+ d.dragging = false;
2463+ if (containsMouse) {
2464+ d.updateBorders();
2465+ }
2466+ }
2467+ }
2468+
2469+ onEntered: {
2470+ if (!pressed) {
2471+ d.updateBorders();
2472+ }
2473+ }
2474+
2475+ onPositionChanged: {
2476+ if (!pressed) {
2477+ d.updateBorders();
2478+ }
2479+
2480+ if (!d.dragging) {
2481+ return;
2482+ }
2483+
2484+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
2485+
2486+ var deltaX = pos.x - d.startMousePosX;
2487+ var deltaY = pos.y - d.startMousePosY;
2488+
2489+ if (d.leftBorder) {
2490+ var newTargetX = d.startX + deltaX;
2491+ if (target.x + target.width > newTargetX + minWidth) {
2492+ target.requestedWidth = target.x + target.width - newTargetX;
2493+ } else {
2494+ target.requestedWidth = minWidth;
2495+ }
2496+
2497+ } else if (d.rightBorder) {
2498+ if (d.startWidth + deltaX >= minWidth) {
2499+ target.requestedWidth = d.startWidth + deltaX;
2500+ } else {
2501+ target.requestedWidth = minWidth;
2502+ }
2503+ }
2504+
2505+ if (d.topBorder) {
2506+ var newTargetY = d.startY + deltaY;
2507+ if (target.y + target.height > newTargetY + minHeight) {
2508+ target.requestedHeight = target.y + target.height - newTargetY;
2509+ } else {
2510+ target.requestedHeight = minHeight;
2511+ }
2512+
2513+ } else if (d.bottomBorder) {
2514+ if (d.startHeight + deltaY >= minHeight) {
2515+ target.requestedHeight = d.startHeight + deltaY;
2516+ } else {
2517+ target.requestedHeight = minHeight;
2518+ }
2519+ }
2520+ }
2521+
2522+ Connections {
2523+ target: root.target
2524+ onWidthChanged: {
2525+ if (root.pressed && d.leftBorder) {
2526+ target.x += d.currentWidth - target.width;
2527+ }
2528+ d.currentWidth = target.width;
2529+ }
2530+ onHeightChanged: {
2531+ if (root.pressed && d.topBorder) {
2532+ target.y += d.currentHeight - target.height;
2533+ }
2534+ d.currentHeight = target.height;
2535+ }
2536+ }
2537 }
2538
2539=== modified file 'tests/mocks/CMakeLists.txt'
2540--- tests/mocks/CMakeLists.txt 2015-08-06 22:45:56 +0000
2541+++ tests/mocks/CMakeLists.txt 2015-10-14 19:15:46 +0000
2542@@ -29,6 +29,7 @@
2543 endmacro()
2544
2545 add_subdirectory(AccountsService)
2546+add_subdirectory(Cursor)
2547 add_subdirectory(GSettings.1.0)
2548 add_subdirectory(indicator-service)
2549 add_subdirectory(libusermetrics)
2550
2551=== added directory 'tests/mocks/Cursor'
2552=== added file 'tests/mocks/Cursor/CMakeLists.txt'
2553--- tests/mocks/Cursor/CMakeLists.txt 1970-01-01 00:00:00 +0000
2554+++ tests/mocks/Cursor/CMakeLists.txt 2015-10-14 19:15:46 +0000
2555@@ -0,0 +1,1 @@
2556+add_unity8_mock(Cursor 1.0 Cursor PREFIX mocks)
2557
2558=== added file 'tests/mocks/Cursor/Cursor.qml'
2559--- tests/mocks/Cursor/Cursor.qml 1970-01-01 00:00:00 +0000
2560+++ tests/mocks/Cursor/Cursor.qml 2015-10-14 19:15:46 +0000
2561@@ -0,0 +1,20 @@
2562+/*
2563+ * Copyright (C) 2015 Canonical, Ltd.
2564+ *
2565+ * This program is free software; you can redistribute it and/or modify
2566+ * it under the terms of the GNU General Public License as published by
2567+ * the Free Software Foundation; version 3.
2568+ *
2569+ * This program is distributed in the hope that it will be useful,
2570+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2571+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2572+ * GNU General Public License for more details.
2573+ *
2574+ * You should have received a copy of the GNU General Public License
2575+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2576+ */
2577+
2578+import QtQuick 2.4
2579+
2580+Item {
2581+}
2582
2583=== added file 'tests/mocks/Cursor/qmldir'
2584--- tests/mocks/Cursor/qmldir 1970-01-01 00:00:00 +0000
2585+++ tests/mocks/Cursor/qmldir 2015-10-14 19:15:46 +0000
2586@@ -0,0 +1,2 @@
2587+module Cursor
2588+Cursor 1.0 Cursor.qml
2589
2590=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
2591--- tests/mocks/Unity/Application/MirSurface.cpp 2015-09-25 12:13:13 +0000
2592+++ tests/mocks/Unity/Application/MirSurface.cpp 2015-10-14 19:15:46 +0000
2593@@ -35,8 +35,12 @@
2594 , m_activeFocus(false)
2595 , m_width(-1)
2596 , m_height(-1)
2597+ , m_slowToResize(false)
2598 {
2599 // qDebug() << "MirSurface::MirSurface() " << name;
2600+ m_delayedResizeTimer.setInterval(600);
2601+ m_delayedResizeTimer.setSingleShot(true);
2602+ connect(&m_delayedResizeTimer, &QTimer::timeout, this, &MirSurface::applyDelayedResize);
2603 }
2604
2605 MirSurface::~MirSurface()
2606@@ -170,7 +174,38 @@
2607
2608 void MirSurface::resize(int width, int height)
2609 {
2610+ if (m_slowToResize) {
2611+ if (!m_delayedResizeTimer.isActive()) {
2612+ m_delayedResize.setWidth(width);
2613+ m_delayedResize.setHeight(height);
2614+ m_delayedResizeTimer.start();
2615+ } else {
2616+ m_pendingResize.setWidth(width);
2617+ m_pendingResize.setHeight(height);
2618+ }
2619+ } else {
2620+ doResize(width, height);
2621+ }
2622+}
2623+
2624+void MirSurface::applyDelayedResize()
2625+{
2626+ doResize(m_delayedResize.width(), m_delayedResize.height());
2627+ m_delayedResize.setWidth(-1);
2628+ m_delayedResize.setHeight(-1);
2629+
2630+ if (m_pendingResize.isValid()) {
2631+ QSize size = m_pendingResize;
2632+ m_pendingResize.setWidth(-1);
2633+ m_pendingResize.setHeight(-1);
2634+ resize(size.width(), size.height());
2635+ }
2636+}
2637+
2638+void MirSurface::doResize(int width, int height)
2639+{
2640 bool changed = false;
2641+
2642 if (width != m_width) {
2643 m_width = width;
2644 Q_EMIT widthChanged();
2645@@ -187,3 +222,20 @@
2646 Q_EMIT sizeChanged(QSize(width, height));
2647 }
2648 }
2649+
2650+bool MirSurface::isSlowToResize() const
2651+{
2652+ return m_slowToResize;
2653+}
2654+
2655+void MirSurface::setSlowToResize(bool value)
2656+{
2657+ if (m_slowToResize != value) {
2658+ m_slowToResize = value;
2659+ Q_EMIT slowToResizeChanged();
2660+ if (!m_slowToResize && m_delayedResizeTimer.isActive()) {
2661+ m_delayedResizeTimer.stop();
2662+ applyDelayedResize();
2663+ }
2664+ }
2665+}
2666
2667=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
2668--- tests/mocks/Unity/Application/MirSurface.h 2015-09-02 10:35:16 +0000
2669+++ tests/mocks/Unity/Application/MirSurface.h 2015-10-14 19:15:46 +0000
2670@@ -18,6 +18,7 @@
2671 #define MOCK_MIR_SURFACE_H
2672
2673 #include <QObject>
2674+#include <QTimer>
2675 #include <QUrl>
2676
2677 // unity-api
2678@@ -32,6 +33,7 @@
2679 Q_PROPERTY(int width READ width NOTIFY widthChanged)
2680 Q_PROPERTY(int height READ height NOTIFY heightChanged)
2681 Q_PROPERTY(bool activeFocus READ activeFocus NOTIFY activeFocusChanged)
2682+ Q_PROPERTY(bool slowToResize READ isSlowToResize WRITE setSlowToResize NOTIFY slowToResizeChanged)
2683
2684 public:
2685 MirSurface(const QString& name,
2686@@ -73,6 +75,9 @@
2687 int width() const;
2688 int height() const;
2689
2690+ bool isSlowToResize() const;
2691+ void setSlowToResize(bool value);
2692+
2693 /////
2694 // internal mock stuff
2695
2696@@ -90,13 +95,19 @@
2697 void orientationAngleChanged(Mir::OrientationAngle angle);
2698 void widthChanged();
2699 void heightChanged();
2700+ void slowToResizeChanged();
2701
2702 ////
2703 // internal mock stuff
2704 void screenshotUrlChanged(QUrl);
2705 void activeFocusChanged(bool);
2706
2707+private Q_SLOTS:
2708+ void applyDelayedResize();
2709+
2710 private:
2711+ void doResize(int width, int height);
2712+
2713 const QString m_name;
2714 const Mir::Type m_type;
2715 Mir::State m_state;
2716@@ -108,6 +119,11 @@
2717 bool m_activeFocus;
2718 int m_width;
2719 int m_height;
2720+
2721+ bool m_slowToResize;
2722+ QTimer m_delayedResizeTimer;
2723+ QSize m_delayedResize;
2724+ QSize m_pendingResize;
2725 };
2726
2727 #endif // MOCK_MIR_SURFACE_H
2728
2729=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
2730--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2015-09-25 12:13:13 +0000
2731+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2015-10-14 19:15:46 +0000
2732@@ -37,6 +37,7 @@
2733 , m_surfaceHeight(0)
2734 , m_touchPressCount(0)
2735 , m_touchReleaseCount(0)
2736+ , m_fillMode(Stretch)
2737 {
2738 // qDebug() << "MirSurfaceItem::MirSurfaceItem() " << (void*)(this) << name();
2739 setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton |
2740@@ -295,3 +296,16 @@
2741 m_qmlSurface->resize(m_surfaceWidth, m_surfaceHeight);
2742 }
2743 }
2744+
2745+MirSurfaceItem::FillMode MirSurfaceItem::fillMode() const
2746+{
2747+ return m_fillMode;
2748+}
2749+
2750+void MirSurfaceItem::setFillMode(FillMode value)
2751+{
2752+ if (m_fillMode != value) {
2753+ m_fillMode = value;
2754+ Q_EMIT fillModeChanged(m_fillMode);
2755+ }
2756+}
2757
2758=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
2759--- tests/mocks/Unity/Application/MirSurfaceItem.h 2015-09-19 07:37:52 +0000
2760+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2015-10-14 19:15:46 +0000
2761@@ -62,6 +62,9 @@
2762 int surfaceHeight() const override;
2763 void setSurfaceHeight(int value) override;
2764
2765+ FillMode fillMode() const override;
2766+ void setFillMode(FillMode value) override;
2767+
2768 /////
2769 // For use in qml tests
2770
2771@@ -106,6 +109,8 @@
2772 int m_touchPressCount;
2773 int m_touchReleaseCount;
2774
2775+ FillMode m_fillMode;
2776+
2777 friend class SurfaceManager;
2778 };
2779
2780
2781=== modified file 'tests/qmltests/CMakeLists.txt'
2782--- tests/qmltests/CMakeLists.txt 2015-09-19 07:37:52 +0000
2783+++ tests/qmltests/CMakeLists.txt 2015-10-14 19:15:46 +0000
2784@@ -78,7 +78,7 @@
2785 add_unity8_qmltest(Stages SurfaceContainer)
2786 add_unity8_qmltest(Stages SessionContainer)
2787 add_unity8_qmltest(Stages TabletStage)
2788-add_unity8_qmltest(Stages WindowMoveResizeArea)
2789+add_unity8_qmltest(Stages WindowResizeArea)
2790 add_unity8_qmltest(Stages Splash)
2791 add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
2792 add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
2793
2794=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
2795--- tests/qmltests/Stages/tst_DesktopStage.qml 2015-09-17 12:25:29 +0000
2796+++ tests/qmltests/Stages/tst_DesktopStage.qml 2015-10-14 19:15:46 +0000
2797@@ -15,9 +15,10 @@
2798 */
2799
2800 import QtQuick 2.0
2801+import QtQuick.Layouts 1.1
2802 import QtTest 1.0
2803-import Ubuntu.Components 1.1
2804-import Ubuntu.Components.ListItems 1.0 as ListItem
2805+import Ubuntu.Components 1.3
2806+import Ubuntu.Components.ListItems 1.3
2807 import Unity.Application 0.1
2808 import Unity.Test 0.1
2809 import Utils 0.1
2810@@ -78,6 +79,21 @@
2811 Column {
2812 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
2813 spacing: units.gu(1)
2814+
2815+ Button {
2816+ color: "white"
2817+ text: "Make surface slow to resize"
2818+ activeFocusOnPress: false
2819+ onClicked: {
2820+ if (ApplicationManager.focusedApplicationId) {
2821+ var surface = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).session.surface;
2822+ surface.slowToResize = true;
2823+ }
2824+ }
2825+ }
2826+
2827+ Divider {}
2828+
2829 Repeater {
2830 model: ApplicationManager.availableApplications
2831 ApplicationCheckBox {
2832
2833=== renamed file 'tests/qmltests/Stages/tst_WindowMoveResizeArea.qml' => 'tests/qmltests/Stages/tst_WindowResizeArea.qml'
2834--- tests/qmltests/Stages/tst_WindowMoveResizeArea.qml 2015-09-07 13:45:50 +0000
2835+++ tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-14 19:15:46 +0000
2836@@ -17,7 +17,7 @@
2837 import QtQuick 2.1
2838 import QtQuick.Layouts 1.1
2839 import QtTest 1.0
2840-import Unity.Test 0.1 as UT
2841+import Unity.Test 0.1
2842 import ".."
2843 import "../../../qml/Stages"
2844 import Ubuntu.Components 0.1
2845@@ -29,41 +29,43 @@
2846 height: units.gu(60)
2847 width: units.gu(60)
2848
2849- property var fakeWindow: windowLoader.item
2850-
2851 Component {
2852 id: fakeWindowComponent
2853
2854 Item {
2855 id: fakeWindow
2856- property alias minWidth: moveResizeArea.minWidth
2857- property alias minHeight: moveResizeArea.minHeight
2858+ property alias minWidth: windowResizeArea.minWidth
2859+ property alias minHeight: windowResizeArea.minHeight
2860 x: units.gu(20)
2861 y: units.gu(20)
2862- height: units.gu(20)
2863- width: units.gu(20)
2864- property int windowHeight: height
2865- property int windowWidth: width
2866- onWindowHeightChanged: height = windowHeight
2867- onWindowWidthChanged: width = windowWidth
2868+ width: requestedWidth
2869+ height: requestedHeight
2870+ property real requestedWidth
2871+ property real requestedHeight
2872
2873- WindowMoveResizeArea {
2874- id: moveResizeArea
2875+ WindowResizeArea {
2876+ id: windowResizeArea
2877 target: fakeWindow
2878- resizeHandleWidth: units.gu(0.5)
2879+ borderThickness: units.gu(2)
2880 minWidth: units.gu(15)
2881 minHeight: units.gu(10)
2882+ defaultWidth: units.gu(20)
2883+ defaultHeight: units.gu(20)
2884 windowId: "test-window-id"
2885 }
2886
2887 Rectangle {
2888- anchors.fill: moveResizeArea
2889+ anchors.fill: windowResizeArea
2890 color: "red"
2891 }
2892
2893 Rectangle {
2894 anchors.fill: fakeWindow
2895 color: "blue"
2896+ MouseArea {
2897+ anchors.fill: parent
2898+ hoverEnabled: true
2899+ }
2900 }
2901 }
2902 }
2903@@ -71,45 +73,41 @@
2904 Loader {
2905 id: windowLoader
2906 sourceComponent: fakeWindowComponent
2907- }
2908-
2909- UT.UnityTestCase {
2910- name: "WindowMoveResizeArea"
2911+ active: windowLoaderCheckbox.checked
2912+ }
2913+
2914+ Column {
2915+ MouseTouchEmulationCheckbox {
2916+ checked: false
2917+ color: "black"
2918+ }
2919+ RowLayout {
2920+ Layout.fillWidth: true
2921+ CheckBox {
2922+ id: windowLoaderCheckbox
2923+ checked: true
2924+ activeFocusOnPress: false
2925+ }
2926+ Label {
2927+ id: label
2928+ color: "black"
2929+ text: "Window loader active"
2930+ anchors.verticalCenter: parent.verticalCenter
2931+ }
2932+ }
2933+ }
2934+
2935+ UnityTestCase {
2936+ name: "WindowResizeArea"
2937 when: windowShown
2938
2939+ property var fakeWindow: windowLoader.item
2940+
2941 function init() {
2942 fakeWindow.x = units.gu(20)
2943 fakeWindow.y = units.gu(20)
2944- fakeWindow.width = units.gu(20)
2945- fakeWindow.height = units.gu(20)
2946- }
2947-
2948- function test_dragWindow_data() {
2949- return [
2950- { tag: "up", dx: 0, dy: units.gu(-10) },
2951- { tag: "down", dx: 0, dy: units.gu(10) },
2952- { tag: "left", dx: units.gu(-10), dy: 0 },
2953- { tag: "right", dx: units.gu(10), dy: 0 },
2954- { tag: "right/down", dx: units.gu(10), dy: units.gu(10) },
2955- { tag: "left/down", dx: units.gu(-10), dy: units.gu(10) }
2956- ]
2957- }
2958-
2959- function test_dragWindow(data) {
2960- var initialWindowX = fakeWindow.x;
2961- var initialWindowY = fakeWindow.y;
2962- var initialWindowWidth = fakeWindow.width
2963- var initialWindowHeight = fakeWindow.height
2964-
2965- var startDragX = initialWindowX + fakeWindow.width / 2;
2966- var startDragY = initialWindowY + fakeWindow.height / 2;
2967- mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10)
2968-
2969- tryCompare(fakeWindow, "x", initialWindowX + data.dx)
2970- tryCompare(fakeWindow, "y", initialWindowX + data.dy)
2971-
2972- compare(fakeWindow.height, initialWindowHeight);
2973- compare(fakeWindow.width, initialWindowWidth);
2974+ fakeWindow.requestedWidth = units.gu(20)
2975+ fakeWindow.requestedHeight = units.gu(20)
2976 }
2977
2978 function test_resizeWindowRightBottom_data() {
2979@@ -166,30 +164,6 @@
2980 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
2981 }
2982
2983- function test_saveRestorePosition() {
2984- var initialWindowX = fakeWindow.x;
2985- var initialWindowY = fakeWindow.y;
2986- var initialWindowWidth = fakeWindow.width;
2987- var initialWindowHeight = fakeWindow.height;
2988-
2989- var moveDelta = units.gu(5);
2990- var startDragX = initialWindowX + fakeWindow.width / 2;
2991- var startDragY = initialWindowY + fakeWindow.height / 2;
2992- mouseFlick(root, startDragX, startDragY, startDragX + moveDelta, startDragY + moveDelta, true, true, units.gu(.5), 10)
2993-
2994- tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
2995- tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
2996-
2997- // This will destroy the window and recreate it
2998- windowLoader.active = false;
2999- waitForRendering(root);
3000- windowLoader.active = true;
3001-
3002- // Make sure it's again where we left it before destroying
3003- tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
3004- tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
3005- }
3006-
3007 function test_saveRestoreSize() {
3008 var initialWindowX = fakeWindow.x;
3009 var initialWindowY = fakeWindow.y;
3010@@ -219,11 +193,12 @@
3011 function test_resizeSmallerAndLarger_data() {
3012 return [
3013 { tag: "topLeft", startX: -1, startY: -1, dx: units.gu(15), dy: units.gu(15) },
3014- { tag: "bottomRight", startX: fakeWindow.width + 1, startY: fakeWindow.height + 1, dx: -units.gu(15), dy: -units.gu(15) }
3015+ { tag: "bottomRight", startX: units.gu(20) + 1, startY: units.gu(20) + 1, dx: -units.gu(15), dy: -units.gu(15) }
3016 ]
3017 }
3018
3019 function test_resizeSmallerAndLarger(data) {
3020+ console.log("windowLoader="+windowLoader + ", windowLoader.item="+windowLoader.item + ", windowLoader.active="+windowLoader.active);
3021 var initialWindowX = fakeWindow.x;
3022 var initialWindowY = fakeWindow.y;
3023 var initialWindowWidth = fakeWindow.width
3024
3025=== modified file 'tests/qmltests/tst_OrientedShell.qml'
3026--- tests/qmltests/tst_OrientedShell.qml 2015-09-21 13:37:47 +0000
3027+++ tests/qmltests/tst_OrientedShell.qml 2015-10-14 19:15:46 +0000
3028@@ -264,6 +264,10 @@
3029 text: "Usage Mode"
3030 model: ["Staged", "Windowed", "Automatic"]
3031 }
3032+ MouseTouchEmulationCheckbox {
3033+ checked: true
3034+ color: "white"
3035+ }
3036 Button {
3037 text: "Switch fullscreen"
3038 activeFocusOnPress: false

Subscribers

People subscribed via source and target branches