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 m_blankCursorImage.reset(new BlankCursorImage());
223}
224
225CursorImageProvider::~CursorImageProvider()
226{
227 {
228 auto cursorList = m_cursors.values();
229
230 for (int i = 0; i < cursorList.count(); ++i) {
231 QList<QSharedPointer<CursorImage>> cursorImageList = cursorList[i].values();
232 for (int j = 0; j < cursorImageList.count(); ++j) {
233 cursorImageList[j].reset();
234 }
235 }
236 }
237
238 m_cursors.clear();
239 m_instance = nullptr;
240}
241
242QImage CursorImageProvider::requestImage(const QString &cursorThemeAndNameAndHeight, QSize *size, const QSize & /*requestedSize*/)
243{
244 QSharedPointer<CursorImage> cursorImage = fetchCursor(cursorThemeAndNameAndHeight);
245 size->setWidth(cursorImage->qimage.width());
246 size->setHeight(cursorImage->qimage.height());
247
248 return cursorImage->qimage;
249}
250
251QSharedPointer<CursorImage> CursorImageProvider::fetchCursor(const QString &cursorThemeAndNameAndHeight)
252{
253 QString themeName;
254 QString cursorName;
255 int cursorHeight;
256 {
257 QStringList themeAndNameList = cursorThemeAndNameAndHeight.split('/');
258 if (themeAndNameList.size() != 3) {
259 return nullptr;
260 }
261 themeName = themeAndNameList[0];
262 cursorName = themeAndNameList[1];
263
264 bool ok;
265 cursorHeight = themeAndNameList[2].toInt(&ok);
266 if (!ok) {
267 cursorHeight = 32;
268 qWarning().nospace() << "CursorImageProvider: invalid cursor height ("<<themeAndNameList[2]<<")."
269 " Falling back to "<<cursorHeight<<" pixels";
270 }
271 }
272
273 return fetchCursor(themeName, cursorName, cursorHeight);
274}
275
276QSharedPointer<CursorImage> CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName, int cursorHeight)
277{
278 QSharedPointer<CursorImage> cursorImage = fetchCursorHelper(themeName, cursorName, cursorHeight);
279
280 // Try some fallbacks
281 if (cursorImage->qimage.isNull()) {
282 if (m_fallbackNames.contains(cursorName)) {
283 const QStringList &fallbackNames = m_fallbackNames[cursorName];
284 int i = 0;
285 while (cursorImage->qimage.isNull() && i < fallbackNames.count()) {
286 qDebug().nospace() << "CursorImageProvider: "<< cursorName <<" not found, trying " << fallbackNames.at(i);
287 cursorImage = fetchCursorHelper(themeName, fallbackNames.at(i), cursorHeight);
288 ++i;
289 }
290 }
291 }
292
293 // if it all fails, there must be at least a left_ptr
294 if (cursorImage->qimage.isNull() && cursorName != QLatin1String("left_ptr")) {
295 qDebug() << "CursorImageProvider:" << cursorName
296 << "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
297 cursorImage = fetchCursorHelper(themeName, QStringLiteral("left_ptr"), cursorHeight);
298 }
299
300 if (cursorImage->qimage.isNull()) {
301 // finally, go for the built-in cursor
302 qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
303 if (!m_builtInCursorImage || m_builtInCursorImage->requestedHeight != cursorHeight) {
304 m_builtInCursorImage.reset(new BuiltInCursorImage(cursorHeight));
305 }
306 cursorImage = m_builtInCursorImage;
307 }
308
309 return cursorImage;
310}
311
312QSharedPointer<CursorImage> CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName, int cursorHeight)
313{
314 if (cursorName == QLatin1String("blank")) {
315 return m_blankCursorImage;
316 } else if (cursorName.startsWith(QLatin1String("custom"))) {
317 return m_customCursorImage;
318 } else {
319 QMap<QString, QSharedPointer<CursorImage>> &themeCursors = m_cursors[themeName];
320
321 if (!themeCursors.contains(cursorName)) {
322 themeCursors[cursorName] = QSharedPointer<CursorImage>(new XCursorImage(themeName, cursorName, cursorHeight));
323 } else if (themeCursors[cursorName]->requestedHeight != cursorHeight) {
324 themeCursors.take(cursorName).reset();
325 themeCursors[cursorName] = QSharedPointer<CursorImage>(new XCursorImage(themeName, cursorName, cursorHeight));
326 }
327
328 return themeCursors[cursorName];
329 }
330}
331
332void CursorImageProvider::setCustomCursor(const QCursor &customCursor)
333{
334 if (customCursor.pixmap().isNull()) {
335 m_customCursorImage.reset();
336 } else {
337 m_customCursorImage.reset(new CustomCursorImage(customCursor));
338 }
339}