Lomiri
Loading...
Searching...
No Matches
CursorImageProvider.cpp
1/*
2 * Copyright (C) 2015-2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "CursorImageProvider.h"
18
19#include <QCursor>
20#include <QDebug>
21#include <QFile>
22#include <QPainter>
23#include <QSvgRenderer>
24
25CursorImageProvider *CursorImageProvider::m_instance = nullptr;
26
28// BuiltInCursorImage
29
30BuiltInCursorImage::BuiltInCursorImage(int cursorHeight)
31{
32 const char *svgString =
33 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
34 "<svg"
35 " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
36 " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
37 " xmlns:svg=\"http://www.w3.org/2000/svg\""
38 " xmlns=\"http://www.w3.org/2000/svg\""
39 " version=\"1.1\">"
40 " <path"
41 " 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\""
42 " 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\" />"
43 "</svg>";
44
45 // NB: Original image dimension is 20x32. Ensure aspect ratio is kept
46 qimage = QImage((20./32.)*cursorHeight, cursorHeight, QImage::Format_ARGB32);
47 qimage.fill(Qt::transparent);
48 QPainter imagePainter(&qimage);
49
50 frameWidth = qimage.width();
51 frameHeight = qimage.height();
52 requestedHeight = cursorHeight;
53
54 QSvgRenderer *svgRenderer = new QSvgRenderer(QByteArray(svgString));
55 svgRenderer->render(&imagePainter);
56 delete svgRenderer;
57}
58
60// BlankCursorImage
61
62
63BlankCursorImage::BlankCursorImage()
64{
65 qimage = QImage(1, 1, QImage::Format_ARGB32);
66 qimage.fill(Qt::transparent);
67 frameWidth = qimage.width();
68 frameHeight = qimage.height();
69}
70
72// CustomCursorImage
73
74
75CustomCursorImage::CustomCursorImage(const QCursor &cursor)
76{
77 qimage = cursor.pixmap().toImage();
78 hotspot = cursor.hotSpot();
79 frameWidth = qimage.width();
80 frameHeight = qimage.height();
81}
82
84// XCursorImage
85
86XCursorImage::XCursorImage(const QString &theme, const QString &file, int preferredCursorHeightPx)
87{
88 requestedHeight = preferredCursorHeightPx;
89
90 XcursorImages *xcursorImages = XcursorLibraryLoadImages(QFile::encodeName(file), QFile::encodeName(theme),
91 preferredCursorHeightPx);
92 if (!xcursorImages || xcursorImages->nimage == 0) {
93 return;
94 }
95
96 frameCount = xcursorImages->nimage;
97
98 for (int i = 0; i < xcursorImages->nimage; ++i) {
99 XcursorImage *xcursorImage = xcursorImages->images[i];
100 if (frameWidth < (int)xcursorImage->width) {
101 frameWidth = xcursorImage->width;
102 }
103 if (frameHeight < (int)xcursorImage->height) {
104 frameHeight = xcursorImage->height;
105 }
106 if (i == 0) {
107 frameDuration = (int)xcursorImage->delay;
108 } else {
109 if (frameDuration != (int)xcursorImage->delay) {
110 qWarning().nospace() << "CursorImageProvider: XCursorImage("<<theme<<","<<file<<") has"
111 " varying delays in its animation. Animation won't look right.";
112 }
113 }
114 }
115
116 {
117 // Assume that the hotspot position does not animate
118 XcursorImage *xcursorImage = xcursorImages->images[0];
119 hotspot.setX(xcursorImage->xhot);
120 hotspot.setY(xcursorImage->yhot);
121 }
122
123 // Build the sprite as a single row of frames
124 qimage = QImage(frameWidth*frameCount, frameHeight, QImage::Format_ARGB32);
125 qimage.fill(Qt::transparent);
126
127 {
128 QPainter painter(&qimage);
129
130 for (int i = 0; i < xcursorImages->nimage; ++i) {
131 XcursorImage *xcursorImage = xcursorImages->images[i];
132
133 auto frameImage = QImage((uchar*)xcursorImage->pixels,
134 xcursorImage->width, xcursorImage->height, QImage::Format_ARGB32);
135
136 painter.drawImage(QPoint(i*frameWidth, 0), frameImage);
137 }
138 }
139
140 XcursorImagesDestroy(xcursorImages);
141}
142
143XCursorImage::~XCursorImage()
144{
145}
146
148// CursorImageProvider
149
150CursorImageProvider::CursorImageProvider()
151 : QQuickImageProvider(QQuickImageProvider::Image)
152{
153 if (m_instance) {
154 qFatal("Cannot have multiple CursorImageProvider instances");
155 }
156 m_instance = this;
157
158 m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("grabbing"));
159 m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("dnd-none"));
160
161 m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("dnd-none"));
162 m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("grabbing"));
163 m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("closedhand"));
164
165 m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("dnd-none"));
166 m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("grabbing"));
167 m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("closedhand"));
168
169 m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("dnd-none"));
170 m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("grabbing"));
171 m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("closedhand"));
172
173 m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("crossed_circle")); // DMZ-White and DMZ-Black themes
174 m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("not-allowed"));
175 m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("circle"));
176
177 m_fallbackNames[QStringLiteral("grabbing")].append(QStringLiteral("closedhand")); // Breeze
178
179 m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointing_hand"));
180 m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointer"));
181
182 m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("xterm"));
183 m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("text"));
184
185 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("default"));
186 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("top_left_arrow"));
187 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("left_arrow"));
188
189 m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("half-busy"));
190 m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("progress"));
191
192 m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("fd_double_arrow"));
193 m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("nesw-resize"));
194
195 m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("bd_double_arrow")); // DMZ-White and DMZ-Black themes
196 m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("nwse-resize"));
197
198 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
199 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("ew-resize"));
200 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("h_double_arrow"));
201
202 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
203 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("ns-resize"));
204 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("v_double_arrow"));
205
206 m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
207 m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("col-resize"));
208
209 m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
210 m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("row-resize"));
211
212 m_fallbackNames[QStringLiteral("up_arrow")].append(QStringLiteral("sb_up_arrow")); // DMZ-White and DMZ-Black themes
213
214 m_fallbackNames[QStringLiteral("watch")].append(QStringLiteral("wait"));
215
216 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("left_ptr_help"));
217 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("help"));
218 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("question_arrow"));
219
220 m_fallbackNames[QStringLiteral("xterm")].append(QStringLiteral("ibeam"));
221}
222
223CursorImageProvider::~CursorImageProvider()
224{
225 {
226 QList< QMap<QString, CursorImage*> > cursorList = m_cursors.values();
227
228 for (int i = 0; i < cursorList.count(); ++i) {
229 QList<CursorImage*> cursorImageList = cursorList[i].values();
230 for (int j = 0; j < cursorImageList.count(); ++j) {
231 delete cursorImageList[j];
232 }
233 }
234 }
235
236 m_cursors.clear();
237 m_instance = nullptr;
238}
239
240QImage CursorImageProvider::requestImage(const QString &cursorThemeAndNameAndHeight, QSize *size, const QSize & /*requestedSize*/)
241{
242 CursorImage *cursorImage = fetchCursor(cursorThemeAndNameAndHeight);
243 size->setWidth(cursorImage->qimage.width());
244 size->setHeight(cursorImage->qimage.height());
245
246 return cursorImage->qimage;
247}
248
249CursorImage *CursorImageProvider::fetchCursor(const QString &cursorThemeAndNameAndHeight)
250{
251 QString themeName;
252 QString cursorName;
253 int cursorHeight;
254 {
255 QStringList themeAndNameList = cursorThemeAndNameAndHeight.split('/');
256 if (themeAndNameList.size() != 3) {
257 return nullptr;
258 }
259 themeName = themeAndNameList[0];
260 cursorName = themeAndNameList[1];
261
262 bool ok;
263 cursorHeight = themeAndNameList[2].toInt(&ok);
264 if (!ok) {
265 cursorHeight = 32;
266 qWarning().nospace() << "CursorImageProvider: invalid cursor height ("<<themeAndNameList[2]<<")."
267 " Falling back to "<<cursorHeight<<" pixels";
268 }
269 }
270
271 return fetchCursor(themeName, cursorName, cursorHeight);
272}
273
274CursorImage *CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName, int cursorHeight)
275{
276 CursorImage *cursorImage = fetchCursorHelper(themeName, cursorName, cursorHeight);
277
278 // Try some fallbacks
279 if (cursorImage->qimage.isNull()) {
280 if (m_fallbackNames.contains(cursorName)) {
281 const QStringList &fallbackNames = m_fallbackNames[cursorName];
282 int i = 0;
283 while (cursorImage->qimage.isNull() && i < fallbackNames.count()) {
284 qDebug().nospace() << "CursorImageProvider: "<< cursorName <<" not found, trying " << fallbackNames.at(i);
285 cursorImage = fetchCursorHelper(themeName, fallbackNames.at(i), cursorHeight);
286 ++i;
287 }
288 }
289 }
290
291 // if it all fails, there must be at least a left_ptr
292 if (cursorImage->qimage.isNull() && cursorName != QLatin1String("left_ptr")) {
293 qDebug() << "CursorImageProvider:" << cursorName
294 << "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
295 cursorImage = fetchCursorHelper(themeName, QStringLiteral("left_ptr"), cursorHeight);
296 }
297
298 if (cursorImage->qimage.isNull()) {
299 // finally, go for the built-in cursor
300 qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
301 if (!m_builtInCursorImage || m_builtInCursorImage->requestedHeight != cursorHeight) {
302 m_builtInCursorImage.reset(new BuiltInCursorImage(cursorHeight));
303 }
304 cursorImage = m_builtInCursorImage.data();
305 }
306
307 return cursorImage;
308}
309
310CursorImage *CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName, int cursorHeight)
311{
312 if (cursorName == QLatin1String("blank")) {
313 return &m_blankCursorImage;
314 } else if (cursorName.startsWith(QLatin1String("custom"))) {
315 return m_customCursorImage.data();
316 } else {
317 QMap<QString, CursorImage*> &themeCursors = m_cursors[themeName];
318
319 if (!themeCursors.contains(cursorName)) {
320 themeCursors[cursorName] = new XCursorImage(themeName, cursorName, cursorHeight);
321 } else if (themeCursors[cursorName]->requestedHeight != cursorHeight) {
322 delete themeCursors.take(cursorName);
323 themeCursors[cursorName] = new XCursorImage(themeName, cursorName, cursorHeight);
324 }
325
326 return themeCursors[cursorName];
327 }
328}
329
330void CursorImageProvider::setCustomCursor(const QCursor &customCursor)
331{
332 if (customCursor.pixmap().isNull()) {
333 m_customCursorImage.reset();
334 } else {
335 m_customCursorImage.reset(new CustomCursorImage(customCursor));
336 }
337}