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/applets/kicker/package/metadata.desktop b/applets/kicker/package/metadata.desktop --- a/applets/kicker/package/metadata.desktop +++ b/applets/kicker/package/metadata.desktop @@ -5,7 +5,7 @@ Name[ca]=Menú d'aplicacions Name[ca@valencia]=Menú d'aplicacions Name[cs]=Nabídka aplikací -Name[da]=Startmenu +Name[da]=Programmenu Name[de]=Anwendungsmenü Name[el]=Μενού εφαρμογών Name[en_GB]=Application Menu @@ -54,7 +54,7 @@ Comment[ca]=Un llançador basat en menús emergents en cascada Comment[ca@valencia]=Un llançador basat en menús emergents en cascada Comment[cs]=Spouštěč založený na vyskakovacích nabídkách v kaskádě -Comment[da]=Startmenu baseret på pop-op-menuer i kaskader +Comment[da]=En starter baseret på pop op-menuer i kaskader Comment[de]=Ein Anwendungsstarter auf der Grundlage von kaskadierenden Aufklappmenüs Comment[el]=Εκτελεστής βασισμένος σε επικαλυπτόμενα αναδυόμενα μενού Comment[en_GB]=A launcher based on cascading popup menus diff --git a/applets/kickoff/package/metadata.desktop b/applets/kickoff/package/metadata.desktop --- a/applets/kickoff/package/metadata.desktop +++ b/applets/kickoff/package/metadata.desktop @@ -12,7 +12,7 @@ Name[ca@valencia]=Llançador d'aplicacions Name[cs]=Spouštěč aplikací Name[csb]=Zrëszôcz programów -Name[da]=Startmenu +Name[da]=Programstarter Name[de]=Anwendungs-Starter Name[el]=Εκτελεστής εφαρμογών Name[en_GB]=Application Launcher diff --git a/containments/desktop/package/contents/ui/ConfigFilter.qml b/containments/desktop/package/contents/ui/ConfigFilter.qml --- a/containments/desktop/package/contents/ui/ConfigFilter.qml +++ b/containments/desktop/package/contents/ui/ConfigFilter.qml @@ -45,7 +45,10 @@ // SortFilterModel doesn't have a case-sensitivity option // but filterRegExp always causes case-insensitive sorting. filterRegExp: mimeFilter.text - filterRole: "display" + filterRole: "name" + + sortRole: mimeTypesView.getColumn(mimeTypesView.sortIndicatorColumn).role + sortOrder: mimeTypesView.sortIndicatorOrder } ColumnLayout { @@ -94,50 +97,118 @@ Layout.fillWidth: true Layout.fillHeight: true - ScrollView { + CheckBox { // Purely for metrics. + id: metricsCheckBox + visible: false + } + + TableView { + id: mimeTypesView + + // Signal the delegates listen to when user presses space to toggle current row. + signal toggleCurrent + Layout.fillWidth: true Layout.fillHeight: true - frameVisible: true - enabled: (filterMode.currentIndex > 0) - ListView { - model: filderedMimeTypesModel - - delegate: RowLayout { - CheckBox { - Layout.maximumWidth: 18 // FIXME HACK: Use actual radio button width. + model: filderedMimeTypesModel - checked: model.checked - onCheckedChanged: model.checked = checked - } + sortIndicatorVisible: true + sortIndicatorColumn: 2 // Default to sort by "File type". - PlasmaCore.IconItem { - anchors.verticalCenter: parent.verticalCenter + onSortIndicatorColumnChanged: { // Disallow sorting by icon. + if (sortIndicatorColumn === 1) { + sortIndicatorColumn = 2; + } + } - width: units.iconSizes.small - height: units.iconSizes.small + Keys.onSpacePressed: toggleCurrent() - Layout.maximumWidth: width - Layout.maximumHeight: height + function adjustColumns() { + // Resize description column to take whatever space is left. + var width = viewport.width; + for (var i = 0; i < columnCount - 1; ++i) { + width -= getColumn(i).width; + } + descriptionColumn.width = width; + } - source: model.decoration + onWidthChanged: adjustColumns() + // Component.onCompleted is too early to do this... + onRowCountChanged: adjustColumns() + + TableViewColumn { + role: "checked" + width: metricsCheckBox.width + resizable: false + movable: false + + delegate: CheckBox { + id: checkBox + + checked: styleData.value + activeFocusOnTab: false // only let the TableView as a whole get focus + onClicked: { + model.checked = checked + // Clicking it breaks the binding to the model value which becomes + // an issue during sorting as TableView re-uses delegates. + checked = Qt.binding(function() { + return styleData.value; + }); } - Label { - Layout.fillWidth: true - - text: model.display + Connections { + target: mimeTypesView + onToggleCurrent: { + if (styleData.row === mimeTypesView.currentRow) { + model.checked = !checkBox.checked + } + } } } } + + TableViewColumn { + role: "decoration" + width: units.iconSizes.small + resizable: false + movable: false + + delegate: PlasmaCore.IconItem { + width: units.iconSizes.small + height: units.iconSizes.small + animated: false // TableView re-uses delegates, avoid animation when sorting/filtering. + source: styleData.value + } + } + + TableViewColumn { + id: nameColumn + role: "name" + title: i18n("File type") + width: units.gridUnit * 10 // Assume somewhat reasonable default for mime type name. + onWidthChanged: mimeTypesView.adjustColumns() + movable: false + } + TableViewColumn { + id: descriptionColumn + role: "comment" + title: i18n("Description") + movable: false + resizable: false + } } ColumnLayout { Layout.alignment: Qt.AlignTop + // Need to explicitly base the size off the button's implicitWidth + // to avoid the column from growing way too wide due to fillWidth... + Layout.maximumWidth: Math.max(selectAllButton.implicitWidth, deselectAllButton.implicitWidth) Button { + id: selectAllButton Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) @@ -150,6 +221,7 @@ } Button { + id: deselectAllButton Layout.fillWidth: true enabled: (filterMode.currentIndex > 0) diff --git a/containments/desktop/plugins/folder/mimetypesmodel.cpp b/containments/desktop/plugins/folder/mimetypesmodel.cpp --- a/containments/desktop/plugins/folder/mimetypesmodel.cpp +++ b/containments/desktop/plugins/folder/mimetypesmodel.cpp @@ -42,7 +42,8 @@ QHash MimeTypesModel::roleNames() const { return { - { Qt::DisplayRole, "display" }, + { Qt::DisplayRole, "comment" }, + { Qt::UserRole, "name" }, { Qt::DecorationRole, "decoration" }, { Qt::CheckStateRole, "checked" } }; @@ -56,6 +57,8 @@ switch (role) { case Qt::DisplayRole: + return m_mimeTypesList.at(index.row()).comment(); + case Qt::UserRole: return m_mimeTypesList.at(index.row()).name(); case Qt::DecorationRole: 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/GridView.qml b/kcmcontrols/GridView.qml new file mode 100644 --- /dev/null +++ b/kcmcontrols/GridView.qml @@ -0,0 +1,57 @@ +/* + 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.3 as Kirigami + +QtControls.ScrollView { + id: scroll + property alias view: view + property int cellWidth: Math.min(Math.floor((parent.width - scrollBarSpace - 4) / 2), Kirigami.Units.gridUnit * 10) + property int cellHeight: cellWidth / 1.6 + Kirigami.Units.gridUnit + + readonly property int scrollBarSpace: Kirigami.Units.gridUnit + + implicitWidth: Math.max( Math.min(parent.width, cellWidth * 2 + scroll.scrollBarSpace) + , Math.floor(view.availableWidth / cellWidth) * cellWidth + scroll.scrollBarSpace + 4) + activeFocusOnTab: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + + Component.onCompleted: scroll.background.visible = true; + + GridView { + id: view + property int availableWidth: scroll.parent.width - scroll.scrollBarSpace - 4 + anchors { + fill: parent + margins: 2 + leftMargin: contentHeight <= height ? scroll.scrollBarSpace/2 : 2 + rightMargin: contentHeight <= height ? scroll.scrollBarSpace/2 : scroll.scrollBarSpace + 2 + } + clip: true + activeFocusOnTab: true + cellWidth: scroll.cellWidth + cellHeight: scroll.cellHeight + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveDuration: 0 + } +} diff --git a/kcmcontrols/GridViewKCM.qml b/kcmcontrols/GridViewKCM.qml new file mode 100644 --- /dev/null +++ b/kcmcontrols/GridViewKCM.qml @@ -0,0 +1,48 @@ +/* + 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.kcmcontrols 1.0 as KCMControls +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: scroll.view + + topPadding: 0 + leftPadding: 0 + rightPadding: 0 + bottomPadding: footer ? Kirigami.Units.smallSpacing : 0 + + KCMControls.GridView { + id: scroll + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + } +} diff --git a/kcmcontrols/SimpleKCM.qml b/kcmcontrols/SimpleKCM.qml new file mode 100644 --- /dev/null +++ b/kcmcontrols/SimpleKCM.qml @@ -0,0 +1,28 @@ +/* + 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 org.kde.kirigami 2.2 as Kirigami + +Kirigami.ScrollablePage { + id: root + + title: kcm.name + implicitWidth: Kirigami.Units.gridUnit * 20 + implicitHeight: Kirigami.Units.gridUnit * 20 +} diff --git a/kcmcontrols/qmldir b/kcmcontrols/qmldir new file mode 100644 --- /dev/null +++ b/kcmcontrols/qmldir @@ -0,0 +1,5 @@ +module org.kde.kcmcontrols + +GridDelegate 1.0 GridDelegate.qml +GridViewKCM 1.0 GridViewKCM.qml +GridView 1.0 GridView.qml diff --git a/kcms/access/kcmaccess.desktop b/kcms/access/kcmaccess.desktop --- a/kcms/access/kcmaccess.desktop +++ b/kcms/access/kcmaccess.desktop @@ -159,6 +159,7 @@ X-KDE-Keywords[ca@valencia]=accés,accessibilitat,sord,discapacitat,oïda,pèrdua d'oïda,timbre,timbre audible,timbre visual,Teclat,tecles apegaloses,repetició de tecles,tecles lentes,navegació de ratolí,teclat numèric,activació de gestos,gestors,apegalós,tecles modificadores,modificador,tecles bloquejadores X-KDE-Keywords[es]=acceso,accesibilidad,sordera,descapacitado,oído,pérdida de oído,campana,campana audible,campana visible,campana visual,teclado,teclas,teclas adhesivas,repetición de teclas,teclas lentas,navegación con ratón,teclado numérico,gestos de activación,gestos,adhesivo,teclas modificadoras,modificador,teclas de bloqueo X-KDE-Keywords[eu]=sarbide,irisgarritasuna,gor,baliaezintasun,entzumen,entzumena galdu,kanpai,kanpai entzungarri,kanpai ikusgarri,kanpai ikusgaia,teklatu,tekla,tekla itsaskor, errebote-teklak,tekla motel,sagu bidezko nabigazio,zenbakizko teklatu,zk teklatu,aktibazio-keinu,keinu,itsaskor,tekla aldatzaile,aldatzaile,blokeo-tekla +X-KDE-Keywords[it]=accesso,accessibilità,sordo,handicap,udito,sordità,campanella,campanella udibile,campanella visiva,tastiera,tasti,permanenza dei tasti,pressione ravvicinata dei tasti,rallentamento dei tasti,navigazione col mouse,tastierino numerico,attivazione gesti,gesti,tasti modificatori,modificatore,tasti di blocco X-KDE-Keywords[nl]=toegang,toegankelijkheid,doof,gehandicapt,gehoor,gehoorverlies,bel,hoorbare bel,zichtbare bel,toetsenbord,toetsen,dode toetsen,herhaaltoetsen,langzame toetsen,muisnavigatie,numeriek toetsenbord,activeringsgebaren,gebaren,plakkerig,modificatietoetsen,samensteltoets,vergrendeltoetsen X-KDE-Keywords[pt]=acesso,acessibilidade,surdo,deficiente,campainha,campainha audível,campainha visível,teclado,teclas,teclas fixas,teclas lentas,teclas sonoras,navegação do rato,teclado numérico,numérico,gestos de activação,gestos,fixo,teclas modificadoras,modificadora,teclas de bloqueio X-KDE-Keywords[sv]=åtkomst,handikappstöd,döva,funktionshindrade,hörsel,hörselförlust,summer,hörbar summer,visuellt alarm,Tangentbord,tangenter,klistriga tangenter,studsande tangenter,långsamma tangenter,musnavigering,numeriskt tangentbord,aktiveringsgester,gester,klistrig,väljartangenter,väljare,låstangenter diff --git a/kcms/componentchooser/componentchooser.desktop b/kcms/componentchooser/componentchooser.desktop --- a/kcms/componentchooser/componentchooser.desktop +++ b/kcms/componentchooser/componentchooser.desktop @@ -151,7 +151,7 @@ X-KDE-Keywords[ca]=aplicacions per omissió,components,selector de components,recursos,e-mail,client de correu electrònic,editor de text,missatgeria instantània,emulador de terminal,navegador web,URL,hiperenllaços X-KDE-Keywords[ca@valencia]=aplicacions per omissió,components,selector de components,recursos,e-mail,client de correu electrònic,editor de text,missatgeria instantània,emulador de terminal,navegador web,URL,hiperenllaços X-KDE-Keywords[cs]=výchozí aplikace,komponenty,výběr komponent,zdroje,e-mail,emailový klient,editor textu,komunikátor,emulátor terminálů,webový prohlížeč,URL,odkazy -X-KDE-Keywords[da]=standardprogrammer,komponenter,komponentvælger,ressourcer,e-mail,email klient,tekst-editor,instant messenger,terminal-emulator,webbrowser,URL,hyperlinks +X-KDE-Keywords[da]=standardprogrammer,komponenter,komponentvælger,ressourcer,e-mail,email klient,teksteditor,instant messenger,terminalemulator,webbrowser,URL,hyperlinks X-KDE-Keywords[de]=Standard-Anwendungen,Standard-Komponenten,Komponenten,Ressourcen,E-Mail,E-Mail-Programm,Terminal-Emulation,Programme,Anwendungen,Web,Browser,URL X-KDE-Keywords[el]=προκαθορισμένες εφαρμογές,συστατικά,επιλογέας συστατικών,πόροι,αλληλογραφία,εφαρμογή αλληλογραφίας,επεξεργαστής κειμένου,εφαρμογή στιγμιαίων μηνυμάτων,εξομοιωτής τερματικού,περιηγητής ιστού,URL,υπερσύνδεσμοι X-KDE-Keywords[en_GB]=default applications,components,component chooser,resources,e-mail,email client,text editor,instant messenger,terminal emulator,web browser,URL,hyperlinks 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,100 @@ +/* + 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 1.2 as QQC1 +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.GridViewKCM { + + + 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: ColumnLayout { + id: footerLayout + RowLayout { + id: row1 + //spacer + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + RowLayout { + id: comboLayout + 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 + } + } + } + RowLayout { + parent: footerLayout.x + footerLayout.width - comboLayout.width > width ? row1 : row2 + //TODO: port to QQC2 buttons as soon they have icons + QQC1.Button { + iconName: "document-import" + text: i18n("&Install From File...") + onClicked: kcm.installClicked(); + enabled: kcm.canInstall + } + QQC1.Button { + iconName: "get-hot-new-stuff" + text: i18n("&Get New Theme...") + onClicked: kcm.getNewClicked(); + enabled: kcm.canInstall + } + } + } + RowLayout { + id: row2 + visible: children.length > 1 + //spacer + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } +} + 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/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; } 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/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" @@ -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); diff --git a/kcms/input/mouse.desktop b/kcms/input/mouse.desktop --- a/kcms/input/mouse.desktop +++ b/kcms/input/mouse.desktop @@ -152,7 +152,7 @@ X-KDE-Keywords=Mouse,Mouse acceleration,Mouse threshold,Mouse buttons,Selection,Cursor Shape,Input Devices,Button Mapping,Click,icons,feedback,Pointers,Drag,Double Click,Single Click,mapping,right handed,left handed,Pointer Device,Mouse Wheel,Mouse Emulation,Mouse Navigation,Mouse Drag and Drop,Mouse Scrolling,Mouse Sensitivity,Move Mouse with Num Pad,Mouse Num Pad Emulation X-KDE-Keywords[bs]=Miš, Brzina kretanja miša, Prag osjetljivosti miša, Tasteri miša, Oblik kursora, Ulazni Uređaji, Mapiranje tastera, Klik, Ikone, feedback (povratna veza), Pokazivači, Vuči (drag), Dupli klik, Jedan Klik, Mapiranje, Dešnjak, Ljevak, Uređaj koji je Pokazivač (miš, Trackball, Joystick), Točkić za pomicanje (gore ili dolje), Emulatija Miša, Navigacija Miša, Miš Povucite i ispustite(drag and drop), Skrolanje Mišem, Osjetljivost Miša, Pomjeranje miša pomoću brojevne tastature, Emulacija miša pomoću brojevne tastature -X-KDE-Keywords[ca]=Ratolí,Acceleració de ratolí,Llindar de ratolí,Botons de ratolí,Selecció,Ombra de cursor,Dispositius d'entrada,Mapatge de boton,Clic,icones,reacció,Apuntadors,Arrossegar,Clic doble,Clic normal,mapatge,dretà,esquerrà,Dispositiu apuntador,Roda de ratolí,Emulació de ratolí,Navegació amb ratolí,Arrossegar i deixar anar de ratolí,Desplaçament amb ratolí,Sensitivitat de ratolí,Moure el ratolí amb teclat numèric,Emulació de ratolí amb teclat numèric +X-KDE-Keywords[ca]=Ratolí,Acceleració de ratolí,Llindar de ratolí,Botons de ratolí,Selecció,Ombra de cursor,Dispositius d'entrada,Mapatge de boton,Clic,icones,reacció,Apuntadors,Arrossegar,Clic doble,Clic normal,mapatge,dretà,esquerrà,Dispositiu apuntador,Roda de ratolí,Emulació de ratolí,Navegació amb ratolí,Arrossegar i deixar anar de ratolí,Desplaçament amb ratolí,Sensibilitat del ratolí,Moure el ratolí amb teclat numèric,Emulació de ratolí amb teclat numèric X-KDE-Keywords[ca@valencia]=Ratolí,Acceleració de ratolí,Llindar de ratolí,Botons de ratolí,Selecció,Ombra de cursor,Dispositius d'entrada,Mapatge de boton,Clic,icones,reacció,Apuntadors,Arrossegar,Clic doble,Clic normal,mapatge,dretà,esquerrà,Dispositiu apuntador,Roda de ratolí,Emulació de ratolí,Navegació amb ratolí,Arrossegar i deixar anar de ratolí,Desplaçament amb ratolí,Sensitivitat de ratolí,Moure el ratolí amb teclat numèric,Emulació de ratolí amb teclat numèric X-KDE-Keywords[da]=Mus,Museacceleration,musetærskel,museknapper,markering,markørform,Input-enheder,knapkobling,klik,ikoner,feedback,Pointers,træk,dobbeltklik,enkeltklik,kobling,højrehåndet,venstrehåndet,pegeenhed,musehjul,museemulering,musenavigation,træk og slip,muserulning,scrolling,musefølsomhed,bevæg mus med numerisk tastatur,markører,cursor,rulning X-KDE-Keywords[de]=Maus,Mausbeschleunigung,Mausschwellwert,Maustasten,Auswahl,Cursor,Cursor-Form,Eingabegeräte,Knöpfe,Buttons,Zuordnungen,Klicks,Zeigegeräte,Doppelklick,Rechtshänder,Linkshänder,Ziehen,Mausrad,Maus-Emulation,Maus-Navigation,Ziehen und Ablegen mit der Maus,Blättern mit der Maus,Maus-Empfindlichkeit,Mauszeiger mit der Zahlentastatur verschieben,Maus-Emulation mit der Zahlentastatur diff --git a/kcms/kfontinst/apps/org.kde.kfontview.desktop b/kcms/kfontinst/apps/org.kde.kfontview.desktop --- a/kcms/kfontinst/apps/org.kde.kfontview.desktop +++ b/kcms/kfontinst/apps/org.kde.kfontview.desktop @@ -108,7 +108,7 @@ GenericName[cs]=Prohlížeč písem GenericName[csb]=Przezérnik fòntów GenericName[cy]=Gwelydd Wynebfathau -GenericName[da]=Skrifttype-fremviser +GenericName[da]=Skrifttypevisning GenericName[de]=Schriftartenbetrachter GenericName[el]=Προβολέας γραμματοσειρών GenericName[en_GB]=Font Viewer diff --git a/kcms/kfontinst/kcmfontinst/fontinst.desktop b/kcms/kfontinst/kcmfontinst/fontinst.desktop --- a/kcms/kfontinst/kcmfontinst/fontinst.desktop +++ b/kcms/kfontinst/kcmfontinst/fontinst.desktop @@ -116,7 +116,7 @@ X-KDE-Keywords[bs]=slovo,slova,instalacijski program,truetype,tip1,bitna mapa X-KDE-Keywords[ca]=tipus de lletra,tipus de lletres,instal·lador,truetype,type1,mapa de bits X-KDE-Keywords[ca@valencia]=tipus de lletra,tipus de lletres,instal·lador,truetype,type1,mapa de bits -X-KDE-Keywords[da]=font,fonte,skrifttype,skrifttyper,truetype,type1, bitmap +X-KDE-Keywords[da]=font,fonts,skrifttype,skrifttyper,installer,truetype,type1,bitmap X-KDE-Keywords[de]=Schrift,Fonts,Schriftarten,Installation,TrueType,Type1,Bitmapschriften X-KDE-Keywords[el]=γραμματοσειρά,γραμματοσειρές,πρόγραμμα εγκατάστασης,truetype,type1,bitmap X-KDE-Keywords[en_GB]=font,fonts,installer,truetype,type1,bitmap diff --git a/kcms/runners/kcm_plasmasearch.desktop b/kcms/runners/kcm_plasmasearch.desktop --- a/kcms/runners/kcm_plasmasearch.desktop +++ b/kcms/runners/kcm_plasmasearch.desktop @@ -59,6 +59,7 @@ Comment[ca]=Configura la barra de cerca Comment[ca@valencia]=Configura la barra de busca Comment[cs]=Nastavit panel vyhledávání +Comment[da]=Indstil søgelinje Comment[es]=Configurar la barra de búsqueda Comment[eu]=Konfiguratu bilaketa-barra Comment[id]=Konfigurasi Bilah Pencarian diff --git a/runners/plasma-desktop/plasma-runner-plasma-desktop.desktop b/runners/plasma-desktop/plasma-runner-plasma-desktop.desktop --- a/runners/plasma-desktop/plasma-runner-plasma-desktop.desktop +++ b/runners/plasma-desktop/plasma-runner-plasma-desktop.desktop @@ -6,7 +6,7 @@ Name[ca]=Intèrpret d'ordres de l'escriptori Plasma Name[ca@valencia]=Intèrpret d'ordes de l'escriptori Plasma Name[cs]=Pracovní plocha Plasma -Name[da]=Plasma Desktop Shell +Name[da]=Plasma-skrivebordets skal Name[de]=Plasma-Umgebung Name[el]=Κέλυφος επιφάνειας εργασίας Plasma Name[en_GB]=Plasma Desktop Shell @@ -70,7 +70,7 @@ Comment[ca]=Interactua amb l'intèrpret d'ordres de l'escriptori Plasma Comment[ca@valencia]=Interactua amb l'intèrpret d'ordes de l'escriptori Plasma Comment[cs]=Interakce se shellem pracovní plochy plasmy -Comment[da]=Interaktion med Plasma desktop-skal +Comment[da]=Interaktion med Plasma-skrivebordets skal Comment[de]=Interaktion mit der Plasma-Shell Comment[el]=Αλληλεπίδραση με το κέλυφος επιφάνειας εργασίας Plasma Comment[en_GB]=Interact with the Plasma desktop shell diff --git a/toolboxes/plasma-toolbox-paneltoolbox.desktop b/toolboxes/plasma-toolbox-paneltoolbox.desktop --- a/toolboxes/plasma-toolbox-paneltoolbox.desktop +++ b/toolboxes/plasma-toolbox-paneltoolbox.desktop @@ -6,7 +6,7 @@ Comment[ca]=Quadre d'eines per defecte del plafó per a l'àrea de treball de l'escriptori Plasma Comment[ca@valencia]=Quadre d'eines per defecte del plafó per a l'àrea de treball de l'escriptori Plasma Comment[cs]=Výchozí nástroje panelu pro Plasma shell pracovní plochy -Comment[da]=Standard panelværktøjskasse til Plasma desktop-skal +Comment[da]=Standard panelværktøjskasse til Plasma-skrivebordets skal Comment[de]=Standard-Werkzeugkasten für die Plasma-Kontrollleiste Comment[el]=Η προκαθορισμένη εργαλειοθήκη πίνακα για το κέλυφος της επιφάνειας εργασίας Plasma Comment[en_GB]=Default panel toolbox for the Plasma desktop shell