on 669c2af help sign in
unity8/plugins/Cursor/CursorImageProvider.cpp
335 lines 13 kB history raw
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 */
17#include "CursorImageProvider.h"
19#include <QCursor>
20#include <QDebug>
21#include <QFile>
22#include <QPainter>
23#include <QSvgRenderer>
25CursorImageProvider *CursorImageProvider::m_instance = nullptr;
27/////
28// BuiltInCursorImage
30BuiltInCursorImage::BuiltInCursorImage(int cursorHeight)
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>";
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);
50 frameWidth = qimage.width();
51 frameHeight = qimage.height();
52 requestedHeight = cursorHeight;
54 QSvgRenderer *svgRenderer = new QSvgRenderer(QByteArray(svgString));
55 svgRenderer->render(&imagePainter);
56 delete svgRenderer;
59/////
60// BlankCursorImage
63BlankCursorImage::BlankCursorImage()
65 qimage = QImage(1, 1, QImage::Format_ARGB32);
66 qimage.fill(Qt::transparent);
67 frameWidth = qimage.width();
68 frameHeight = qimage.height();
71/////
72// CustomCursorImage
75CustomCursorImage::CustomCursorImage(const QCursor &cursor)
77 qimage = cursor.pixmap().toImage();
78 hotspot = cursor.hotSpot();
79 frameWidth = qimage.width();
80 frameHeight = qimage.height();
83/////
84// XCursorImage
86XCursorImage::XCursorImage(const QString &theme, const QString &file, int preferredCursorHeightPx)
88 requestedHeight = preferredCursorHeightPx;
90 XcursorImages *xcursorImages = XcursorLibraryLoadImages(QFile::encodeName(file), QFile::encodeName(theme),
91 preferredCursorHeightPx);
92 if (!xcursorImages || xcursorImages->nimage == 0) {
93 return;
94 }
96 frameCount = xcursorImages->nimage;
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 }
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 }
123 // Build the sprite as a single row of frames
124 qimage = QImage(frameWidth*frameCount, frameHeight, QImage::Format_ARGB32);
125 qimage.fill(Qt::transparent);
127 {
128 QPainter painter(&qimage);
130 for (int i = 0; i < xcursorImages->nimage; ++i) {
131 XcursorImage *xcursorImage = xcursorImages->images[i];
133 auto frameImage = QImage((uchar*)xcursorImage->pixels,
134 xcursorImage->width, xcursorImage->height, QImage::Format_ARGB32);
136 painter.drawImage(QPoint(i*frameWidth, 0), frameImage);
137 }
138 }
140 XcursorImagesDestroy(xcursorImages);
143XCursorImage::~XCursorImage()
147/////
148// CursorImageProvider
150CursorImageProvider::CursorImageProvider()
151 : QQuickImageProvider(QQuickImageProvider::Image)
153 if (m_instance) {
154 qFatal("Cannot have multiple CursorImageProvider instances");
155 }
156 m_instance = this;
158 m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("grabbing"));
159 m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("dnd-none"));
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"));
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"));
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"));
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"));
177 m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointing_hand"));
178 m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointer"));
180 m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("xterm"));
181 m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("text"));
183 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("default"));
184 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("top_left_arrow"));
185 m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("left_arrow"));
187 m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("half-busy"));
188 m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("progress"));
190 m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("fd_double_arrow"));
191 m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("nesw-resize"));
193 m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("bd_double_arrow")); // DMZ-White and DMZ-Black themes
194 m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("nwse-resize"));
196 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
197 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("ew-resize"));
198 m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("h_double_arrow"));
200 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
201 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("ns-resize"));
202 m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("v_double_arrow"));
204 m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
205 m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("col-resize"));
207 m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
208 m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("row-resize"));
210 m_fallbackNames[QStringLiteral("up_arrow")].append(QStringLiteral("sb_up_arrow")); // DMZ-White and DMZ-Black themes
212 m_fallbackNames[QStringLiteral("watch")].append(QStringLiteral("wait"));
214 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("left_ptr_help"));
215 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("help"));
216 m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("question_arrow"));
218 m_fallbackNames[QStringLiteral("xterm")].append(QStringLiteral("ibeam"));
221CursorImageProvider::~CursorImageProvider()
223 {
224 QList< QMap<QString, CursorImage*> > cursorList = m_cursors.values();
226 for (int i = 0; i < cursorList.count(); ++i) {
227 QList<CursorImage*> cursorImageList = cursorList[i].values();
228 for (int j = 0; j < cursorImageList.count(); ++j) {
229 delete cursorImageList[j];
230 }
231 }
232 }
234 m_cursors.clear();
235 m_instance = nullptr;
238QImage CursorImageProvider::requestImage(const QString &cursorThemeAndNameAndHeight, QSize *size, const QSize & /*requestedSize*/)
240 CursorImage *cursorImage = fetchCursor(cursorThemeAndNameAndHeight);
241 size->setWidth(cursorImage->qimage.width());
242 size->setHeight(cursorImage->qimage.height());
244 return cursorImage->qimage;
247CursorImage *CursorImageProvider::fetchCursor(const QString &cursorThemeAndNameAndHeight)
249 QString themeName;
250 QString cursorName;
251 int cursorHeight;
252 {
253 QStringList themeAndNameList = cursorThemeAndNameAndHeight.split('/');
254 if (themeAndNameList.size() != 3) {
255 return nullptr;
256 }
257 themeName = themeAndNameList[0];
258 cursorName = themeAndNameList[1];
260 bool ok;
261 cursorHeight = themeAndNameList[2].toInt(&ok);
262 if (!ok) {
263 cursorHeight = 32;
264 qWarning().nospace() << "CursorImageProvider: invalid cursor height ("<<themeAndNameList[2]<<")."
265 " Falling back to "<<cursorHeight<<" pixels";
266 }
267 }
269 return fetchCursor(themeName, cursorName, cursorHeight);
272CursorImage *CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName, int cursorHeight)
274 CursorImage *cursorImage = fetchCursorHelper(themeName, cursorName, cursorHeight);
276 // Try some fallbacks
277 if (cursorImage->qimage.isNull()) {
278 if (m_fallbackNames.contains(cursorName)) {
279 const QStringList &fallbackNames = m_fallbackNames[cursorName];
280 int i = 0;
281 while (cursorImage->qimage.isNull() && i < fallbackNames.count()) {
282 qDebug().nospace() << "CursorImageProvider: "<< cursorName <<" not found, trying " << fallbackNames.at(i);
283 cursorImage = fetchCursorHelper(themeName, fallbackNames.at(i), cursorHeight);
284 ++i;
285 }
286 }
287 }
289 // if it all fails, there must be at least a left_ptr
290 if (cursorImage->qimage.isNull() && cursorName != QLatin1String("left_ptr")) {
291 qDebug() << "CursorImageProvider:" << cursorName
292 << "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
293 cursorImage = fetchCursorHelper(themeName, QStringLiteral("left_ptr"), cursorHeight);
294 }
296 if (cursorImage->qimage.isNull()) {
297 // finally, go for the built-in cursor
298 qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
299 if (!m_builtInCursorImage || m_builtInCursorImage->requestedHeight != cursorHeight) {
300 m_builtInCursorImage.reset(new BuiltInCursorImage(cursorHeight));
301 }
302 cursorImage = m_builtInCursorImage.data();
303 }
305 return cursorImage;
308CursorImage *CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName, int cursorHeight)
310 if (cursorName == QLatin1String("blank")) {
311 return &m_blankCursorImage;
312 } else if (cursorName.startsWith(QLatin1String("custom"))) {
313 return m_customCursorImage.data();
314 } else {
315 QMap<QString, CursorImage*> &themeCursors = m_cursors[themeName];
317 if (!themeCursors.contains(cursorName)) {
318 themeCursors[cursorName] = new XCursorImage(themeName, cursorName, cursorHeight);
319 } else if (themeCursors[cursorName]->requestedHeight != cursorHeight) {
320 delete themeCursors.take(cursorName);
321 themeCursors[cursorName] = new XCursorImage(themeName, cursorName, cursorHeight);
322 }
324 return themeCursors[cursorName];
325 }
328void CursorImageProvider::setCustomCursor(const QCursor &customCursor)
330 if (customCursor.pixmap().isNull()) {
331 m_customCursorImage.reset();
332 } else {
333 m_customCursorImage.reset(new CustomCursorImage(customCursor));
334 }