diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ add_subdirectory(attica-kde) add_subdirectory(imports/activitymanager/) add_subdirectory(solid-device-automounter) +install(DIRECTORY kcmcontrols/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kcmcontrols) if(X11_Xkb_FOUND AND XCB_XKB_FOUND) add_subdirectory(kaccess) endif() diff --git a/kcmcontrols/GridDelegate.qml b/kcmcontrols/GridDelegate.qml new file mode 100644 --- /dev/null +++ b/kcmcontrols/GridDelegate.qml @@ -0,0 +1,153 @@ +/* + Copyright (c) 2015 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 as Controls +import QtQuick.Templates 2.2 as T2 +import QtGraphicalEffects 1.0 + +import org.kde.kirigami 2.2 as Kirigami + + +T2.ItemDelegate { + id: delegate + + property string toolTip + property alias thumbnail: thumbnailArea.data + property list actions + + width: view.cellWidth + height: view.cellHeight + hoverEnabled: true + + Rectangle { + id: thumbnail + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: Kirigami.Units.smallSpacing * 2 + } + height: width/1.6 + radius: Kirigami.Units.smallSpacing + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + + color: { + if (delegate.GridView.isCurrentItem) { + return Kirigami.Theme.highlightColor; + } else if (parent.hovered) { + return Qt.tint(Kirigami.Theme.backgroundColor, Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.3)) + } else { + return Kirigami.Theme.backgroundColor + } + } + Behavior on color { + ColorAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.OutQuad + } + } + Rectangle { + id: thumbnailArea + radius: Kirigami.Units.smallSpacing/2 + anchors { + fill: parent + margins: Kirigami.Units.smallSpacing + } + + color: Kirigami.Theme.backgroundColor + } + + Rectangle { + anchors.fill: parent + visible: actionsRow.children.length > 0 + opacity: delegate.hovered || (actionsScope.focus) ? 1 : 0 + radius: Kirigami.Units.smallSpacing + color: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.4) + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Complementary + + Behavior on opacity { + PropertyAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.OutQuad + } + } + + FocusScope { + id: actionsScope + anchors { + right: parent.right + bottom: parent.bottom + margins: Kirigami.Units.smallSpacing + } + width: actionsRow.width + height: actionsRow.height + RowLayout { + id: actionsRow + + Repeater { + model: delegate.actions + delegate: Controls.ToolButton { + Kirigami.Icon { + anchors.centerIn: parent + width: Kirigami.Units.iconSizes.smallMedium + height: width + source: modelData.iconName + } + activeFocusOnTab: focus || delegate.focus + onClicked: modelData.trigger() + enabled: modelData.enabled + Controls.ToolTip.delay: 1000 + Controls.ToolTip.timeout: 5000 + Controls.ToolTip.visible: hovered + Controls.ToolTip.text: modelData.tooltip + } + } + } + } + } + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 0 + verticalOffset: 2 + radius: 10 + samples: 32 + color: Qt.rgba(0, 0, 0, 0.3) + } + } + + Controls.Label { + anchors { + left: parent.left + right: parent.right + top: thumbnail.bottom + topMargin: Kirigami.Units.smallSpacing + } + text: delegate.text + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + Controls.ToolTip.delay: 1000 + Controls.ToolTip.timeout: 5000 + Controls.ToolTip.visible: hovered + Controls.ToolTip.text: toolTip +} diff --git a/kcmcontrols/GridViewPage.qml b/kcmcontrols/GridViewPage.qml new file mode 100644 --- /dev/null +++ b/kcmcontrols/GridViewPage.qml @@ -0,0 +1,70 @@ +/* + Copyright (c) 2017 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 as QtControls +import org.kde.kirigami 2.2 as Kirigami +import org.kde.kcm 1.0 + +Kirigami.Page { + id: root + + title: kcm.name + implicitWidth: Kirigami.Units.gridUnit * 20 + implicitHeight: Kirigami.Units.gridUnit * 20 + + property alias view: view + + topPadding: 0 + leftPadding: 0 + rightPadding: 0 + bottomPadding: footer ? Kirigami.Units.smallSpacing : 0 + + QtControls.ScrollView { + id: scroll + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + width: Math.floor((root.width - Kirigami.Units.gridUnit*2) / view.cellWidth) * view.cellWidth + Kirigami.Units.gridUnit*2 + activeFocusOnTab: false + + GridView { + id: view + anchors { + fill: parent + margins: scroll.background ? 2 : 0 + rightMargin: contentHeight > height ? Kirigami.Units.gridUnit : 2 + } + contentItem.x: Math.round((width - Math.floor(width / cellWidth) * cellWidth) / 2) + clip: true + activeFocusOnTab: true + cellWidth: Kirigami.Units.gridUnit * 10 + cellHeight: cellWidth / 1.6 + Kirigami.Units.gridUnit*2 + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveDuration: 0 + } + //not all styles have background defined + Component.onCompleted: { + background.visible = true; + } + } +} diff --git a/kcmcontrols/qmldir b/kcmcontrols/qmldir new file mode 100644 --- /dev/null +++ b/kcmcontrols/qmldir @@ -0,0 +1,4 @@ +module org.kde.kcmcontrols + +GridDelegate 1.0 GridDelegate.qml +GridViewPage 1.0 GridViewPage.qml diff --git a/kcms/cursortheme/CMakeLists.txt b/kcms/cursortheme/CMakeLists.txt --- a/kcms/cursortheme/CMakeLists.txt +++ b/kcms/cursortheme/CMakeLists.txt @@ -1,22 +1,20 @@ # KI18N Translation Domain for this library -add_definitions(-DTRANSLATION_DOMAIN=\"kcmmousetheme\") +add_definitions(-DTRANSLATION_DOMAIN=\"kcmcursortheme\") + +include_directories( ${LIBUSB_INCLUDE_DIR} ) if(X11_Xcursor_FOUND) set( libnoinst_SRCS - xcursor/themepage.cpp xcursor/thememodel.cpp xcursor/cursortheme.cpp xcursor/xcursortheme.cpp xcursor/previewwidget.cpp - xcursor/itemdelegate.cpp xcursor/sortproxymodel.cpp ../krdb/krdb.cpp ) - ki18n_wrap_ui( libnoinst_SRCS xcursor/themepage.ui ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xcursor/ ) - include_directories( ${KWINDOWSYSTEM_INCLUDE_DIR} ) else() - set( libnoinst_SRCS xcursor/themepage.cpp ) - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xcursor/ ) + set( libnoinst_SRCS core/themepage.cpp ) + include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/core/ ) endif() set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) @@ -32,13 +30,15 @@ target_link_libraries(kcm_cursortheme Qt5::DBus Qt5::X11Extras + Qt5::Quick KF5::Archive KF5::KCMUtils KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::KDELibs4Support KF5::NewStuff + KF5::QuickAddons ${X11_LIBRARIES} XCB::XCB ) @@ -50,11 +50,14 @@ target_link_libraries(kcm_cursortheme ${X11_Xfixes_LIB}) endif () -install(TARGETS kcm_cursortheme DESTINATION ${PLUGIN_INSTALL_DIR} ) +install(TARGETS kcm_cursortheme DESTINATION ${PLUGIN_INSTALL_DIR}/kcms ) +kcoreaddons_desktop_to_json(kcm_cursortheme "kcm_cursortheme.desktop") ########### install files ############### -install( FILES cursortheme.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES kcm_cursortheme.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) install( FILES xcursor/xcursor.knsrc DESTINATION ${CONFIG_INSTALL_DIR} ) +kpackage_install_package(package kcm_cursortheme kcms) + diff --git a/kcms/cursortheme/Messages.sh b/kcms/cursortheme/Messages.sh --- a/kcms/cursortheme/Messages.sh +++ b/kcms/cursortheme/Messages.sh @@ -1,4 +1,4 @@ #! /usr/bin/env bash $EXTRACTRC `find -name \*.ui` >> rc.cpp || exit 11 -$XGETTEXT *.cpp */*.cpp -o $podir/kcmmousetheme.pot +$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_cursortheme.pot rm -f rc.cpp diff --git a/kcms/cursortheme/cursortheme.desktop b/kcms/cursortheme/kcm_cursortheme.desktop rename from kcms/cursortheme/cursortheme.desktop rename to kcms/cursortheme/kcm_cursortheme.desktop --- a/kcms/cursortheme/cursortheme.desktop +++ b/kcms/cursortheme/kcm_cursortheme.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Exec=kcmshell5 cursortheme +Exec=kcmshell5 kcm_cursortheme Icon=edit-select Type=Service @@ -36,19 +36,18 @@ Name[ia]=Thema de cursor Name[id]=Tema Kursor Name[is]=Bendilþema -Name[it]=Tema dei puntatori +Name[it]=Tema dei cursori Name[ja]=カーソルテーマ Name[kk]=Меңзер нақышы Name[km]=ស្បែក​ទស្សន៍ទ្រនិច Name[kn]=ಸ್ಥಳಸೂಚಕ (ಕರ್ಸರ್) ಪರಿಸರವಿನ್ಯಾಸ Name[ko]=커서 테마 -Name[lt]=Žymeklių apipavidalinimas +Name[lt]=Žymeklių tema Name[lv]=Kursora tēma Name[mr]=कर्सर शैली Name[nb]=Pekertema Name[nds]=Wieserutsehn Name[nl]=Cursorthema -Name[nn]=Peikartema Name[pa]=ਕਰਸਰ ਥੀਮ Name[pl]=Zestawy wskaźników Name[pt]=Tema de Cursores @@ -75,33 +74,25 @@ Comment=Mouse Cursor Theme Comment[ar]=سمة مؤشّر الفأرة Comment[bs]=Tema kursora miša -Comment[ca]=Tema de cursor del ratolí -Comment[ca@valencia]=Tema de cursor del ratolí +Comment[ca]=Tema de cursor de ratolí Comment[cs]=Motiv kurzorů myši Comment[da]=Markørtema Comment[de]=Mauszeigerdesign Comment[el]=Θέμα δρομέα ποντικιού Comment[en_GB]=Mouse Cursor Theme Comment[es]=Tema de cursores del ratón -Comment[et]=Hiirekursori teema -Comment[eu]=Sagu-kurtsoreen gaia Comment[fi]=Osoitinteema Comment[fr]=Thème du pointeur de la souris -Comment[gl]=Tema do cursor do rato -Comment[he]=ערכת־נושא למצביעי העכבר +Comment[gl]=Tema do cursor Comment[hu]=Egérkurzor témája Comment[id]=Tema Kursor Tetikus -Comment[is]=Bendilþema fyrir mús -Comment[it]=Tema dei puntatori del mouse -Comment[ja]=マウスカーソルテーマ +Comment[it]=Tema dei cursori del mouse Comment[ko]=마우스 커서 테마 -Comment[lt]=Žymeklių apipavidalinimas +Comment[lt]=Žymeklių tema Comment[mr]=माउस कर्सर शैली Comment[nb]=Musepekertema Comment[nds]=Utsehn vun den Muuswieser Comment[nl]=Muiscursorthema -Comment[nn]=Peikartema -Comment[pa]=ਮਾਊਸ ਕਰਸਰ ਥੀਮ Comment[pl]=Zestawy wskaźników myszy Comment[pt]=Tema de Cursores do Rato Comment[pt_BR]=Tema de cursores do mouse @@ -137,15 +128,14 @@ X-KDE-Keywords[hu]=Egér,Kurzor,Téma,Kurzormegjelenés,Kurzorszín,Kurzortéma,Egértéma,Egérmegjelenés,Egérfelületek,Mutató színek,Mutató megjelenés X-KDE-Keywords[ia]=Mus,Cursor,Thema,Apparentia,Cursor,Color,Thema de Cursor,Thema de Mus, Apparentia de Mus,Pelles de Mus,Colores de punctator,Apparentia de punctator X-KDE-Keywords[id]=Tetikus,Kursor,Tema,Tampilan Kursor,Warna Kursor,Tema Kursor,Tema Tetikus,Tampilan Tetikus,Kulit Tetikus,Warna Penunjuk,Tampilan Penunjuk -X-KDE-Keywords[it]=Mouse,Puntatore,Aspetto puntatore,Colore puntatore,Tema puntatore,Tema mouse,Aspetto mouse,Skin mouse,Colore puntatore,Aspetto puntatore +X-KDE-Keywords[it]=Mouse,Cursore,Aspetto cursore,Colore cursore,Tema cursore,Tema mouse,Aspetto mouse,Skin mouse,Colore puntatore,Aspetto puntatore X-KDE-Keywords[kk]=Mouse,Cursor,Theme,Cursor Appearance,Cursor Color,Cursor Theme,Mouse Theme,Mouse Appearance,Mouse Skins,Pointer Colors,Pointer Appearance X-KDE-Keywords[km]=Mouse,Cursor,Theme,Cursor Appearance,Cursor Color,Cursor Theme,Mouse Theme,Mouse Appearance,Mouse Skins,Pointer Colors,Pointer Appearance X-KDE-Keywords[ko]=Mouse,Cursor,Theme,Cursor Appearance,Cursor Color,Cursor Theme,Mouse Theme,Mouse Appearance,Mouse Skins,Pointer Colors,Pointer Appearance,마우스,커서,커서 테마,포인터 X-KDE-Keywords[mr]=माऊस, कर्सर, थीम, कर्सर, अपिरिअन्स, कर्सर, कलर, कर्सर थीम, माऊस थीम, माऊस अपिरिअन्स, माऊस स्कीन्स, पॉईटर अपिरिअन्स X-KDE-Keywords[nb]=Mus,peker,tema,pekerutseende,pekerfarge,pekertema,musetema,musutseende,museskins,pekerfarger,pekeerutseende X-KDE-Keywords[nds]=Muus,Wieser,Muster, Wieserutsehn,Klöör,Utsehn X-KDE-Keywords[nl]=Muis,Cursor,Thema,Uiterlijk van cursor,kleur van cursor,Thema van cursor,Thema van muis,uiterlijk van muis,Muisoppervlak,Kleuren van aanwijzer,Uiterlijk van aanwijzer -X-KDE-Keywords[nn]=mus,peikar,tema,peikarutsjånad,peikarfarge,peikartema,musetema,musutsjånad,musedrakt,peikarfargar,peikarutsjånad X-KDE-Keywords[pl]=Mysz,Kursor,Motyw,Wygląd kursora,Kolor kursora,Motyw kursora,Motyw myszy,Wygląd myszy,Skórki myszy,Kolory wskaźnika,Wygląd wskaźnika X-KDE-Keywords[pt]=Rato,Cursor,Tema,Aparência do Cursor,Cor do Cursor,Tema do Cursor,Tema do Rato,Aparência do Rato,Visuais do Rato,Cores do Cursor X-KDE-Keywords[pt_BR]=Mouse,Cursor,Tema,Aparência do cursor,Cor do cursor,Tema do cursor,Tema do mouse,Aparência do mouse,Visuais do mouse,Cores do ponteiro,Aparência do ponteiro diff --git a/kcms/cursortheme/kcmcursortheme.h b/kcms/cursortheme/kcmcursortheme.h --- a/kcms/cursortheme/kcmcursortheme.h +++ b/kcms/cursortheme/kcmcursortheme.h @@ -19,24 +19,112 @@ #ifndef KCMCURSORTHEME_H #define KCMCURSORTHEME_H -#include -#include "themepage.h" +#include -class CursorThemeConfig : public KCModule +class QStandardItemModel; + +class CursorThemeModel; +class SortProxyModel; +class CursorTheme; + +class CursorThemeConfig : public KQuickAddons::ConfigModule { Q_OBJECT + Q_PROPERTY(bool canInstall READ canInstall WRITE setCanInstall NOTIFY canInstallChanged) + Q_PROPERTY(bool canResize READ canResize WRITE setCanResize NOTIFY canResizeChanged) + Q_PROPERTY(bool canConfigure READ canConfigure WRITE setCanConfigure NOTIFY canConfigureChanged) + Q_PROPERTY(QAbstractItemModel *cursorsModel READ cursorsModel CONSTANT) + Q_PROPERTY(QAbstractItemModel *sizesModel READ sizesModel CONSTANT) + Q_PROPERTY(int selectedThemeRow READ selectedThemeRow WRITE setSelectedThemeRow NOTIFY selectedThemeRowChanged) + Q_PROPERTY(int preferredSize READ preferredSize WRITE setPreferredSize NOTIFY preferredSizeChanged) public: - CursorThemeConfig(QWidget *parent, const QVariantList &); + CursorThemeConfig(QObject *parent, const QVariantList &); ~CursorThemeConfig(); public: void load() Q_DECL_OVERRIDE; void save() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; + //for QML properties + bool canInstall() const; + void setCanInstall(bool can); + + bool canResize() const; + void setCanResize(bool can); + + bool canConfigure() const; + void setCanConfigure(bool can); + + int selectedThemeRow() const; + void setSelectedThemeRow(int row); + + /** @returns 0 if in the UI "automatic size" is selected, otherwise + returns the custom size. */ + int preferredSize() const; + void setPreferredSize(int size); + + QAbstractItemModel *cursorsModel(); + QAbstractItemModel *sizesModel(); + +Q_SIGNALS: + void canInstallChanged(); + void canResizeChanged(); + void canConfigureChanged(); + void selectedThemeRowChanged(); + void preferredSizeChanged(); + +public Q_SLOTS: + void getNewClicked(); + void installClicked(); + void removeTheme(int row); + +private Q_SLOTS: + void selectionChanged(); + /** Updates the size combo box. It loads the size list of the selected cursor + theme with the corresponding icons and chooses an appropriate entry. It + enables the combo box and the label if the theme provides more than one + size, otherwise it disables it. If the size setting is looked in kiosk + mode, it stays always disabled. */ + void updateSizeComboBox(); + + private: - ThemePage *themepage; + QModelIndex selectedIndex() const; + bool installThemes(const QString &file); + /** Applies a given theme, using XFixes, XCursor and KGlobalSettings. + @param theme The cursor theme to be applied. It is save to pass 0 here + (will result in \e false as return value). + @param size The size hint that is used to select the cursor size. + @returns If the changes could be applied. Will return \e false if \e theme is + 0 or if the XFixes and XCursor libraries aren't available in the required + version, otherwise returns \e true. */ + bool applyTheme(const CursorTheme *theme, const int size); + bool iconsIsWritable() const; + + + CursorThemeModel *m_model; + SortProxyModel *m_proxyModel; + QStandardItemModel *m_sizesModel; + + int m_appliedSize; + // This index refers to the CursorThemeModel, not the proxy or the view + QPersistentModelIndex m_appliedIndex; + +/** Holds the last size that was choosen by the user. Example: The user chooses + theme1 which provides the sizes 24 and 36. He chooses 36. preferredSize gets + set to 36. Now, he switchs to theme2 which provides the sizes 30 and 40. + preferredSize still is 36, so the UI will default to 40, which is next to 36. + Now, he chooses theme3 which provides the sizes 34 and 44. preferredSize is + still 36, so the UI defaults to 34. Now the user changes manually to 44. This + will also change preferredSize. */ + int m_preferredSize; + + int m_selectedThemeRow; + bool m_canInstall; + bool m_canResize; + bool m_canConfigure; }; #endif diff --git a/kcms/cursortheme/kcmcursortheme.cpp b/kcms/cursortheme/kcmcursortheme.cpp --- a/kcms/cursortheme/kcmcursortheme.cpp +++ b/kcms/cursortheme/kcmcursortheme.cpp @@ -16,57 +16,579 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include + #include "kcmcursortheme.h" +#include "xcursor/thememodel.h" +#include "xcursor/sortproxymodel.h" +#include "xcursor/cursortheme.h" +#include "xcursor/previewwidget.h" +#include "../krdb/krdb.h" + #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include -K_PLUGIN_FACTORY(CursorThemeConfigFactory, - registerPlugin(); -) -K_EXPORT_PLUGIN(CursorThemeConfigFactory("kcm_cursortheme", "kcminput")) +#include +#include +#include +#include -CursorThemeConfig::CursorThemeConfig(QWidget *parent, const QVariantList &args) - : KCModule(parent, args) -{ - QLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); +#include + +#ifdef HAVE_XFIXES +# include +#endif + +K_PLUGIN_FACTORY_WITH_JSON(CursorThemeConfigFactory, "kcm_cursortheme.json", registerPlugin();) - themepage = new ThemePage(this); - connect(themepage, SIGNAL(changed(bool)), SLOT(changed())); - layout->addWidget(themepage); +CursorThemeConfig::CursorThemeConfig(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args), + m_appliedSize(0), + m_preferredSize(0), + m_selectedThemeRow(-1), + m_canInstall(true), + m_canResize(true), + m_canConfigure(true) +{ + qmlRegisterType("org.kde.private.kcm_cursortheme", 1, 0, "PreviewWidget"); + qmlRegisterType(); KAboutData* aboutData = new KAboutData(QStringLiteral("kcm_cursortheme"), i18n("Cursor Theme"), - QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(c) 2003-2007 Fredrik Höglund")); + QStringLiteral("1.0"), QString(i18n("Mouse Cursor Theme settings")), KAboutLicense::GPL, i18n("(c) 2003-2007 Fredrik Höglund")); aboutData->addAuthor(i18n("Fredrik Höglund")); + aboutData->addAuthor(i18n("Marco Martin")); setAboutData(aboutData); + + m_model = new CursorThemeModel(this); + + m_proxyModel = new SortProxyModel(this); + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseSensitive); + m_proxyModel->sort(NameColumn, Qt::AscendingOrder); + + m_sizesModel = new QStandardItemModel(this); + + // Disable the install button if we can't install new themes to ~/.icons, + // or Xcursor isn't set up to look for cursor themes there. + if (!m_model->searchPaths().contains(QDir::homePath() + "/.icons") || !iconsIsWritable()) { + setCanInstall(false); + } } CursorThemeConfig::~CursorThemeConfig() { /* */ } -void CursorThemeConfig::load() +void CursorThemeConfig::setCanInstall(bool can) +{ + if (m_canInstall == can) { + return; + } + + m_canInstall = can; + emit canInstallChanged(); +} + +bool CursorThemeConfig::canInstall() const +{ + return m_canInstall; +} + +void CursorThemeConfig::setCanResize(bool can) +{ + if (m_canResize == can) { + return; + } + + m_canResize = can; + emit canResizeChanged(); +} + +bool CursorThemeConfig::canResize() const +{ + return m_canResize; +} + +void CursorThemeConfig::setCanConfigure(bool can) +{ + if (m_canConfigure == can) { + return; + } + + m_canConfigure = can; + emit canConfigureChanged(); +} + +bool CursorThemeConfig::canConfigure() const +{ + return m_canConfigure; +} + +void CursorThemeConfig::setSelectedThemeRow(int row) +{ + if (m_selectedThemeRow == row) { + return; + } + + m_selectedThemeRow = row; + emit selectedThemeRowChanged(); + setNeedsSave(true); + updateSizeComboBox(); + + QModelIndex selected = selectedIndex(); + + if (selected.isValid()) { + const CursorTheme *theme = m_proxyModel->theme(selected); + } +} + +int CursorThemeConfig::selectedThemeRow() const +{ + return m_selectedThemeRow; +} + +void CursorThemeConfig::setPreferredSize(int size) +{ + if (m_preferredSize == size) { + return; + } + + m_preferredSize = size; + emit preferredSizeChanged(); + setNeedsSave(true); +} + +int CursorThemeConfig::preferredSize() const { - themepage->load(); - emit changed(false); + return m_preferredSize; } + + +QAbstractItemModel *CursorThemeConfig::cursorsModel() +{ + return m_proxyModel; +} + +QAbstractItemModel *CursorThemeConfig::sizesModel() +{ + return m_sizesModel; +} + +bool CursorThemeConfig::iconsIsWritable() const +{ + const QFileInfo icons = QFileInfo(QDir::homePath() + "/.icons"); + const QFileInfo home = QFileInfo(QDir::homePath()); + + return ((icons.exists() && icons.isDir() && icons.isWritable()) || + (!icons.exists() && home.isWritable())); +} + + +void CursorThemeConfig::updateSizeComboBox() +{ + + // clear the combo box + m_sizesModel->clear(); + + // refill the combo box and adopt its icon size + QModelIndex selected = selectedIndex(); + int maxIconWidth = 0; + int maxIconHeight = 0; + if (selected.isValid()) + { + const CursorTheme *theme = m_proxyModel->theme(selected); + const QList sizes = theme->availableSizes(); + QIcon m_icon; + if (sizes.size() > 1) // only refill the combobox if there is more that 1 size + { + int i; + QList comboBoxList; + QPixmap m_pixmap; + + // insert the items + m_pixmap = theme->createIcon(0); + if (m_pixmap.width() > maxIconWidth) + maxIconWidth = m_pixmap.width(); + if (m_pixmap.height() > maxIconHeight) + maxIconHeight = m_pixmap.height(); + QStandardItem *item = new QStandardItem(QIcon(m_pixmap), + i18nc("@item:inlistbox size", "Resolution dependent")); + item->setData(0); + m_sizesModel->appendRow(item); + comboBoxList << 0; + foreach (i, sizes) + { + m_pixmap = theme->createIcon(i); + if (m_pixmap.width() > maxIconWidth) + maxIconWidth = m_pixmap.width(); + if (m_pixmap.height() > maxIconHeight) + maxIconHeight = m_pixmap.height(); + QStandardItem *item = new QStandardItem(QIcon(m_pixmap), QString::number(i)); + item->setData(i); + m_sizesModel->appendRow(item); + comboBoxList << i; + }; + + // select an item + int selectItem = comboBoxList.indexOf(m_preferredSize); + if (selectItem < 0) // m_preferredSize not available for this theme + { + /* Search the value next to m_preferredSize. The first entry (0) + is ignored. (If m_preferredSize would have been 0, then we + would had found it yet. As m_preferredSize is not 0, we won't + default to "automatic size".)*/ + int j; + int distance; + int smallestDistance; + selectItem = 1; + j = comboBoxList.value(selectItem); + smallestDistance = j < m_preferredSize ? m_preferredSize - j : j - m_preferredSize; + for (int i = 2; i < comboBoxList.size(); ++i) + { + j = comboBoxList.value(i); + distance = j < m_preferredSize ? m_preferredSize - j : j - m_preferredSize; + if (distance < smallestDistance || (distance == smallestDistance && j > m_preferredSize)) + { + smallestDistance = distance; + selectItem = i; + }; + } + }; + setPreferredSize(selectItem); + }; + }; + + // enable or disable the combobox + KConfig c("kcminputrc"); + KConfigGroup cg(&c, "Mouse"); + if (cg.isEntryImmutable("cursorSize")) { + setCanResize(false); + } else { + setCanResize(m_sizesModel->rowCount() > 0); + }; + +} + +bool CursorThemeConfig::applyTheme(const CursorTheme *theme, const int size) +{ + // Require the Xcursor version that shipped with X11R6.9 or greater, since + // in previous versions the Xfixes code wasn't enabled due to a bug in the + // build system (freedesktop bug #975). +#if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105 + if (!theme) + return false; + + if (!CursorTheme::haveXfixes()) + return false; + + QByteArray themeName = QFile::encodeName(theme->name()); + + // Set up the proper launch environment for newly started apps + OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), + QStringLiteral("/KLauncher"), + QDBusConnection::sessionBus()); + klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), themeName); + + // Update the Xcursor X resources + runRdb(0); + + // Notify all applications that the cursor theme has changed + KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged); + + // Reload the standard cursors + QStringList names; + + // Qt cursors + names << "left_ptr" << "up_arrow" << "cross" << "wait" + << "left_ptr_watch" << "ibeam" << "size_ver" << "size_hor" + << "size_bdiag" << "size_fdiag" << "size_all" << "split_v" + << "split_h" << "pointing_hand" << "openhand" + << "closedhand" << "forbidden" << "whats_this" << "copy" << "move" << "link"; + + // X core cursors + names << "X_cursor" << "right_ptr" << "hand1" + << "hand2" << "watch" << "xterm" + << "crosshair" << "left_ptr_watch" << "center_ptr" + << "sb_h_double_arrow" << "sb_v_double_arrow" << "fleur" + << "top_left_corner" << "top_side" << "top_right_corner" + << "right_side" << "bottom_right_corner" << "bottom_side" + << "bottom_left_corner" << "left_side" << "question_arrow" + << "pirate"; + + foreach (const QString &name, names) + { + XFixesChangeCursorByName(QX11Info::display(), theme->loadCursor(name, size), QFile::encodeName(name)); + } + + return true; +#else + Q_UNUSED(theme) + return false; +#endif +} + + void CursorThemeConfig::save() { - themepage->save(); - emit changed(false); + const CursorTheme *theme = selectedIndex().isValid() ? m_proxyModel->theme(selectedIndex()) : NULL; + + KConfig config("kcminputrc"); + KConfigGroup c(&config, "Mouse"); + if (theme) + { + c.writeEntry("cursorTheme", theme->name()); + }; + c.writeEntry("cursorSize", m_preferredSize); + c.sync(); + + if (!applyTheme(theme, m_preferredSize)) + { + KMessageBox::information(0, + i18n("You have to restart KDE for these changes to take effect."), + i18n("Cursor Settings Changed"), "CursorSettingsChanged"); + } + + m_appliedIndex = selectedIndex(); + m_appliedSize = m_preferredSize; + setNeedsSave(false); } + +void CursorThemeConfig::load() +{ + + // Get the name of the theme libXcursor currently uses + QString currentTheme; + if (QX11Info::isPlatformX11()) { + currentTheme = XcursorGetTheme(QX11Info::display()); + } + + // Get the name of the theme KDE is configured to use + KConfig c("kcminputrc"); + KConfigGroup cg(&c, "Mouse"); + currentTheme = cg.readEntry("cursorTheme", currentTheme); + + // Find the theme in the listview + if (!currentTheme.isEmpty()) + m_appliedIndex = m_proxyModel->findIndex(currentTheme); + else + m_appliedIndex = m_proxyModel->defaultIndex(); + + // Disable the listview and the buttons if we're in kiosk mode + if (cg.isEntryImmutable("cursorTheme")) + { + setCanConfigure(false); + setCanInstall(false); + } + + // Load cursor size + int size = cg.readEntry("cursorSize", 0); + if (size <= 0) + m_preferredSize = 0; + else + m_preferredSize = size; + updateSizeComboBox(); // This handles also the kiosk mode + + m_appliedSize = size; + + const CursorTheme *theme = m_proxyModel->theme(m_appliedIndex); + + setSelectedThemeRow(m_appliedIndex.row()); + + setNeedsSave(false); +} + + void CursorThemeConfig::defaults() { - themepage->defaults(); - changed(); + QModelIndex defaultIndex = m_proxyModel->findIndex("breeze_cursors"); + setSelectedThemeRow(defaultIndex.row()); + m_preferredSize = 0; + updateSizeComboBox(); + setNeedsSave(true); +} + + +void CursorThemeConfig::selectionChanged() +{ + updateSizeComboBox(); + + setNeedsSave(m_appliedIndex != selectedIndex()); +} + +QModelIndex CursorThemeConfig::selectedIndex() const +{ + return m_proxyModel->index(m_selectedThemeRow, 0); +} + +void CursorThemeConfig::getNewClicked() +{ + KNS3::DownloadDialog dialog("xcursor.knsrc", 0); + if (dialog.exec()) { + KNS3::Entry::List list = dialog.changedEntries(); + if (list.count() > 0) + m_model->refreshList(); + } +} + +void CursorThemeConfig::installClicked() +{ + // Get the URL for the theme we're going to install + QUrl url = KUrlRequesterDialog::getUrl(QUrl(), 0, i18n("Drag or Type Theme URL")); + + if (url.isEmpty()) + return; + + QString tempFile; + if (!KIO::NetAccess::download(url, tempFile, 0)) + { + QString text; + + if (url.isLocalFile()) + text = i18n("Unable to find the cursor theme archive %1.", + url.toDisplayString()); + else + text = i18n("Unable to download the cursor theme archive; " + "please check that the address %1 is correct.", + url.toDisplayString()); + + KMessageBox::sorry(0, text); + return; + } + + if (!installThemes(tempFile)) + KMessageBox::error(0, i18n("The file %1 does not appear to be a valid " + "cursor theme archive.", url.fileName())); + + KIO::NetAccess::removeTempFile(tempFile); +} + + +void CursorThemeConfig::removeTheme(int row) +{ + QModelIndex idx = m_proxyModel->index(row, 0); + if (!idx.isValid()) { + return; + } + + const CursorTheme *theme = m_proxyModel->theme(idx); + + // Don't let the user delete the currently configured theme + if (idx == m_appliedIndex) { + KMessageBox::sorry(0, i18n("You cannot delete the theme you are currently " + "using.
You have to switch to another theme first.
")); + return; + } + + // Get confirmation from the user + QString question = i18n("Are you sure you want to remove the " + "%1 cursor theme?
" + "This will delete all the files installed by this theme.
", + theme->title()); + + int answer = KMessageBox::warningContinueCancel(0, question, + i18n("Confirmation"), KStandardGuiItem::del()); + + if (answer != KMessageBox::Continue) + return; + + // Delete the theme from the harddrive + KIO::del(QUrl::fromLocalFile(theme->path())); // async + + // Remove the theme from the model + m_proxyModel->removeTheme(idx); + + // TODO: + // Since it's possible to substitute cursors in a system theme by adding a local + // theme with the same name, we shouldn't remove the theme from the list if it's + // still available elsewhere. We could add a + // bool CursorThemeModel::tryAddTheme(const QString &name), and call that, but + // since KIO::del() is an asynchronos operation, the theme we're deleting will be + // readded to the list again before KIO has removed it. +} + + +bool CursorThemeConfig::installThemes(const QString &file) +{ + KTar archive(file); + + if (!archive.open(QIODevice::ReadOnly)) + return false; + + const KArchiveDirectory *archiveDir = archive.directory(); + QStringList themeDirs; + + // Extract the dir names of the cursor themes in the archive, and + // append them to themeDirs + foreach(const QString &name, archiveDir->entries()) + { + const KArchiveEntry *entry = archiveDir->entry(name); + if (entry->isDirectory() && entry->name().toLower() != "default") + { + const KArchiveDirectory *dir = static_cast(entry); + if (dir->entry("index.theme") && dir->entry("cursors")) + themeDirs << dir->name(); + } + } + + if (themeDirs.isEmpty()) + return false; + + // The directory we'll install the themes to + QString destDir = QDir::homePath() + "/.icons/"; + QDir().mkpath(destDir); // Make sure the directory exists + + // Process each cursor theme in the archive + foreach (const QString &dirName, themeDirs) + { + QDir dest(destDir + dirName); + if (dest.exists()) + { + QString question = i18n("A theme named %1 already exists in your icon " + "theme folder. Do you want replace it with this one?", dirName); + + int answer = KMessageBox::warningContinueCancel(0, question, + i18n("Overwrite Theme?"), + KStandardGuiItem::overwrite()); + + if (answer != KMessageBox::Continue) + continue; + + // ### If the theme that's being replaced is the current theme, it + // will cause cursor inconsistencies in newly started apps. + } + + // ### Should we check if a theme with the same name exists in a global theme dir? + // If that's the case it will effectively replace it, even though the global theme + // won't be deleted. Checking for this situation is easy, since the global theme + // will be in the listview. Maybe this should never be allowed since it might + // result in strange side effects (from the average users point of view). OTOH + // a user might want to do this 'upgrade' a global theme. + + const KArchiveDirectory *dir = static_cast + (archiveDir->entry(dirName)); + dir->copyTo(dest.path()); + m_model->addTheme(dest); + } + + archive.close(); + return true; } #include "kcmcursortheme.moc" diff --git a/kcms/cursortheme/package/contents/ui/Delegate.qml b/kcms/cursortheme/package/contents/ui/Delegate.qml new file mode 100644 --- /dev/null +++ b/kcms/cursortheme/package/contents/ui/Delegate.qml @@ -0,0 +1,67 @@ +/* + Copyright (c) 2015 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.1 +import QtQuick.Window 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 as Controls +import QtQuick.Templates 2.2 as T2 +import QtGraphicalEffects 1.0 + +import org.kde.kirigami 2.2 as Kirigami + +import org.kde.kcm 1.0 +import org.kde.kcmcontrols 1.0 as KCMControls +import org.kde.private.kcm_cursortheme 1.0 + +KCMControls.GridDelegate { + id: delegate + + text: model.display + toolTip: model.description + + thumbnail: PreviewWidget { + id: previewWidget + //for cursor themes we must ignore the native scaling, + //as they will be rendered by X11/KWin, ignoring whatever Qt + //scaling + width: parent.width * Screen.devicePixelRatio + height: parent.height * Screen.devicePixelRatio + x: Screen.devicePixelRatio % 1 + y: Screen.devicePixelRatio % 1 + transformOrigin: Item.TopLeft + scale: 1 / Screen.devicePixelRatio + themeModel: kcm.cursorsModel + currentIndex: index + currentSize: parseInt(sizeCombo.currentText) !== NaN ? parseInt(sizeCombo.currentText) : 0 + } + + actions: [ + Kirigami.Action { + iconName: "edit-delete" + tooltip: i18n("Remove Theme") + enabled: model.isWritable + onTriggered: kcm.removeTheme(index); + } + ] + + onClicked: { + view.currentIndex = index; + view.forceActiveFocus(); + } +} diff --git a/kcms/cursortheme/package/contents/ui/main.qml b/kcms/cursortheme/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/cursortheme/package/contents/ui/main.qml @@ -0,0 +1,79 @@ +/* + Copyright (c) 2015 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.2 as QtControls +import org.kde.kirigami 2.2 as Kirigami +import org.kde.kcm 1.0 +import org.kde.kcmcontrols 1.0 as KCMControls + +import org.kde.private.kcm_cursortheme 1.0 + +KCMControls.GridViewPage { + + + ConfigModule.quickHelp: i18n("This module lets you configure the mouse cursor theme used.") + + view.model: kcm.cursorsModel + view.delegate: Delegate {} + view.onCurrentIndexChanged: { + kcm.selectedThemeRow = view.currentIndex; + view.positionViewAtIndex(view.currentIndex, view.GridView.Beginning); + } + + Connections { + target: kcm + onSelectedThemeRowChanged: view.currentIndex = kcm.selectedThemeRow; + } + + footer: RowLayout { + //spacer + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + QtControls.Label { + text: i18n("Size:") + } + QtControls.ComboBox { + id: sizeCombo + currentIndex: 0 + onCountChanged: currentIndex = 0 + model: kcm.sizesModel + textRole: "display" + onCurrentTextChanged: { + kcm.preferredSize = parseInt(sizeCombo.currentText) !== NaN ? parseInt(sizeCombo.currentText) : 0 + } + } + QtControls.Button { + // iconName: "document-import" + text: i18n("&Install From File...") + onClicked: kcm.installClicked(); + enabled: kcm.canInstall + } + QtControls.Button { + // iconName: "get-hot-new-stuff" + text: i18n("&Get New Theme...") + onClicked: kcm.getNewClicked(); + enabled: kcm.canInstall + } + } +} + diff --git a/kcms/cursortheme/package/metadata.desktop b/kcms/cursortheme/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/cursortheme/package/metadata.desktop @@ -0,0 +1,19 @@ +[Desktop Entry] +Name=Cursor Theme +Comment=Mouse Cursor Theme +Icon=edit-select +Encoding=UTF-8 +Keywords= +Type=Service +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Marco Martin +X-KDE-PluginInfo-Email=mart@kde.org +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_cursortheme +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml +X-Plasma-RemoteLocation= diff --git a/kcms/cursortheme/xcursor/cursortheme.h b/kcms/cursortheme/xcursor/cursortheme.h --- a/kcms/cursortheme/xcursor/cursortheme.h +++ b/kcms/cursortheme/xcursor/cursortheme.h @@ -50,7 +50,8 @@ enum ItemDataRole { // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) // to define additional roles. - DisplayDetailRole = 0x24A3DAF8 + DisplayDetailRole = 0x24A3DAF8, + IsWritableRole }; CursorTheme() {} @@ -88,6 +89,8 @@ /// the default cursor from the active theme instead. virtual qulonglong loadCursor(const QString &name, int size = 0) const = 0; + virtual int defaultCursorSize() const = 0; + /** Creates the icon returned by @ref icon(). Don't use this function directly but use @ref icon() instead, because @ref icon() caches the icon. diff --git a/kcms/cursortheme/xcursor/cursortheme.cpp b/kcms/cursortheme/xcursor/cursortheme.cpp --- a/kcms/cursortheme/xcursor/cursortheme.cpp +++ b/kcms/cursortheme/xcursor/cursortheme.cpp @@ -38,7 +38,7 @@ { setTitle(title); setDescription(description); - setSample(QStringLiteral("left_ptr")); + setSample("left_ptr"); setIsHidden(false); setIsWritable(false); } @@ -127,8 +127,8 @@ QPixmap pixmap; QImage image = loadImage(sample(), size); - if (image.isNull() && sample() != QLatin1String("left_ptr")) - image = loadImage(QStringLiteral("left_ptr"), size); + if (image.isNull() && sample() != "left_ptr") + image = loadImage("left_ptr", size); if (!image.isNull()) { diff --git a/kcms/cursortheme/xcursor/itemdelegate.h b/kcms/cursortheme/xcursor/itemdelegate.h deleted file mode 100644 --- a/kcms/cursortheme/xcursor/itemdelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2006-2007 Fredrik Höglund - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License version 2 or at your option 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 warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef ITEMDELEGATE_H -#define ITEMDELEGATE_H - -#include - -class QPainter; - -class ItemDelegate : public QAbstractItemDelegate -{ - public: - ItemDelegate(QObject *parent = 0); - ~ItemDelegate(); - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; - - private: - QString firstLine(const QModelIndex &index) const; - QString secondLine(const QModelIndex &index) const; - QPixmap decoration(const QModelIndex &index) const; - QPalette::ColorRole foregroundRole(const QStyleOptionViewItem &option, const QModelIndex &index) const; -}; - -#endif diff --git a/kcms/cursortheme/xcursor/itemdelegate.cpp b/kcms/cursortheme/xcursor/itemdelegate.cpp deleted file mode 100644 --- a/kcms/cursortheme/xcursor/itemdelegate.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright © 2006-2007 Fredrik Höglund - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License version 2 or at your option 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 warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "itemdelegate.h" -#include "cursortheme.h" - -#include -#include -#include - -namespace -{ - const int decorationMargin = 8; -} - - -ItemDelegate::ItemDelegate(QObject *parent) - : QAbstractItemDelegate(parent) -{ -} - - -ItemDelegate::~ItemDelegate() -{ -} - - -QString ItemDelegate::firstLine(const QModelIndex &index) const -{ - if (index.isValid()) - return index.model()->data(index, Qt::DisplayRole).toString(); - - return QString(); -} - - -QString ItemDelegate::secondLine(const QModelIndex &index) const -{ - if (index.isValid()) - return index.model()->data(index, CursorTheme::DisplayDetailRole).toString(); - - return QString(); -} - - -QPixmap ItemDelegate::decoration(const QModelIndex &index) const -{ - if (index.isValid()) - return qvariant_cast(index.model()->data(index, Qt::DecorationRole)); - - return QPixmap(); -} - - -QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - if (!index.isValid()) - return QSize(); - - QFont normalfont = option.font; - QFont boldfont = normalfont; - boldfont.setBold(true); - - // Extract the items we want to measure - QString firstRow = firstLine(index); - QString secondRow = secondLine(index); - - // Compute the height - QFontMetrics fm1(boldfont); - QFontMetrics fm2(normalfont); - int height = fm1.lineSpacing() + fm2.lineSpacing(); - height = qMax(height, option.decorationSize.height()); - - // Compute the text width - int width = fm1.width(firstRow); - width = qMax(width, fm2.width(secondRow)); - - // Add decoration width + margin - width += option.decorationSize.width() + decorationMargin; - - return QSize(width, height + 16); -} - - -QPalette::ColorRole ItemDelegate::foregroundRole(const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - Q_UNUSED(index) - - if (option.state & QStyle::State_Selected) - return QPalette::HighlightedText; - - return QPalette::Text; -} - - -void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - if (!index.isValid()) - return; - - painter->save(); - - QFont normalfont = option.font; - QFont boldfont = normalfont; - boldfont.setBold(true); - - QString firstRow = firstLine(index); - QString secondRow = secondLine(index); - QPixmap pixmap = decoration(index); - - QColor textcol = option.palette.color(foregroundRole(option, index)); - - // Draw the background - QStyleOptionViewItemV4 opt = option; - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); - - // Draw the icon - int x = option.rect.left() + (option.decorationSize.width() - pixmap.width() + decorationMargin) / 2; - int y = option.rect.top() + (option.rect.height() - pixmap.height()) / 2; - - QRect pixmapRect = QStyle::visualRect(option.direction, option.rect, - QRect(x, y, pixmap.width(), pixmap.height())); - - painter->drawPixmap(pixmapRect.x(), pixmapRect.y(), pixmap); - - // Draw the text - QFontMetrics fm1(boldfont); - QFontMetrics fm2(normalfont); - - int textAreaHeight = fm1.lineSpacing(); - if (!secondRow.isEmpty()) { - textAreaHeight += fm2.lineSpacing(); - } - - x = option.rect.left() + option.decorationSize.width() + decorationMargin; - int y1 = option.rect.top() + (option.rect.height() - textAreaHeight) / 2; - int y2 = y1 + fm1.lineSpacing(); - - QRect firstRowRect = QStyle::visualRect(option.direction, option.rect, - QRect(x, y1, fm1.width(firstRow), fm1.lineSpacing())); - - QRect secondRowRect = QStyle::visualRect(option.direction, option.rect, - QRect(x, y2, fm2.width(secondRow), fm2.lineSpacing())); - - painter->setPen(textcol); - - // First line - painter->setFont(boldfont); - painter->drawText(firstRowRect, Qt::AlignCenter, firstRow); - - // Second line - painter->setFont(normalfont); - painter->drawText(secondRowRect, Qt::AlignCenter, secondRow); - - painter->restore(); -} - diff --git a/kcms/cursortheme/xcursor/previewwidget.h b/kcms/cursortheme/xcursor/previewwidget.h --- a/kcms/cursortheme/xcursor/previewwidget.h +++ b/kcms/cursortheme/xcursor/previewwidget.h @@ -19,32 +19,58 @@ #ifndef PREVIEWWIDGET_H #define PREVIEWWIDGET_H -#include +#include +#include +#include "sortproxymodel.h" class CursorTheme; class PreviewCursor; -class PreviewWidget : public QWidget +class PreviewWidget : public QQuickPaintedItem { + Q_OBJECT + Q_PROPERTY(SortProxyModel *themeModel READ themeModel WRITE setThemeModel NOTIFY themeModelChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(int currentSize READ currentSize WRITE setCurrentSize NOTIFY currentSizeChanged) + + public: - PreviewWidget(QWidget *parent); + PreviewWidget(QQuickItem *parent = 0); ~PreviewWidget(); void setTheme(const CursorTheme *theme, const int size); void setUseLables(bool); - QSize sizeHint() const Q_DECL_OVERRIDE; + void updateImplicitSize(); + + void setThemeModel(SortProxyModel *themeModel); + SortProxyModel *themeModel(); + + void setCurrentIndex(int idx); + int currentIndex() const; + + void setCurrentSize(int size); + int currentSize() const; + + Q_SIGNALS: + void themeModelChanged(); + void currentIndexChanged(); + void currentSizeChanged(); protected: - void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; - void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; + void paint(QPainter *); + void hoverMoveEvent(QHoverEvent *event); + void hoverLeaveEvent(QHoverEvent *e); + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; private: void layoutItems(); QList list; const PreviewCursor *current; bool needLayout:1; + QPointer m_themeModel; + int m_currentIndex; + int m_currentSize; }; #endif // PREVIEWWIDGET_H diff --git a/kcms/cursortheme/xcursor/previewwidget.cpp b/kcms/cursortheme/xcursor/previewwidget.cpp --- a/kcms/cursortheme/xcursor/previewwidget.cpp +++ b/kcms/cursortheme/xcursor/previewwidget.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "previewwidget.h" @@ -53,8 +54,8 @@ const int numCursors = 9; // The number of cursors from the above list to be previewed const int cursorSpacing = 20; // Spacing between preview cursors - const int widgetMinWidth = 10; // The minimum width of the preview widget - const int widgetMinHeight = 48; // The minimum height of the preview widget + const qreal widgetMinWidth = 10; // The minimum width of the preview widget + const qreal widgetMinHeight = 48; // The minimum height of the preview widget } @@ -67,21 +68,24 @@ const QPixmap &pixmap() const { return m_pixmap; } int width() const { return m_pixmap.width(); } int height() const { return m_pixmap.height(); } + int boundingSize() const { return m_boundingSize; } inline QRect rect() const; void setPosition( const QPoint &p ) { m_pos = p; } void setPosition( int x, int y ) { m_pos = QPoint(x, y); } QPoint position() const { return m_pos; } operator const uint32_t () const { return m_cursor; } operator const QPixmap& () const { return pixmap(); } private: + int m_boundingSize; QPixmap m_pixmap; uint32_t m_cursor; QPoint m_pos; }; PreviewCursor::PreviewCursor(const CursorTheme *theme, const QString &name, int size) + : m_boundingSize(size > 0 ? size : theme->defaultCursorSize()) { // Create the preview pixmap QImage image = theme->loadImage(name, size); @@ -92,11 +96,7 @@ m_pixmap = QPixmap::fromImage(image); // Load the cursor - if (QX11Info::isPlatformX11()) { - m_cursor = theme->loadCursor(name, size); - } else { - m_cursor = XCB_CURSOR_NONE; - } + m_cursor = theme->loadCursor(name, size); // ### perhaps we should tag the cursor so it doesn't get // replaced when a new theme is applied } @@ -121,9 +121,12 @@ -PreviewWidget::PreviewWidget(QWidget *parent) : QWidget(parent) +PreviewWidget::PreviewWidget(QQuickItem *parent) + : QQuickPaintedItem(parent), + m_currentIndex(-1), + m_currentSize(0) { - setMouseTracking(true); + setAcceptHoverEvents(true); current = NULL; } @@ -134,38 +137,98 @@ list.clear(); } +void PreviewWidget::setThemeModel(SortProxyModel *themeModel) +{ + if (m_themeModel == themeModel) { + return; + } + + m_themeModel = themeModel; + emit themeModelChanged(); +} + +SortProxyModel *PreviewWidget::themeModel() +{ + return m_themeModel; +} + +void PreviewWidget::setCurrentIndex(int idx) +{ + if (m_currentIndex == idx) { + return; + } + + m_currentIndex = idx; + emit currentIndexChanged(); + + if (!m_themeModel) { + return; + } + const CursorTheme *theme = m_themeModel->theme(m_themeModel->index(idx, 0)); + setTheme(theme, m_currentSize); +} + +int PreviewWidget::currentIndex() const +{ + return m_currentIndex; +} + +void PreviewWidget::setCurrentSize(int size) +{ + if (m_currentSize == size) { + return; + } + + m_currentSize = size; + emit currentSizeChanged(); -QSize PreviewWidget::sizeHint() const + if (!m_themeModel) { + return; + } + const CursorTheme *theme = m_themeModel->theme(m_themeModel->index(m_currentIndex, 0)); + setTheme(theme, size); +} + +int PreviewWidget::currentSize() const { - int totalWidth = 0; - int maxHeight = 0; + return m_currentSize; +} + +void PreviewWidget::updateImplicitSize() +{ + qreal totalWidth = 0; + qreal maxHeight = 0; foreach (const PreviewCursor *c, list) { totalWidth += c->width(); - maxHeight = qMax(c->height(), maxHeight); + maxHeight = qMax(c->height(), (int)maxHeight); } totalWidth += (list.count() - 1) * cursorSpacing; maxHeight = qMax(maxHeight, widgetMinHeight); - return QSize(qMax(totalWidth, widgetMinWidth), qMax(height(), maxHeight)); + setImplicitWidth(qMax(totalWidth, widgetMinWidth)); + setImplicitHeight(qMax(height(), maxHeight)); } void PreviewWidget::layoutItems() { if (!list.isEmpty()) { - QSize size = sizeHint(); - int cursorWidth = size.width() / list.count(); - int nextX = (width() - size.width()) / 2; + const int spacing = 12; + int nextX = spacing; + int nextY = spacing; foreach (PreviewCursor *c, list) { - c->setPosition(nextX + (cursorWidth - c->width()) / 2, - (height() - c->height()) / 2); - nextX += cursorWidth; + c->setPosition(nextX, nextY); + nextX += c->boundingSize() + spacing; + if (nextX + c->boundingSize() > width()) { + nextX = spacing; + nextY += c->boundingSize() + spacing; + } } } @@ -184,46 +247,45 @@ list << new PreviewCursor(theme, cursor_names[i], size); needLayout = true; - updateGeometry(); + updateImplicitSize(); } current = NULL; update(); } -void PreviewWidget::paintEvent(QPaintEvent *) +void PreviewWidget::paint(QPainter *painter) { - QPainter p(this); - if (needLayout) layoutItems(); foreach(const PreviewCursor *c, list) { if (c->pixmap().isNull()) continue; - p.drawPixmap(c->position(), *c); + painter->drawPixmap(c->position(), *c); } } -void PreviewWidget::mouseMoveEvent(QMouseEvent *e) +void PreviewWidget::hoverMoveEvent(QHoverEvent *e) { if (needLayout) layoutItems(); - + //FIXME: we can't find an handle to the actual window + //in the case we are in a QQuickWidget, so we can't do the live preview +/* foreach (const PreviewCursor *c, list) { if (c->rect().contains(e->pos())) { if (c != current) { - // TODO: implement for Wayland const uint32_t cursor = *c; - if (QX11Info::isPlatformX11() && (cursor != XCB_CURSOR_NONE)) { - xcb_change_window_attributes(QX11Info::connection(), winId(), XCB_CW_CURSOR, &cursor); + if (QX11Info::isPlatformX11() && (cursor != XCB_CURSOR_NONE) && window()) { + xcb_change_window_attributes(QX11Info::connection(), window()->winId(), XCB_CW_CURSOR, &cursor); } current = c; } @@ -233,12 +295,22 @@ setCursor(Qt::ArrowCursor); current = NULL; + */ } +void PreviewWidget::hoverLeaveEvent(QHoverEvent *e) +{ + if (window()) { + window()->unsetCursor(); + } +} -void PreviewWidget::resizeEvent(QResizeEvent *) +void PreviewWidget::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!list.isEmpty()) + Q_UNUSED(newGeometry) + Q_UNUSED(oldGeometry) + if (!list.isEmpty()) { needLayout = true; + } } diff --git a/kcms/cursortheme/xcursor/sortproxymodel.h b/kcms/cursortheme/xcursor/sortproxymodel.h --- a/kcms/cursortheme/xcursor/sortproxymodel.h +++ b/kcms/cursortheme/xcursor/sortproxymodel.h @@ -34,9 +34,11 @@ */ class SortProxyModel : public QSortFilterProxyModel { + Q_OBJECT public: SortProxyModel(QObject *parent = 0) : QSortFilterProxyModel(parent) {} ~SortProxyModel() {} + QHash roleNames() const; inline const CursorTheme *theme(const QModelIndex &index) const; inline QModelIndex findIndex(const QString &name) const; inline QModelIndex defaultIndex() const; @@ -46,7 +48,7 @@ int compare(const QModelIndex &left, const QModelIndex &right, int role) const; protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; diff --git a/kcms/cursortheme/xcursor/sortproxymodel.cpp b/kcms/cursortheme/xcursor/sortproxymodel.cpp --- a/kcms/cursortheme/xcursor/sortproxymodel.cpp +++ b/kcms/cursortheme/xcursor/sortproxymodel.cpp @@ -21,6 +21,13 @@ #include "sortproxymodel.h" #include "cursortheme.h" +QHash SortProxyModel::roleNames() const +{ + QHash roleNames = QSortFilterProxyModel::roleNames(); + roleNames[CursorTheme::DisplayDetailRole] = "description"; + + return roleNames; +} int SortProxyModel::compare(const QModelIndex &left, const QModelIndex &right, int role) const { diff --git a/kcms/cursortheme/xcursor/thememodel.h b/kcms/cursortheme/xcursor/thememodel.h --- a/kcms/cursortheme/xcursor/thememodel.h +++ b/kcms/cursortheme/xcursor/thememodel.h @@ -62,11 +62,12 @@ public: CursorThemeModel(QObject *parent = 0); ~CursorThemeModel(); - inline int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - inline int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; + QHash roleNames() const; + inline int columnCount(const QModelIndex &parent = QModelIndex()) const; + inline int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QVariant data(const QModelIndex &index, int role) const; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); /// Returns the CursorTheme at @p index. const CursorTheme *theme(const QModelIndex &index); diff --git a/kcms/cursortheme/xcursor/thememodel.cpp b/kcms/cursortheme/xcursor/thememodel.cpp --- a/kcms/cursortheme/xcursor/thememodel.cpp +++ b/kcms/cursortheme/xcursor/thememodel.cpp @@ -1,6 +1,5 @@ /* * Copyright © 2005-2007 Fredrik Höglund - * Copyright © 2016 Jason A. Donenfeld * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public @@ -21,16 +20,24 @@ #include #include #include -#include #include #include #include #include "thememodel.h" #include "xcursortheme.h" +#include #include +// Check for older version +#if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR) +# define XCURSOR_LIB_MAJOR XCURSOR_MAJOR +# define XCURSOR_LIB_MINOR XCURSOR_MINOR +#endif + + + CursorThemeModel::CursorThemeModel(QObject *parent) : QAbstractTableModel(parent) { @@ -43,6 +50,15 @@ list.clear(); } +QHash CursorThemeModel::roleNames() const +{ + QHash roleNames = QAbstractTableModel::roleNames(); + roleNames[CursorTheme::DisplayDetailRole] = "description"; + roleNames[CursorTheme::IsWritableRole] = "isWritable"; + + return roleNames; +} + void CursorThemeModel::refreshList() { beginResetModel(); @@ -108,6 +124,10 @@ if (role == Qt::DecorationRole && index.column() == NameColumn) return theme->icon(); + if (role == CursorTheme::IsWritableRole) { + return theme->isWritable(); + } + return QVariant(); } @@ -160,10 +180,35 @@ if (!baseDirs.isEmpty()) return baseDirs; - baseDirs = QString(XcursorLibraryPath()).split(':', QString::SkipEmptyParts); - std::transform(baseDirs.begin(), baseDirs.end(), baseDirs.begin(), KShell::tildeExpand); - baseDirs.removeDuplicates(); +#if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1 + // These are the default paths Xcursor will scan for cursor themes + QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"); + + // If XCURSOR_PATH is set, use that instead of the default path + char *xcursorPath = std::getenv("XCURSOR_PATH"); + if (xcursorPath) + path = xcursorPath; +#else + // Get the search path from Xcursor + QString path = XcursorLibraryPath(); +#endif + + // Separate the paths + baseDirs = path.split(':', QString::SkipEmptyParts); + + // Remove duplicates + QMutableStringListIterator i(baseDirs); + while (i.hasNext()) + { + const QString path = i.next(); + QMutableStringListIterator j(i); + while (j.hasNext()) + if (j.next() == path) + j.remove(); + } + // Expand all occurrences of ~/ to the home dir + baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/'); return baseDirs; } @@ -194,11 +239,11 @@ continue; // If there's a cursors subdir, we'll assume this is a cursor theme - if (dir.exists(QStringLiteral("cursors"))) + if (dir.exists("cursors")) return true; // If the theme doesn't have an index.theme file, it can't inherit any themes. - if (!dir.exists(QStringLiteral("index.theme"))) + if (!dir.exists("index.theme")) continue; // Open the index.theme file, so we can get the list of inherited themes @@ -238,41 +283,46 @@ } // If there's no cursors subdir, or if it's empty - if (!themeDir.exists(QStringLiteral("cursors")) || QDir(themeDir.path() + "/cursors") + if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors") .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty()) { - if (themeDir.exists(QStringLiteral("index.theme"))) + if (themeDir.exists("index.theme")) { XCursorTheme theme(themeDir); if (!theme.inherits().isEmpty()) defaultName = theme.inherits().at(0); } return true; } - defaultName = QStringLiteral("default"); + defaultName = QLatin1String("default"); return false; } void CursorThemeModel::processThemeDir(const QDir &themeDir) { - bool haveCursors = themeDir.exists(QStringLiteral("cursors")); + bool haveCursors = themeDir.exists("cursors"); // Special case handling of "default", since it's usually either a // symlink to another theme, or an empty theme that inherits another // theme. - if (defaultName.isNull() && themeDir.dirName() == QLatin1String("default")) + if (defaultName.isNull() && themeDir.dirName() == "default") { if (handleDefault(themeDir)) return; } // If the directory doesn't have a cursors subdir and lacks an // index.theme file it can't be a cursor theme. - if (!themeDir.exists(QStringLiteral("index.theme")) && !haveCursors) + if (!themeDir.exists("index.theme") && !haveCursors) return; + static bool isX11 = QX11Info::isPlatformX11(); + if (!isX11) { + // TODO: implement Wayland Cursor Theme support + return; + } // Create a cursor theme object for the theme dir XCursorTheme *theme = new XCursorTheme(themeDir); @@ -331,7 +381,7 @@ // The theme Xcursor will end up using if no theme is configured if (defaultName.isNull() || !hasTheme(defaultName)) - defaultName = QStringLiteral("KDE_Classic"); + defaultName = QLatin1String("KDE_Classic"); } diff --git a/kcms/cursortheme/xcursor/themepage.h b/kcms/cursortheme/xcursor/themepage.h deleted file mode 100644 --- a/kcms/cursortheme/xcursor/themepage.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright © 2003-2007 Fredrik Höglund - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License version 2 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 warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef THEMEPAGE_H -#define THEMEPAGE_H - -#include -#include - -#include "ui_themepage.h" - -class CursorThemeModel; -class SortProxyModel; -class CursorTheme; - -class ThemePage : public QWidget, private Ui::ThemePage -{ - Q_OBJECT - - public: - ThemePage(QWidget* parent = 0); - ~ThemePage(); - - // Called by the KCM - void save(); - void load(); - void defaults(); - - Q_SIGNALS: - void changed(bool); - - private Q_SLOTS: - void selectionChanged(); - /** Updates the preview. If the size has changed, it also emits changed() */ - void sizeChanged(); - /** Sets #preferredSize to the item that is currently selected in sizeComboBox. - If none is selected, it is set to 0. */ - void preferredSizeChanged(); - /** Updates the size combo box. It loads the size list of the selected cursor - theme with the corresponding icons and chooses an appropriate entry. It - enables the combo box and the label if the theme provides more than one - size, otherwise it disables it. If the size setting is looked in kiosk - mode, it stays always disabled. */ - void updateSizeComboBox(); - void getNewClicked(); - void installClicked(); - void removeClicked(); - - private: - /** @returns 0 if in the UI "automatic size" is selected, otherwise - returns the custom size. */ - int selectedSize() const; - /** Holds the last size that was choosen by the user. Example: The user chooses - theme1 which provides the sizes 24 and 36. He chooses 36. preferredSize gets - set to 36. Now, he switchs to theme2 which provides the sizes 30 and 40. - preferredSize still is 36, so the UI will default to 40, which is next to 36. - Now, he chooses theme3 which provides the sizes 34 and 44. preferredSize is - still 36, so the UI defaults to 34. Now the user changes manually to 44. This - will also change preferredSize. */ - int preferredSize; - void updatePreview(); - QModelIndex selectedIndex() const; - bool installThemes(const QString &file); - /** Applies a given theme, using XFixes, XCursor and KGlobalSettings. - @param theme The cursor theme to be applied. It is save to pass 0 here - (will result in \e false as return value). - @param size The size hint that is used to select the cursor size. - @returns If the changes could be applied. Will return \e false if \e theme is - 0 or if the XFixes and XCursor libraries aren't available in the required - version, otherwise returns \e true. */ - bool applyTheme(const CursorTheme *theme, const int size); - bool iconsIsWritable() const; - - CursorThemeModel *model; - SortProxyModel *proxy; - - int appliedSize; - // This index refers to the CursorThemeModel, not the proxy or the view - QPersistentModelIndex appliedIndex; -}; - -#endif // THEMEPAGE_H diff --git a/kcms/cursortheme/xcursor/themepage.cpp b/kcms/cursortheme/xcursor/themepage.cpp deleted file mode 100644 --- a/kcms/cursortheme/xcursor/themepage.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright © 2003-2007 Fredrik Höglund - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License version 2 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 warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -#include - -#include -#include "../../krdb/krdb.h" - -#include -#include -#include -#include - -#include "themepage.h" - -#include "thememodel.h" -#include "itemdelegate.h" -#include "sortproxymodel.h" -#include "cursortheme.h" - -#include -#include - -#ifdef HAVE_XFIXES -# include -#endif - - -ThemePage::ThemePage(QWidget *parent) - : QWidget(parent) -{ - setupUi(this); - installKnsButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); - installButton->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); - removeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); - - model = new CursorThemeModel(this); - - proxy = new SortProxyModel(this); - proxy->setSourceModel(model); - proxy->setFilterCaseSensitivity(Qt::CaseSensitive); - proxy->sort(NameColumn, Qt::AscendingOrder); - - // Get the icon size for QListView - int size = style()->pixelMetric(QStyle::PM_LargeIconSize); - - view->setModel(proxy); - view->setItemDelegate(new ItemDelegate(this)); - view->setIconSize(QSize(size, size)); - view->setSelectionMode(QAbstractItemView::SingleSelection); - - // Make sure we find out about selection changes - connect(view->selectionModel(), - &QItemSelectionModel::selectionChanged, - this, &ThemePage::selectionChanged); - - // Make sure we find out about size changes - connect(sizeComboBox, - SIGNAL(currentIndexChanged(int)), - SLOT(sizeChanged())); - - // Make sure we find out about user activity - connect(sizeComboBox, - SIGNAL(activated(int)), - SLOT(preferredSizeChanged())); - - // Disable the install button if we can't install new themes to ~/.icons, - // or Xcursor isn't set up to look for cursor themes there. - if (!model->searchPaths().contains(QDir::homePath() + "/.icons") || !iconsIsWritable()) { - installButton->setEnabled(false); - installKnsButton->setEnabled(false); - } - - connect(installKnsButton, &QAbstractButton::clicked, this, &ThemePage::getNewClicked); - connect(installButton, &QAbstractButton::clicked, this, &ThemePage::installClicked); - connect(removeButton, &QAbstractButton::clicked, this, &ThemePage::removeClicked); -} - - -ThemePage::~ThemePage() -{ -} - - -bool ThemePage::iconsIsWritable() const -{ - const QFileInfo icons = QFileInfo(QDir::homePath() + "/.icons"); - const QFileInfo home = QFileInfo(QDir::homePath()); - - return ((icons.exists() && icons.isDir() && icons.isWritable()) || - (!icons.exists() && home.isWritable())); -} - - -void ThemePage::updateSizeComboBox() -{ - // clear the combo box - sizeComboBox->clear(); - - // refill the combo box and adopt its icon size - QModelIndex selected = selectedIndex(); - int maxIconWidth = 0; - int maxIconHeight = 0; - if (selected.isValid()) - { - const CursorTheme *theme = proxy->theme(selected); - const QList sizes = theme->availableSizes(); - QIcon m_icon; - if (sizes.size() > 1) // only refill the combobox if there is more that 1 size - { - int i; - QList comboBoxList; - QPixmap m_pixmap; - - // insert the items - m_pixmap = theme->createIcon(0); - if (m_pixmap.width() > maxIconWidth) - maxIconWidth = m_pixmap.width(); - if (m_pixmap.height() > maxIconHeight) - maxIconHeight = m_pixmap.height(); - sizeComboBox->addItem( - QIcon(m_pixmap), - i18nc("@item:inlistbox size", "Resolution dependent"), - 0); - comboBoxList << 0; - foreach (i, sizes) - { - m_pixmap = theme->createIcon(i); - if (m_pixmap.width() > maxIconWidth) - maxIconWidth = m_pixmap.width(); - if (m_pixmap.height() > maxIconHeight) - maxIconHeight = m_pixmap.height(); - sizeComboBox->addItem(QIcon(m_pixmap), QString::number(i), i); - comboBoxList << i; - }; - - // select an item - int selectItem = comboBoxList.indexOf(preferredSize); - if (selectItem < 0) // preferredSize not available for this theme - { - /* Search the value next to preferredSize. The first entry (0) - is ignored. (If preferredSize would have been 0, then we - would had found it yet. As preferredSize is not 0, we won't - default to "automatic size".)*/ - int j; - int distance; - int smallestDistance; - selectItem = 1; - j = comboBoxList.value(selectItem); - smallestDistance = j < preferredSize ? preferredSize - j : j - preferredSize; - for (int i = 2; i < comboBoxList.size(); ++i) - { - j = comboBoxList.value(i); - distance = j < preferredSize ? preferredSize - j : j - preferredSize; - if (distance < smallestDistance || (distance == smallestDistance && j > preferredSize)) - { - smallestDistance = distance; - selectItem = i; - }; - } - }; - sizeComboBox->setCurrentIndex(selectItem); - }; - }; - sizeComboBox->setIconSize(QSize(maxIconWidth, maxIconHeight)); - - // enable or disable the combobox - KConfig c(QStringLiteral("kcminputrc")); - KConfigGroup cg(&c, "Mouse"); - if (cg.isEntryImmutable("cursorSize")) { - sizeComboBox->setEnabled(false); - } else { - sizeComboBox->setEnabled(sizeComboBox->count() > 0); - }; -} - - -int ThemePage::selectedSize() const -{ - if (sizeComboBox->isEnabled() && sizeComboBox->currentIndex() >= 0) - return sizeComboBox->itemData(sizeComboBox->currentIndex(), Qt::UserRole).toInt(); - return 0; -} - - -void ThemePage::updatePreview() -{ - QModelIndex selected = selectedIndex(); - - if (selected.isValid()) { - const CursorTheme *theme = proxy->theme(selected); - preview->setTheme(theme, selectedSize()); - removeButton->setEnabled(theme->isWritable()); - } else { - preview->setTheme(NULL, 0); - removeButton->setEnabled(false); - }; -} - - -bool ThemePage::applyTheme(const CursorTheme *theme, const int size) -{ - // Require the Xcursor version that shipped with X11R6.9 or greater, since - // in previous versions the Xfixes code wasn't enabled due to a bug in the - // build system (freedesktop bug #975). -#if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105 - if (!theme) - return false; - const bool isX11 = QX11Info::isPlatformX11(); - - if (isX11 && !CursorTheme::haveXfixes()) - return false; - - QByteArray themeName = QFile::encodeName(theme->name()); - - // Set up the proper launch environment for newly started apps - OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), - QStringLiteral("/KLauncher"), - QDBusConnection::sessionBus()); - klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), themeName); - - // Update the Xcursor X resources - if (isX11) { - runRdb(0); - } - - // Notify all applications that the cursor theme has changed - KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged); - - if (!isX11) { - // TODO: Wayland clients don't reload the cursor, QtWayland doesn't support it at all - return false; - } - - // Reload the standard cursors - QStringList names; - - // Qt cursors - names << QStringLiteral("left_ptr") << QStringLiteral("up_arrow") << QStringLiteral("cross") << QStringLiteral("wait") - << QStringLiteral("left_ptr_watch") << QStringLiteral("ibeam") << QStringLiteral("size_ver") << QStringLiteral("size_hor") - << QStringLiteral("size_bdiag") << QStringLiteral("size_fdiag") << QStringLiteral("size_all") << QStringLiteral("split_v") - << QStringLiteral("split_h") << QStringLiteral("pointing_hand") << QStringLiteral("openhand") - << QStringLiteral("closedhand") << QStringLiteral("forbidden") << QStringLiteral("whats_this") << QStringLiteral("copy") << QStringLiteral("move") << QStringLiteral("link"); - - // X core cursors - names << QStringLiteral("X_cursor") << QStringLiteral("right_ptr") << QStringLiteral("hand1") - << QStringLiteral("hand2") << QStringLiteral("watch") << QStringLiteral("xterm") - << QStringLiteral("crosshair") << QStringLiteral("left_ptr_watch") << QStringLiteral("center_ptr") - << QStringLiteral("sb_h_double_arrow") << QStringLiteral("sb_v_double_arrow") << QStringLiteral("fleur") - << QStringLiteral("top_left_corner") << QStringLiteral("top_side") << QStringLiteral("top_right_corner") - << QStringLiteral("right_side") << QStringLiteral("bottom_right_corner") << QStringLiteral("bottom_side") - << QStringLiteral("bottom_left_corner") << QStringLiteral("left_side") << QStringLiteral("question_arrow") - << QStringLiteral("pirate"); - - foreach (const QString &name, names) - { - XFixesChangeCursorByName(QX11Info::display(), theme->loadCursor(name, size), QFile::encodeName(name)); - } - - return true; -#else - Q_UNUSED(theme) - return false; -#endif -} - - -void ThemePage::save() -{ - const CursorTheme *theme = selectedIndex().isValid() ? proxy->theme(selectedIndex()) : NULL; - const int size = selectedSize(); - - KConfig config(QStringLiteral("kcminputrc")); - KConfigGroup c(&config, "Mouse"); - if (theme) - { - c.writeEntry("cursorTheme", theme->name()); - }; - c.writeEntry("cursorSize", size); - preferredSize = size; - c.sync(); - - if (!applyTheme(theme, size)) - { - KMessageBox::information(this, - i18n("You have to restart the Plasma session for these changes to take effect."), - i18n("Cursor Settings Changed"), QStringLiteral("CursorSettingsChanged")); - } - - appliedIndex = selectedIndex(); - appliedSize = size; -} - - -void ThemePage::load() -{ - view->selectionModel()->clear(); - // Get the name of the theme libXcursor currently uses - QString currentTheme; - if (QX11Info::isPlatformX11()) { - currentTheme = XcursorGetTheme(QX11Info::display()); - } - - // Get the name of the theme KDE is configured to use - KConfig c(QStringLiteral("kcminputrc")); - KConfigGroup cg(&c, "Mouse"); - currentTheme = cg.readEntry("cursorTheme", currentTheme); - - // Find the theme in the listview - if (!currentTheme.isEmpty()) - appliedIndex = proxy->findIndex(currentTheme); - else - appliedIndex = proxy->defaultIndex(); - - // Disable the listview and the buttons if we're in kiosk mode - if (cg.isEntryImmutable("cursorTheme")) - { - view->setEnabled(false); - installButton->setEnabled(false); - removeButton->setEnabled(false); - } - - // Load cursor size - int size = cg.readEntry("cursorSize", 0); - if (size <= 0) - preferredSize = 0; - else - preferredSize = size; - updateSizeComboBox(); // This handles also the kiosk mode - - appliedSize = size; - - const CursorTheme *theme = proxy->theme(appliedIndex); - - if (appliedIndex.isValid()) - { - // Select the current theme - view->setCurrentIndex(appliedIndex); - view->scrollTo(appliedIndex, QListView::PositionAtCenter); - - // Update the preview widget as well - preview->setTheme(theme, size); - } - - if (!theme || !theme->isWritable()) - removeButton->setEnabled(false); -} - - -void ThemePage::defaults() -{ - view->selectionModel()->clear(); - QModelIndex defaultIndex = proxy->findIndex(QStringLiteral("breeze_cursors")); - view->setCurrentIndex(defaultIndex); - preferredSize = 0; - updateSizeComboBox(); -} - - -void ThemePage::selectionChanged() -{ - updateSizeComboBox(); - updatePreview(); - - emit changed(appliedIndex != selectedIndex()); -} - -QModelIndex ThemePage::selectedIndex() const -{ - QModelIndexList selection = view->selectionModel()->selectedIndexes(); - if (!selection.isEmpty()) { - return (selection.at(0)); - } - return QModelIndex(); -} - -void ThemePage::sizeChanged() -{ - updatePreview(); - emit changed(selectedSize() != appliedSize); -} - -void ThemePage::preferredSizeChanged() -{ - int index = sizeComboBox->currentIndex(); - if (index >= 0) - preferredSize = sizeComboBox->itemData(index, Qt::UserRole).toInt(); - else - preferredSize = 0; -} - -void ThemePage::getNewClicked() -{ - KNS3::DownloadDialog dialog(QStringLiteral("xcursor.knsrc"), this); - if (dialog.exec()) { - KNS3::Entry::List list = dialog.changedEntries(); - if (list.count() > 0) - model->refreshList(); - } -} - -void ThemePage::installClicked() -{ - // Get the URL for the theme we're going to install - QUrl url = KUrlRequesterDialog::getUrl(QUrl(), this, i18n("Drag or Type Theme URL")); - - if (url.isEmpty()) - return; - - QString tempFile; - if (!KIO::NetAccess::download(url, tempFile, this)) - { - QString text; - - if (url.isLocalFile()) - text = i18n("Unable to find the cursor theme archive %1.", - url.toDisplayString()); - else - text = i18n("Unable to download the cursor theme archive; " - "please check that the address %1 is correct.", - url.toDisplayString()); - - KMessageBox::sorry(this, text); - return; - } - - if (!installThemes(tempFile)) - KMessageBox::error(this, i18n("The file %1 does not appear to be a valid " - "cursor theme archive.", url.fileName())); - - KIO::NetAccess::removeTempFile(tempFile); -} - - -void ThemePage::removeClicked() -{ - // We don't have to check if the current index is valid, since - // the remove button will be disabled when there's no selection. - const CursorTheme *theme = proxy->theme(selectedIndex()); - - // Don't let the user delete the currently configured theme - if (selectedIndex() == appliedIndex) { - KMessageBox::sorry(this, i18n("You cannot delete the theme you are currently " - "using.
You have to switch to another theme first.
")); - return; - } - - // Get confirmation from the user - QString question = i18n("Are you sure you want to remove the " - "%1 cursor theme?
" - "This will delete all the files installed by this theme.
", - theme->title()); - - int answer = KMessageBox::warningContinueCancel(this, question, - i18n("Confirmation"), KStandardGuiItem::del()); - - if (answer != KMessageBox::Continue) - return; - - // Delete the theme from the harddrive - KIO::del(QUrl::fromLocalFile(theme->path())); // async - - // Remove the theme from the model - proxy->removeTheme(selectedIndex()); - - // TODO: - // Since it's possible to substitute cursors in a system theme by adding a local - // theme with the same name, we shouldn't remove the theme from the list if it's - // still available elsewhere. We could add a - // bool CursorThemeModel::tryAddTheme(const QString &name), and call that, but - // since KIO::del() is an asynchronos operation, the theme we're deleting will be - // readded to the list again before KIO has removed it. -} - - -bool ThemePage::installThemes(const QString &file) -{ - KTar archive(file); - - if (!archive.open(QIODevice::ReadOnly)) - return false; - - const KArchiveDirectory *archiveDir = archive.directory(); - QStringList themeDirs; - - // Extract the dir names of the cursor themes in the archive, and - // append them to themeDirs - foreach(const QString &name, archiveDir->entries()) - { - const KArchiveEntry *entry = archiveDir->entry(name); - if (entry->isDirectory() && entry->name().toLower() != QLatin1String("default")) - { - const KArchiveDirectory *dir = static_cast(entry); - if (dir->entry(QStringLiteral("index.theme")) && dir->entry(QStringLiteral("cursors"))) - themeDirs << dir->name(); - } - } - - if (themeDirs.isEmpty()) - return false; - - // The directory we'll install the themes to - QString destDir = QDir::homePath() + "/.icons/"; - QDir().mkpath(destDir); // Make sure the directory exists - - // Process each cursor theme in the archive - foreach (const QString &dirName, themeDirs) - { - QDir dest(destDir + dirName); - if (dest.exists()) - { - QString question = i18n("A theme named %1 already exists in your icon " - "theme folder. Do you want replace it with this one?", dirName); - - int answer = KMessageBox::warningContinueCancel(this, question, - i18n("Overwrite Theme?"), - KStandardGuiItem::overwrite()); - - if (answer != KMessageBox::Continue) - continue; - - // ### If the theme that's being replaced is the current theme, it - // will cause cursor inconsistencies in newly started apps. - } - - // ### Should we check if a theme with the same name exists in a global theme dir? - // If that's the case it will effectively replace it, even though the global theme - // won't be deleted. Checking for this situation is easy, since the global theme - // will be in the listview. Maybe this should never be allowed since it might - // result in strange side effects (from the average users point of view). OTOH - // a user might want to do this 'upgrade' a global theme. - - const KArchiveDirectory *dir = static_cast - (archiveDir->entry(dirName)); - dir->copyTo(dest.path()); - model->addTheme(dest); - } - - archive.close(); - return true; -} - diff --git a/kcms/cursortheme/xcursor/themepage.ui b/kcms/cursortheme/xcursor/themepage.ui deleted file mode 100644 --- a/kcms/cursortheme/xcursor/themepage.ui +++ /dev/null @@ -1,114 +0,0 @@ - - - ThemePage - - - - 0 - 0 - 548 - 360 - - - - - - - - 0 - 0 - - - - - 0 - 48 - - - - - - - - - 0 - 1 - - - - true - - - QAbstractItemView::ScrollPerPixel - - - - - - - Get new color schemes from the Internet - - - Get new Theme - - - - - - - Remove Theme - - - - - - - - 0 - 0 - - - - QComboBox::AdjustToContents - - - - 0 - 0 - - - - - - - - Install from File - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - PreviewWidget - QWidget -
previewwidget.h
- 1 -
-
- - -
diff --git a/kcms/cursortheme/xcursor/xcursor.knsrc b/kcms/cursortheme/xcursor/xcursor.knsrc --- a/kcms/cursortheme/xcursor/xcursor.knsrc +++ b/kcms/cursortheme/xcursor/xcursor.knsrc @@ -1,43 +1,5 @@ [KNewStuff3] -Name=Cursors -Name[ca]=Cursors -Name[ca@valencia]=Cursors -Name[cs]=Kurzory -Name[da]=Markører -Name[de]=Zeiger -Name[el]=Δρομείς -Name[en_GB]=Cursors -Name[es]=Cursores -Name[eu]=Kurtsoreak -Name[fi]=Osoittimet -Name[fr]=Pointeurs -Name[gl]=Cursores -Name[he]=מצביעים -Name[hu]=Kurzorok -Name[it]=Puntatori -Name[ko]=커서 -Name[lt]=Žymekliai -Name[nl]=Cursors -Name[nn]=Peikarar -Name[pa]=ਕਰਸਰਾਂ -Name[pl]=Wskaźniki -Name[pt]=Cursores -Name[pt_BR]=Cursores -Name[ru]=Курсоры мыши -Name[sk]=Kurzory -Name[sl]=Kazalke -Name[sr]=Показивачи -Name[sr@ijekavian]=Показивачи -Name[sr@ijekavianlatin]=Pokazivači -Name[sr@latin]=Pokazivači -Name[sv]=Pekare -Name[tr]=İmleçler -Name[uk]=Вказівники -Name[x-test]=xxCursorsxx -Name[zh_CN]=光标 -Name[zh_TW]=游標 - -ProvidersUrl=https://download.kde.org/ocs/providers.xml +ProvidersUrl=http://download.kde.org/ocs/providers.xml Categories=X11 Mouse Theme InstallPath=.icons Uncompress=always diff --git a/kcms/cursortheme/xcursor/xcursortheme.h b/kcms/cursortheme/xcursor/xcursortheme.h --- a/kcms/cursortheme/xcursor/xcursortheme.h +++ b/kcms/cursortheme/xcursor/xcursortheme.h @@ -46,8 +46,12 @@ virtual ~XCursorTheme() {} const QStringList inherits() const { return m_inherits; } - QImage loadImage(const QString &name, int size = 0) const Q_DECL_OVERRIDE; - qulonglong loadCursor(const QString &name, int size = 0) const Q_DECL_OVERRIDE; + QImage loadImage(const QString &name, int size = 0) const; + qulonglong loadCursor(const QString &name, int size = 0) const; + + /** Returns the size that the XCursor library would use if no + cursor size is given. This depends mainly on Xft.dpi. */ + int defaultCursorSize() const; protected: XCursorTheme(const QString &title, const QString &desc) @@ -59,9 +63,7 @@ XcursorImages *xcLoadImages(const QString &name, int size) const; void parseIndexFile(); QString findAlternative(const QString &name) const; - /** Returns the size that the XCursor library would use if no - cursor size is given. This depends mainly on Xft.dpi. */ - int autodetectCursorSize() const; + QStringList m_inherits; static QHash alternatives; diff --git a/kcms/cursortheme/xcursor/xcursortheme.cpp b/kcms/cursortheme/xcursor/xcursortheme.cpp --- a/kcms/cursortheme/xcursor/xcursortheme.cpp +++ b/kcms/cursortheme/xcursor/xcursortheme.cpp @@ -21,17 +21,14 @@ #include #include -#include -#include #include #include #include #include #include #include -#include #include "xcursortheme.h" @@ -46,7 +43,7 @@ setPath(themeDir.path()); setIsWritable(QFileInfo(themeDir.path()).isWritable()); // ### perhaps this shouldn't be cached - if (themeDir.exists(QStringLiteral("index.theme"))) + if (themeDir.exists("index.theme")) parseIndexFile(); QString cursorFile = path() + "/cursors/left_ptr"; @@ -107,27 +104,27 @@ // If Xcursor fails to load the cursor, Qt creates it with the correct name using the // core protcol instead (which in turn calls Xcursor). We emulate that process here. // Note that there's a core cursor called cross, but it's not the one Qt expects. - alternatives.insert(QStringLiteral("cross"), QStringLiteral("crosshair")); - alternatives.insert(QStringLiteral("up_arrow"), QStringLiteral("center_ptr")); - alternatives.insert(QStringLiteral("wait"), QStringLiteral("watch")); - alternatives.insert(QStringLiteral("ibeam"), QStringLiteral("xterm")); - alternatives.insert(QStringLiteral("size_all"), QStringLiteral("fleur")); - alternatives.insert(QStringLiteral("pointing_hand"), QStringLiteral("hand2")); + alternatives.insert("cross", "crosshair"); + alternatives.insert("up_arrow", "center_ptr"); + alternatives.insert("wait", "watch"); + alternatives.insert("ibeam", "xterm"); + alternatives.insert("size_all", "fleur"); + alternatives.insert("pointing_hand", "hand2"); // Precomputed MD5 hashes for the hardcoded bitmap cursors in Qt and KDE. // Note that the MD5 hash for left_ptr_watch is for the KDE version of that cursor. - alternatives.insert(QStringLiteral("size_ver"), QStringLiteral("00008160000006810000408080010102")); - alternatives.insert(QStringLiteral("size_hor"), QStringLiteral("028006030e0e7ebffc7f7070c0600140")); - alternatives.insert(QStringLiteral("size_bdiag"), QStringLiteral("fcf1c3c7cd4491d801f1e1c78f100000")); - alternatives.insert(QStringLiteral("size_fdiag"), QStringLiteral("c7088f0f3e6c8088236ef8e1e3e70000")); - alternatives.insert(QStringLiteral("whats_this"), QStringLiteral("d9ce0ab605698f320427677b458ad60b")); - alternatives.insert(QStringLiteral("split_h"), QStringLiteral("14fef782d02440884392942c11205230")); - alternatives.insert(QStringLiteral("split_v"), QStringLiteral("2870a09082c103050810ffdffffe0204")); - alternatives.insert(QStringLiteral("forbidden"), QStringLiteral("03b6e0fcb3499374a867c041f52298f0")); - alternatives.insert(QStringLiteral("left_ptr_watch"), QStringLiteral("3ecb610c1bf2410f44200f48c40d3599")); - alternatives.insert(QStringLiteral("hand2"), QStringLiteral("e29285e634086352946a0e7090d73106")); - alternatives.insert(QStringLiteral("openhand"), QStringLiteral("9141b49c8149039304290b508d208c40")); - alternatives.insert(QStringLiteral("closedhand"), QStringLiteral("05e88622050804100c20044008402080")); + alternatives.insert("size_ver", "00008160000006810000408080010102"); + alternatives.insert("size_hor", "028006030e0e7ebffc7f7070c0600140"); + alternatives.insert("size_bdiag", "fcf1c3c7cd4491d801f1e1c78f100000"); + alternatives.insert("size_fdiag", "c7088f0f3e6c8088236ef8e1e3e70000"); + alternatives.insert("whats_this", "d9ce0ab605698f320427677b458ad60b"); + alternatives.insert("split_h", "14fef782d02440884392942c11205230"); + alternatives.insert("split_v", "2870a09082c103050810ffdffffe0204"); + alternatives.insert("forbidden", "03b6e0fcb3499374a867c041f52298f0"); + alternatives.insert("left_ptr_watch", "3ecb610c1bf2410f44200f48c40d3599"); + alternatives.insert("hand2", "e29285e634086352946a0e7090d73106"); + alternatives.insert("openhand", "9141b49c8149039304290b508d208c40"); + alternatives.insert("closedhand", "05e88622050804100c20044008402080"); } return alternatives.value(name, QString()); @@ -152,15 +149,8 @@ } -int XCursorTheme::autodetectCursorSize() const +int XCursorTheme::defaultCursorSize() const { - if (!QX11Info::isPlatformX11()) { - if (QScreen *s = QGuiApplication::primaryScreen()) { - return s->logicalDotsPerInchY() * 16 / 72; - } - // some default value - return 16; - } /* This code is basically borrowed from display.c of the XCursor library We can't use "int XcursorGetDefaultSize(Display *dpy)" because if previously the cursor size was set to a custom value, it would return @@ -192,7 +182,7 @@ qulonglong XCursorTheme::loadCursor(const QString &name, int size) const { if (size <= 0) - size = autodetectCursorSize(); + size = defaultCursorSize(); // Load the cursor images XcursorImages *images = xcLoadImages(name, size); @@ -215,7 +205,7 @@ QImage XCursorTheme::loadImage(const QString &name, int size) const { if (size <= 0) - size = autodetectCursorSize(); + size = defaultCursorSize(); // Load the image XcursorImage *xcimage = xcLoadImage(name, size);