/* * 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 . */ #include "CursorImageProvider.h" #include #include #include #include #include CursorImageProvider *CursorImageProvider::m_instance = nullptr; ///// // BuiltInCursorImage BuiltInCursorImage::BuiltInCursorImage(int cursorHeight) { const char *svgString = "" "" " " ""; // 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("<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")); } CursorImageProvider::~CursorImageProvider() { { QList< QMap > cursorList = m_cursors.values(); for (int i = 0; i < cursorList.count(); ++i) { QList cursorImageList = cursorList[i].values(); for (int j = 0; j < cursorImageList.count(); ++j) { delete cursorImageList[j]; } } } m_cursors.clear(); m_instance = nullptr; } QImage CursorImageProvider::requestImage(const QString &cursorThemeAndNameAndHeight, QSize *size, const QSize & /*requestedSize*/) { CursorImage *cursorImage = fetchCursor(cursorThemeAndNameAndHeight); size->setWidth(cursorImage->qimage.width()); size->setHeight(cursorImage->qimage.height()); return cursorImage->qimage; } 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 ("<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.data(); } return cursorImage; } 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.data(); } else { QMap &themeCursors = m_cursors[themeName]; if (!themeCursors.contains(cursorName)) { themeCursors[cursorName] = new XCursorImage(themeName, cursorName, cursorHeight); } else if (themeCursors[cursorName]->requestedHeight != cursorHeight) { delete themeCursors.take(cursorName); themeCursors[cursorName] = 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)); } }