Loading...
Searching...
No Matches
CursorImageProvider.cpp
/*
* Copyright (C) 2015-2016 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
* SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CursorImageProvider.h"
#include <QCursor>
#include <QDebug>
#include <QFile>
#include <QPainter>
#include <QSvgRenderer>
CursorImageProvider *CursorImageProvider::m_instance = nullptr;
// BuiltInCursorImage
BuiltInCursorImage::BuiltInCursorImage(int cursorHeight)
{
const char *svgString =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
"<svg"
" xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
" xmlns:svg=\"http://www.w3.org/2000/svg\""
" xmlns=\"http://www.w3.org/2000/svg\""
" version=\"1.1\">"
" <path"
" 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\""
" 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\" />"
"</svg>";
// NB: Original image dimension is 20x32. Ensure aspect ratio is kept
qimage = QImage((20./32.)*cursorHeight, cursorHeight, QImage::Format_ARGB32);
qimage.fill(Qt::transparent);
QPainter imagePainter(&qimage);
frameWidth = qimage.width();
frameHeight = qimage.height();
requestedHeight = cursorHeight;
QSvgRenderer *svgRenderer = new QSvgRenderer(QByteArray(svgString));
svgRenderer->render(&imagePainter);
delete svgRenderer;
}
// BlankCursorImage
BlankCursorImage::BlankCursorImage()
{
qimage = QImage(1, 1, QImage::Format_ARGB32);
qimage.fill(Qt::transparent);
frameWidth = qimage.width();
frameHeight = qimage.height();
}
// CustomCursorImage
CustomCursorImage::CustomCursorImage(const QCursor &cursor)
{
qimage = cursor.pixmap().toImage();
hotspot = cursor.hotSpot();
frameWidth = qimage.width();
frameHeight = qimage.height();
}
// XCursorImage
XCursorImage::XCursorImage(const QString &theme, const QString &file, int preferredCursorHeightPx)
{
requestedHeight = preferredCursorHeightPx;
XcursorImages *xcursorImages = XcursorLibraryLoadImages(QFile::encodeName(file), QFile::encodeName(theme),
preferredCursorHeightPx);
if (!xcursorImages || xcursorImages->nimage == 0) {
return;
}
frameCount = xcursorImages->nimage;
for (int i = 0; i < xcursorImages->nimage; ++i) {
XcursorImage *xcursorImage = xcursorImages->images[i];
if (frameWidth < (int)xcursorImage->width) {
frameWidth = xcursorImage->width;
}
if (frameHeight < (int)xcursorImage->height) {
frameHeight = xcursorImage->height;
}
if (i == 0) {
frameDuration = (int)xcursorImage->delay;
} else {
if (frameDuration != (int)xcursorImage->delay) {
qWarning().nospace() << "CursorImageProvider: XCursorImage("<<theme<<","<<file<<") has"
" varying delays in its animation. Animation won't look right.";
}
}
}
{
// Assume that the hotspot position does not animate
XcursorImage *xcursorImage = xcursorImages->images[0];
hotspot.setX(xcursorImage->xhot);
hotspot.setY(xcursorImage->yhot);
}
// Build the sprite as a single row of frames
qimage = QImage(frameWidth*frameCount, frameHeight, QImage::Format_ARGB32);
qimage.fill(Qt::transparent);
{
QPainter painter(&qimage);
for (int i = 0; i < xcursorImages->nimage; ++i) {
XcursorImage *xcursorImage = xcursorImages->images[i];
auto frameImage = QImage((uchar*)xcursorImage->pixels,
xcursorImage->width, xcursorImage->height, QImage::Format_ARGB32);
painter.drawImage(QPoint(i*frameWidth, 0), frameImage);
}
}
XcursorImagesDestroy(xcursorImages);
}
XCursorImage::~XCursorImage()
{
}
// CursorImageProvider
CursorImageProvider::CursorImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
if (m_instance) {
qFatal("Cannot have multiple CursorImageProvider instances");
}
m_instance = this;
m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("grabbing"));
m_fallbackNames[QStringLiteral("closedhand")].append(QStringLiteral("dnd-none"));
m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("dnd-none"));
m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("grabbing"));
m_fallbackNames[QStringLiteral("dnd-copy")].append(QStringLiteral("closedhand"));
m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("dnd-none"));
m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("grabbing"));
m_fallbackNames[QStringLiteral("dnd-move")].append(QStringLiteral("closedhand"));
m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("dnd-none"));
m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("grabbing"));
m_fallbackNames[QStringLiteral("dnd-link")].append(QStringLiteral("closedhand"));
m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("crossed_circle")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("not-allowed"));
m_fallbackNames[QStringLiteral("forbidden")].append(QStringLiteral("circle"));
m_fallbackNames[QStringLiteral("grabbing")].append(QStringLiteral("closedhand")); // Breeze
m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointing_hand"));
m_fallbackNames[QStringLiteral("hand")].append(QStringLiteral("pointer"));
m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("xterm"));
m_fallbackNames[QStringLiteral("ibeam")].append(QStringLiteral("text"));
m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("default"));
m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("top_left_arrow"));
m_fallbackNames[QStringLiteral("left_ptr")].append(QStringLiteral("left_arrow"));
m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("half-busy"));
m_fallbackNames[QStringLiteral("left_ptr_watch")].append(QStringLiteral("progress"));
m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("fd_double_arrow"));
m_fallbackNames[QStringLiteral("size_bdiag")].append(QStringLiteral("nesw-resize"));
m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("bd_double_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("size_fdiag")].append(QStringLiteral("nwse-resize"));
m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("ew-resize"));
m_fallbackNames[QStringLiteral("size_hor")].append(QStringLiteral("h_double_arrow"));
m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("ns-resize"));
m_fallbackNames[QStringLiteral("size_ver")].append(QStringLiteral("v_double_arrow"));
m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("sb_h_double_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("split_h")].append(QStringLiteral("col-resize"));
m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("sb_v_double_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("split_v")].append(QStringLiteral("row-resize"));
m_fallbackNames[QStringLiteral("up_arrow")].append(QStringLiteral("sb_up_arrow")); // DMZ-White and DMZ-Black themes
m_fallbackNames[QStringLiteral("watch")].append(QStringLiteral("wait"));
m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("left_ptr_help"));
m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("help"));
m_fallbackNames[QStringLiteral("whats_this")].append(QStringLiteral("question_arrow"));
m_fallbackNames[QStringLiteral("xterm")].append(QStringLiteral("ibeam"));
m_blankCursorImage.reset(new BlankCursorImage());
}
CursorImageProvider::~CursorImageProvider()
{
{
auto cursorList = m_cursors.values();
for (int i = 0; i < cursorList.count(); ++i) {
QList<QSharedPointer<CursorImage>> cursorImageList = cursorList[i].values();
for (int j = 0; j < cursorImageList.count(); ++j) {
cursorImageList[j].reset();
}
}
}
m_cursors.clear();
m_instance = nullptr;
}
QImage CursorImageProvider::requestImage(const QString &cursorThemeAndNameAndHeight, QSize *size, const QSize & /*requestedSize*/)
{
QSharedPointer<CursorImage> cursorImage = fetchCursor(cursorThemeAndNameAndHeight);
size->setWidth(cursorImage->qimage.width());
size->setHeight(cursorImage->qimage.height());
return cursorImage->qimage;
}
QSharedPointer<CursorImage> CursorImageProvider::fetchCursor(const QString &cursorThemeAndNameAndHeight)
{
QString themeName;
QString cursorName;
int cursorHeight;
{
QStringList themeAndNameList = cursorThemeAndNameAndHeight.split('/');
if (themeAndNameList.size() != 3) {
return nullptr;
}
themeName = themeAndNameList[0];
cursorName = themeAndNameList[1];
bool ok;
cursorHeight = themeAndNameList[2].toInt(&ok);
if (!ok) {
cursorHeight = 32;
qWarning().nospace() << "CursorImageProvider: invalid cursor height ("<<themeAndNameList[2]<<")."
" Falling back to "<<cursorHeight<<" pixels";
}
}
return fetchCursor(themeName, cursorName, cursorHeight);
}
QSharedPointer<CursorImage> CursorImageProvider::fetchCursor(const QString &themeName, const QString &cursorName, int cursorHeight)
{
QSharedPointer<CursorImage> cursorImage = fetchCursorHelper(themeName, cursorName, cursorHeight);
// Try some fallbacks
if (cursorImage->qimage.isNull()) {
if (m_fallbackNames.contains(cursorName)) {
const QStringList &fallbackNames = m_fallbackNames[cursorName];
int i = 0;
while (cursorImage->qimage.isNull() && i < fallbackNames.count()) {
qDebug().nospace() << "CursorImageProvider: "<< cursorName <<" not found, trying " << fallbackNames.at(i);
cursorImage = fetchCursorHelper(themeName, fallbackNames.at(i), cursorHeight);
++i;
}
}
}
// if it all fails, there must be at least a left_ptr
if (cursorImage->qimage.isNull() && cursorName != QLatin1String("left_ptr")) {
qDebug() << "CursorImageProvider:" << cursorName
<< "not found (nor its fallbacks, if any). Going for \"left_ptr\" as a last resort.";
cursorImage = fetchCursorHelper(themeName, QStringLiteral("left_ptr"), cursorHeight);
}
if (cursorImage->qimage.isNull()) {
// finally, go for the built-in cursor
qWarning() << "CursorImageProvider: couldn't find any cursors. Using the built-in one";
if (!m_builtInCursorImage || m_builtInCursorImage->requestedHeight != cursorHeight) {
m_builtInCursorImage.reset(new BuiltInCursorImage(cursorHeight));
}
cursorImage = m_builtInCursorImage;
}
return cursorImage;
}
QSharedPointer<CursorImage> CursorImageProvider::fetchCursorHelper(const QString &themeName, const QString &cursorName, int cursorHeight)
{
if (cursorName == QLatin1String("blank")) {
return m_blankCursorImage;
} else if (cursorName.startsWith(QLatin1String("custom"))) {
return m_customCursorImage;
} else {
QMap<QString, QSharedPointer<CursorImage>> &themeCursors = m_cursors[themeName];
if (!themeCursors.contains(cursorName)) {
themeCursors[cursorName] = QSharedPointer<CursorImage>(new XCursorImage(themeName, cursorName, cursorHeight));
} else if (themeCursors[cursorName]->requestedHeight != cursorHeight) {
themeCursors.take(cursorName).reset();
themeCursors[cursorName] = QSharedPointer<CursorImage>(new XCursorImage(themeName, cursorName, cursorHeight));
}
return themeCursors[cursorName];
}
}
void CursorImageProvider::setCustomCursor(const QCursor &customCursor)
{
if (customCursor.pixmap().isNull()) {
m_customCursorImage.reset();
} else {
m_customCursorImage.reset(new CustomCursorImage(customCursor));
}
}