diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ find_package(LibKWorkspace CONFIG REQUIRED) find_package(LibTaskManager CONFIG REQUIRED) +find_package(LibColorCorrect CONFIG REQUIRED) find_package(KWinDBusInterface CONFIG REQUIRED) find_package(ScreenSaverDBusInterface CONFIG REQUIRED) find_package(KRunnerAppDBusInterface CONFIG REQUIRED) @@ -112,21 +113,12 @@ find_package(Synaptics) set_package_properties(Synaptics PROPERTIES TYPE OPTIONAL) add_feature_info("Synaptics" SYNAPTICS_FOUND "Synaptics libraries needed for touchpad KCM") -include(ConfigureChecks.cmake) -if(${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL") - find_package(OpenGL) - set_package_properties(OpenGL PROPERTIES DESCRIPTION "The OpenGL libraries" - URL "http://www.opengl.org" - TYPE REQUIRED - ) -else() - find_package(OpenGLES) - set_package_properties(OpenGLES PROPERTIES DESCRIPTION "The OpenGLES libraries" - URL "http://www.khronos.org/opengles" - TYPE REQUIRED - ) -endif() +find_package(XorgLibinput) +set_package_properties(XorgLibinput PROPERTIES TYPE OPTIONAL) +add_feature_info("XorgLibinput" XORGLIBINPUT_FOUND "Libinput driver headers needed for input KCM") + +include(ConfigureChecks.cmake) find_package(Breeze ${PROJECT_VERSION} CONFIG) set_package_properties(Breeze PROPERTIES diff --git a/applets/kicker/package/contents/config/main.xml b/applets/kicker/package/contents/config/main.xml --- a/applets/kicker/package/contents/config/main.xml +++ b/applets/kicker/package/contents/config/main.xml @@ -72,7 +72,7 @@ - bookmarks,baloosearch,locations + shell,bookmarks,baloosearch,locations diff --git a/applets/kicker/package/contents/ui/ItemListDialog.qml b/applets/kicker/package/contents/ui/ItemListDialog.qml --- a/applets/kicker/package/contents/ui/ItemListDialog.qml +++ b/applets/kicker/package/contents/ui/ItemListDialog.qml @@ -75,9 +75,9 @@ } function delayedDestroy() { - var timer = Qt.createQmlObject('import QtQuick 2.0; Timer { onTriggered: itemDialog.destroy() }', itemDialog); - timer.interval = 0; - timer.start(); + Qt.callLater(function() { + itemDialog.destroy(); + }); } 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/kicker/plugin/kastatsfavoritesmodel.cpp b/applets/kicker/plugin/kastatsfavoritesmodel.cpp --- a/applets/kicker/plugin/kastatsfavoritesmodel.cpp +++ b/applets/kicker/plugin/kastatsfavoritesmodel.cpp @@ -464,8 +464,10 @@ this, [&] (const QString ¤tActivity) { qCDebug(KICKER_DEBUG) << "Activity just got changed to" << currentActivity; Q_UNUSED(currentActivity); - auto clientId = d->m_clientId; - initForClient(clientId); + if (d) { + auto clientId = d->m_clientId; + initForClient(clientId); + } }); } @@ -495,7 +497,7 @@ bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) { - return d->trigger(row, actionId, argument); + return d && d->trigger(row, actionId, argument); } bool KAStatsFavoritesModel::enabled() const @@ -541,6 +543,7 @@ void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) { + if (!d) return; qCDebug(KICKER_DEBUG) << "portOldFavorites" << ids; const auto activityId = ":global"; @@ -611,6 +614,8 @@ void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) { + if (!d || id.isEmpty()) return; + const auto url = d->normalizedId(id).value(); Q_ASSERT(!activity.values.isEmpty()); @@ -625,6 +630,8 @@ void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) { + if (!d || id.isEmpty()) return; + const auto url = d->normalizedId(id).value(); qCDebug(KICKER_DEBUG) << "setFavoriteOn" << id << activityId << url << " (actual)"; @@ -644,6 +651,8 @@ void KAStatsFavoritesModel::moveRow(int from, int to) { + if (!d) return; + d->move(from, to); } diff --git a/applets/kickoff/package/contents/ui/FullRepresentation.qml b/applets/kickoff/package/contents/ui/FullRepresentation.qml --- a/applets/kickoff/package/contents/ui/FullRepresentation.qml +++ b/applets/kickoff/package/contents/ui/FullRepresentation.qml @@ -43,18 +43,28 @@ property QtObject globalFavorites: rootModelFavorites state: "Normal" - focus: true + + onFocusChanged: { + header.input.forceActiveFocus(); + } function switchToInitial() { - if(firstButton != null) { + if (firstButton != null) { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; header.query = "" header.state = "hint"; root.state = "Normal"; } } + Kicker.DragHelper { + id: dragHelper + + dragIconSize: units.iconSizes.medium + onDropped: kickoff.dragSource = null + } + Kicker.AppsModel { id: rootModel @@ -400,16 +410,16 @@ } } - onCurrentTabChanged: root.forceActiveFocus(); + onCurrentTabChanged: header.input.forceActiveFocus(); Connections { target: plasmoid onExpandedChanged: { if(menuItemsChanged()) { createButtons(); } if (!expanded) { - switchToInitial() + switchToInitial(); } } } @@ -437,7 +447,7 @@ break; } case Qt.Key_Left: { - if (header.input.focus) { + if (header.input.focus && header.state == "query") { break; } if (!currentView.deactivateCurrentIndex()) { @@ -451,7 +461,7 @@ break; } case Qt.Key_Right: { - if (header.input.focus) { + if (header.input.focus && header.state == "query") { break; } currentView.activateCurrentIndex(); @@ -473,7 +483,7 @@ if (header.state != "query") { plasmoid.expanded = false; } else { - switchToInitial(); + header.query = ""; } event.accepted = true; break; @@ -483,28 +493,10 @@ event.accepted = true; break; } - - default: { // forward key to searchView - //header.query += event.text will break if the key is backspace, - //since if the user continues to type, it will produce an invalid query, - //having backspace as the first character - if (event.key == Qt.Key_Backspace && header.query == "") { - return; - } - if (event.text != "" && !header.input.focus) { - root.currentView.listView.currentIndex = -1; - - if (event.matches(StandardKey.Paste) ) { - header.input.paste(); - } else if (! (event.key & Qt.Key_Escape)) { - //if special key, do nothing. Qt.Escape is 0x10000000 which happens to be a mask used for all special keys in Qt. - header.query = ""; - header.query += event.text; - } + default: + if (!header.input.focus) { header.input.forceActiveFocus(); - event.accepted = true; } - } } } @@ -541,6 +533,10 @@ target: mainTabGroup currentTab: searchPage } + PropertyChanges { + target: root + Keys.forwardTo: [root] + } } ] // states diff --git a/applets/kickoff/package/contents/ui/Header.qml b/applets/kickoff/package/contents/ui/Header.qml --- a/applets/kickoff/package/contents/ui/Header.qml +++ b/applets/kickoff/package/contents/ui/Header.qml @@ -177,7 +177,6 @@ } if (text == "") { root.state = root.previousState; - root.forceActiveFocus(); header.state = "info"; } else { header.state = "query"; diff --git a/applets/kickoff/package/contents/ui/Kickoff.qml b/applets/kickoff/package/contents/ui/Kickoff.qml --- a/applets/kickoff/package/contents/ui/Kickoff.qml +++ b/applets/kickoff/package/contents/ui/Kickoff.qml @@ -75,13 +75,6 @@ property Item dragSource: null - Kicker.DragHelper { - id: dragHelper - - dragIconSize: units.iconSizes.medium - onDropped: kickoff.dragSource = null - } - Kicker.ProcessRunner { id: processRunner; } 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/applets/kimpanel/backend/scim/CMakeLists.txt b/applets/kimpanel/backend/scim/CMakeLists.txt --- a/applets/kimpanel/backend/scim/CMakeLists.txt +++ b/applets/kimpanel/backend/scim/CMakeLists.txt @@ -4,8 +4,8 @@ set(SCIM_ICON_DIR "${PC_SCIM_PREFIX}/share/scim/icons") configure_file(config-scim.h.cmake config-scim.h) set(kimpanel_scim_panel_SRCS main.cpp) - qt4_generate_moc(${kimpanel_scim_panel_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/main.moc) add_executable(kimpanel-scim-panel ${kimpanel_scim_panel_SRCS}) + set_target_properties(kimpanel-scim-panel PROPERTIES AUTOMOC TRUE) target_link_libraries(kimpanel-scim-panel Qt5::Core Qt5::DBus ${SCIM_LIBRARIES}) install(TARGETS kimpanel-scim-panel DESTINATION ${LIBEXEC_INSTALL_DIR}) endif(SCIM_FOUND) diff --git a/applets/pager/plugin/pagermodel.h b/applets/pager/plugin/pagermodel.h --- a/applets/pager/plugin/pagermodel.h +++ b/applets/pager/plugin/pagermodel.h @@ -27,6 +27,7 @@ #include #endif +#include #include #include diff --git a/applets/pager/plugin/pagermodel.cpp b/applets/pager/plugin/pagermodel.cpp --- a/applets/pager/plugin/pagermodel.cpp +++ b/applets/pager/plugin/pagermodel.cpp @@ -60,7 +60,8 @@ WindowTasksModel *tasksModel = nullptr; static ActivityInfo *activityInfo; - QMetaObject::Connection activityInfoConn; + QMetaObject::Connection activityNumberConn; + QMetaObject::Connection activityNamesConn; static VirtualDesktopInfo *virtualDesktopInfo; QMetaObject::Connection virtualDesktopNumberConn; @@ -164,18 +165,24 @@ } ); - QObject::disconnect(activityInfoConn); + QObject::disconnect(activityNumberConn); + QObject::disconnect(activityNamesConn); QObject::disconnect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged); QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); } else { - QObject::disconnect(activityInfoConn); - activityInfoConn = QObject::connect(activityInfo, + QObject::disconnect(activityNumberConn); + activityNumberConn = QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, [this]() { q->refresh(); }); + QObject::disconnect(activityNamesConn); + activityNamesConn = QObject::connect(activityInfo, + &ActivityInfo::namesOfRunningActivitiesChanged, + q, [this]() { q->refresh(); }); + QObject::disconnect(virtualDesktopNumberConn); QObject::disconnect(virtualDesktopNamesConn); @@ -272,7 +279,8 @@ } else if (!enabled && d->enabled) { beginResetModel(); - disconnect(d->activityInfoConn); + disconnect(d->activityNumberConn); + disconnect(d->activityNamesConn); disconnect(d->virtualDesktopNumberConn); disconnect(d->virtualDesktopNamesConn); diff --git a/applets/taskmanager/plugin/backend.h b/applets/taskmanager/plugin/backend.h --- a/applets/taskmanager/plugin/backend.h +++ b/applets/taskmanager/plugin/backend.h @@ -24,6 +24,7 @@ #include #include +#include class QAction; class QActionGroup; diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp --- a/applets/taskmanager/plugin/backend.cpp +++ b/applets/taskmanager/plugin/backend.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -216,30 +217,55 @@ return actions; } + QString previousGroup; + QMenu *subMenu = nullptr; + QScopedPointer placesModel(new KFilePlacesModel()); for (int i = 0; i < placesModel->rowCount(); ++i) { QModelIndex idx = placesModel->index(i, 0); - if (placesModel->data(idx, KFilePlacesModel::HiddenRole).toBool()) { + if (idx.data(KFilePlacesModel::HiddenRole).toBool()) { continue; } - const QString &title = placesModel->data(idx, Qt::DisplayRole).toString(); - const QIcon &icon = placesModel->data(idx, Qt::DecorationRole).value(); - const QUrl &url = placesModel->data(idx, KFilePlacesModel::UrlRole).toUrl(); + const QString &title = idx.data(Qt::DisplayRole).toString(); + const QIcon &icon = idx.data(Qt::DecorationRole).value(); + const QUrl &url = idx.data(KFilePlacesModel::UrlRole).toUrl(); - QAction *action = new QAction(icon, title, parent); + QAction *placeAction = new QAction(icon, title, parent); - connect(action, &QAction::triggered, this, [this, action, url, desktopEntryUrl] { + connect(placeAction, &QAction::triggered, this, [this, url, desktopEntryUrl] { KService::Ptr service = KService::serviceByDesktopPath(desktopEntryUrl.toLocalFile()); if (!service) { return; } KRun::runService(*service, {url}, QApplication::activeWindow()); }); - actions << QVariant::fromValue(action); + const QString &groupName = idx.data(KFilePlacesModel::GroupRole).toString(); + if (previousGroup.isEmpty()) { // Skip first group heading. + previousGroup = groupName; + } + + // Put all subsequent categories into a submenu. + if (previousGroup != groupName) { + QAction *subMenuAction = new QAction(groupName, parent); + subMenu = new QMenu(); + // Cannot parent a QMenu to a QAction, need to delete it manually. + connect(parent, &QObject::destroyed, subMenu, &QObject::deleteLater); + subMenuAction->setMenu(subMenu); + + actions << QVariant::fromValue(subMenuAction); + + previousGroup = groupName; + } + + if (subMenu) { + subMenu->addAction(placeAction); + } else { + actions << QVariant::fromValue(placeAction); + } } // There is nothing more frustrating than having a "More" entry that ends up showing just one or two diff --git a/applets/trash/package/contents/ui/main.qml b/applets/trash/package/contents/ui/main.qml --- a/applets/trash/package/contents/ui/main.qml +++ b/applets/trash/package/contents/ui/main.qml @@ -59,6 +59,7 @@ Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground Plasmoid.icon: (dirModel.count > 0) ? "user-trash-full": "user-trash" + Plasmoid.onActivated: action_open() preventStealing: true diff --git a/applets/trash/package/metadata.desktop b/applets/trash/package/metadata.desktop --- a/applets/trash/package/metadata.desktop +++ b/applets/trash/package/metadata.desktop @@ -95,6 +95,7 @@ Comment[hu]=Hozzáférést biztosít a kukába helyezett fájlokhoz Comment[id]=Menyediakan akses ke file yang dikirim ke tempat sampah Comment[it]=Fornisce l'accesso ai file cestinati +Comment[ko]=휴지통에 버린 파일 접근 제공 Comment[nl]=Biedt toegang tot de bestanden verzonden naar de prullenbak Comment[nn]=Gjev tilgang til filer sende til papirkorga Comment[pl]=Zapewna dostęp do plików w koszu diff --git a/cmake/modules/FindOpenGLES.cmake b/cmake/modules/FindOpenGLES.cmake deleted file mode 100644 --- a/cmake/modules/FindOpenGLES.cmake +++ /dev/null @@ -1,52 +0,0 @@ -# - Try to find OpenGLES -# Once done this will define -# -# OPENGLES_FOUND - system has OpenGLES and EGL -# OPENGL_EGL_FOUND - system has EGL -# OPENGLES_INCLUDE_DIR - the GLES include directory -# OPENGLES_LIBRARY - the GLES library -# OPENGLES_EGL_INCLUDE_DIR - the EGL include directory -# OPENGLES_EGL_LIBRARY - the EGL library -# OPENGLES_LIBRARIES - all libraries needed for OpenGLES -# OPENGLES_INCLUDES - all includes needed for OpenGLES - -FIND_PATH(OPENGLES_INCLUDE_DIR GLES2/gl2.h - /usr/openwin/share/include - /opt/graphics/OpenGL/include /usr/X11R6/include - /usr/include -) - -FIND_LIBRARY(OPENGLES_LIBRARY - NAMES GLESv2 - PATHS /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/shlib /usr/X11R6/lib - /usr/lib -) - -FIND_PATH(OPENGLES_EGL_INCLUDE_DIR EGL/egl.h - /usr/openwin/share/include - /opt/graphics/OpenGL/include /usr/X11R6/include - /usr/include -) - -FIND_LIBRARY(OPENGLES_EGL_LIBRARY - NAMES EGL - PATHS /usr/shlib /usr/X11R6/lib - /usr/lib -) - -SET(OPENGL_EGL_FOUND "NO") -IF(OPENGLES_EGL_LIBRARY AND OPENGLES_EGL_INCLUDE_DIR) - SET(OPENGL_EGL_FOUND "YES") -ENDIF() - -SET(OPENGLES_FOUND "NO") -IF(OPENGLES_LIBRARY AND OPENGLES_INCLUDE_DIR AND - OPENGLES_EGL_LIBRARY AND OPENGLES_EGL_INCLUDE_DIR) - SET(OPENGLES_LIBRARIES ${OPENGLES_LIBRARY} ${OPENGLES_LIBRARIES} - ${OPENGLES_EGL_LIBRARY}) - SET(OPENGLES_INCLUDES ${OPENGLES_INCLUDE_DIR} ${OPENGLES_EGL_INCLUDE_DIR}) - SET(OPENGLES_FOUND "YES") -ENDIF() - diff --git a/cmake/modules/FindXorgLibinput.cmake b/cmake/modules/FindXorgLibinput.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindXorgLibinput.cmake @@ -0,0 +1,45 @@ +# - Find xorg libinput's libraries and headers. +# This module defines the following variables: +# +# XORGLIBINPUT_FOUND - true if libinput was found +# XORGLIBINPUT_INCLUDE_DIRS - include path for synaptics +# There are no libraries, just a header file +# +# Copyright (c) 2017 Weng Xuetian +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +find_package(PkgConfig) +pkg_check_modules(PC_XORGLIBINPUT xorg-libinput) + +find_path(XORGLIBINPUT_INCLUDE_DIRS + NAMES libinput-properties.h + HINTS ${PC_XORGLIBINPUT_INCLUDE_DIRS} ${PC_XORGLIBINPUT_INCLUDEDIR} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(XorgLibinput REQUIRED_VARS XORGLIBINPUT_INCLUDE_DIRS) + +mark_as_advanced(XORGLIBINPUT_INCLUDE_DIRS) 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/package/contents/ui/FolderView.qml b/containments/desktop/package/contents/ui/FolderView.qml --- a/containments/desktop/package/contents/ui/FolderView.qml +++ b/containments/desktop/package/contents/ui/FolderView.qml @@ -253,6 +253,7 @@ if (mouse.buttons & Qt.BackButton) { if (root.isPopup && dir.resolvedUrl != dir.resolve(plasmoid.configuration.url)) { doBack(); + mouse.accepted = true; } return; @@ -273,6 +274,7 @@ if (mouse.buttons & Qt.RightButton) { clearPressState(); dir.openContextMenu(); + mouse.accepted = true; } } else { pressedItem = hoveredItem; @@ -308,6 +310,7 @@ clearPressState(); dir.openContextMenu(); + mouse.accepted = true; } } } @@ -420,6 +423,11 @@ rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y)); } + // Ensure rubberband is at least 1px in size or else it will become + // invisible and not match any items. + rB.width = Math.max(1, rB.width); + rB.height = Math.max(1, rB.height); + gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); return; @@ -1072,6 +1080,8 @@ parseDesktopFiles: (plasmoid.configuration.url == "desktop:/") previews: plasmoid.configuration.previews previewPlugins: plasmoid.configuration.previewPlugins + screenMapper: Folder.ScreenMapper + appletInterface: plasmoid onListingCompleted: { if (!gridView.model && plasmoid.expanded) { @@ -1095,6 +1105,11 @@ var from = -1; var to = -1; + // round the drop pos to half of an item's size to have a more exact + // placement in the grid and avoid jumping items + dropPos.x = Math.floor((2 * dropPos.x) / cellWidth) * (cellWidth / 2); + dropPos.y = Math.floor((2 * dropPos.y) / cellHeight) * (cellHeight / 2); + for (var i = 0; i < urls.length; i++) { from = positioner.indexForUrl(urls[i]); to = -1; diff --git a/containments/desktop/package/contents/ui/FolderViewDialog.qml b/containments/desktop/package/contents/ui/FolderViewDialog.qml --- a/containments/desktop/package/contents/ui/FolderViewDialog.qml +++ b/containments/desktop/package/contents/ui/FolderViewDialog.qml @@ -118,9 +118,9 @@ } function delayedDestroy() { - var timer = Qt.createQmlObject('import QtQuick 2.0; Timer { onTriggered: itemDialog.destroy() }', itemDialog); - timer.interval = 0; - timer.start(); + Qt.callLater(function() { + itemDialog.destroy(); + }); } Component.onDestruction: { diff --git a/containments/desktop/package/contents/ui/main.qml b/containments/desktop/package/contents/ui/main.qml --- a/containments/desktop/package/contents/ui/main.qml +++ b/containments/desktop/package/contents/ui/main.qml @@ -311,7 +311,7 @@ } onImmutableChanged: { - if (!plasmoid.immutable) { + if (root.isContainment && !plasmoid.immutable) { pressToMoveHelp.show(); } } diff --git a/containments/desktop/plugins/folder/CMakeLists.txt b/containments/desktop/plugins/folder/CMakeLists.txt --- a/containments/desktop/plugins/folder/CMakeLists.txt +++ b/containments/desktop/plugins/folder/CMakeLists.txt @@ -20,6 +20,7 @@ viewpropertiesmenu.cpp wheelinterceptor.cpp shortcut.cpp + screenmapper.cpp ) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/private/desktopcontainment/folder) diff --git a/containments/desktop/plugins/folder/autotests/CMakeLists.txt b/containments/desktop/plugins/folder/autotests/CMakeLists.txt --- a/containments/desktop/plugins/folder/autotests/CMakeLists.txt +++ b/containments/desktop/plugins/folder/autotests/CMakeLists.txt @@ -5,6 +5,7 @@ include_directories("..";${include_directories}) ecm_add_tests( + screenmappertest.cpp foldermodeltest.cpp positionertest.cpp viewpropertiesmenutest.cpp diff --git a/containments/desktop/plugins/folder/autotests/foldermodeltest.h b/containments/desktop/plugins/folder/autotests/foldermodeltest.h --- a/containments/desktop/plugins/folder/autotests/foldermodeltest.h +++ b/containments/desktop/plugins/folder/autotests/foldermodeltest.h @@ -32,9 +32,6 @@ Q_OBJECT private Q_SLOTS: - void initTestCase(); - void cleanupTestCase(); - void init(); void cleanup(); void tst_listing(); @@ -48,8 +45,13 @@ void tst_defaultValues(); void tst_actionMenu(); void tst_lockedChanged(); + void tst_multiScreen(); + void tst_multiScreenDifferenPath(); + private: + void createTestFolder(const QString &path); + FolderModel *m_folderModel; QTemporaryDir *m_folderDir; }; diff --git a/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp b/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp --- a/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp +++ b/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp @@ -21,6 +21,7 @@ #include "foldermodeltest.h" #include "foldermodel.h" +#include "screenmapper.h" #include #include @@ -30,38 +31,36 @@ static const QLatin1String desktop(QLatin1String("Desktop")); -void FolderModelTest::initTestCase() +void FolderModelTest::createTestFolder(const QString &path) { - m_folderDir = new QTemporaryDir(); - QDir dir(m_folderDir->path()); - dir.mkdir(desktop); - dir.cd(desktop); + dir.mkdir(path); + dir.cd(path); dir.mkdir("firstDir"); QFile f; for (int i = 1; i < 10; i++) { f.setFileName(QStringLiteral("%1/file%2.txt").arg(dir.path(), QString::number(i))); f.open(QFile::WriteOnly); f.close(); } - -} - -void FolderModelTest::cleanupTestCase() -{ - delete m_folderDir; } void FolderModelTest::init() { + m_folderDir = new QTemporaryDir(); + createTestFolder(desktop); m_folderModel = new FolderModel(this); + m_folderModel->classBegin(); m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop ); + m_folderModel->componentComplete(); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); s.wait(1000); } void FolderModelTest::cleanup() { + delete m_folderDir; + m_folderDir = 0; delete m_folderModel; m_folderModel = nullptr; } @@ -265,3 +264,120 @@ m_folderModel->setLocked(true); QCOMPARE(s.count(), 2); } + +void FolderModelTest::tst_multiScreen() +{ + delete m_folderModel; + // Custom instance for this test to set screen mapper before marking component + // as complete. + m_folderModel = new FolderModel(this); + m_folderModel->classBegin(); + m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop ); + auto *screenMapper = ScreenMapper::instance(); + m_folderModel->setUsedByContainment(true); + m_folderModel->setScreenMapper(screenMapper); + m_folderModel->setScreen(0); + m_folderModel->componentComplete(); + + QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); + s.wait(1000); + const auto count = m_folderModel->rowCount(); + for (int i = 0; i < count; i++) { + const auto index = m_folderModel->index(i, 0); + const auto name = index.data(FolderModel::UrlRole).toString(); + // all items are on the first screen by default + QCOMPARE(screenMapper->screenForItem(name), 0); + } + + // move one file to a new screen + const auto movedItem = m_folderModel->index(0, 0).data(FolderModel::UrlRole).toString(); + FolderModel secondFolderModel; + secondFolderModel.classBegin(); + secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop ); + secondFolderModel.setUsedByContainment(true); + secondFolderModel.setScreenMapper(screenMapper); + secondFolderModel.setScreen(1); + secondFolderModel.componentComplete(); + QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); + s2.wait(1000); + const auto count2 = secondFolderModel.rowCount(); + QCOMPARE(count2, 0); + + screenMapper->addMapping(movedItem, 1); + m_folderModel->invalidate(); + secondFolderModel.invalidate(); + s.wait(1000); + s2.wait(1000); + // we have one less item + QCOMPARE(m_folderModel->rowCount(), count - 1); + QCOMPARE(secondFolderModel.rowCount(), 1); + QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toString(), movedItem); + QCOMPARE(screenMapper->screenForItem(movedItem), 1); + + // remove extra screen, we have all items back + screenMapper->removeScreen(1, m_folderModel->url()); + s.wait(500); + QCOMPARE(m_folderModel->rowCount(), count); + QCOMPARE(secondFolderModel.rowCount(), 0); + QCOMPARE(screenMapper->screenForItem(movedItem), 0); + + // add back extra screen, the item is moved there + screenMapper->addScreen(1, m_folderModel->url()); + s.wait(500); + s2.wait(500); + QCOMPARE(m_folderModel->rowCount(), count - 1); + QCOMPARE(secondFolderModel.rowCount(), 1); + QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toString(), movedItem); + QCOMPARE(screenMapper->screenForItem(movedItem), 1); + + // create a new item, it appears on the first screen + QDir dir(m_folderDir->path()); + dir.cd(desktop); + dir.mkdir("secondDir"); + dir.cd("secondDir"); + s.wait(1000); + QCOMPARE(m_folderModel->rowCount(), count); + QCOMPARE(secondFolderModel.rowCount(), 1); + QCOMPARE(screenMapper->screenForItem("file://" + dir.path()), 0); +} + +void FolderModelTest::tst_multiScreenDifferenPath() +{ + auto *screenMapper = ScreenMapper::instance(); + m_folderModel->setUsedByContainment(true); + m_folderModel->setScreenMapper(screenMapper); + m_folderModel->setScreen(0); + QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); + s.wait(1000); + const auto count = m_folderModel->rowCount(); + QCOMPARE(count, 10); + + const QLatin1String desktop2(QLatin1String("Desktop2")); + createTestFolder(desktop2); + FolderModel secondFolderModel; + secondFolderModel.setUsedByContainment(true); + secondFolderModel.setScreenMapper(screenMapper); + secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop2 ); + secondFolderModel.setScreen(1); + QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); + s2.wait(1000); + const auto count2 = secondFolderModel.rowCount(); + QCOMPARE(count2, 10); + + // create a new item, it appears on the first screen + QDir dir(m_folderDir->path()); + dir.cd(desktop); + dir.mkdir("secondDir"); + s.wait(1000); + QCOMPARE(m_folderModel->rowCount(), count + 1); + QCOMPARE(secondFolderModel.rowCount(), count2); + + + // create a new item, it appears on the second screen + dir.cd(m_folderDir->path() + QDir::separator() + desktop2); + dir.mkdir("secondDir2"); + s.wait(1000); + QCOMPARE(m_folderModel->rowCount(), count + 1); + QCOMPARE(secondFolderModel.rowCount(), count2 + 1); +} + diff --git a/containments/desktop/plugins/folder/autotests/positionertest.h b/containments/desktop/plugins/folder/autotests/positionertest.h --- a/containments/desktop/plugins/folder/autotests/positionertest.h +++ b/containments/desktop/plugins/folder/autotests/positionertest.h @@ -52,6 +52,7 @@ void tst_defaultValues(); void tst_changeEnabledStatus(); void tst_changePerStripe(); + void tst_proxyMapping(); private: void checkPositions(int perStripe); diff --git a/containments/desktop/plugins/folder/autotests/positionertest.cpp b/containments/desktop/plugins/folder/autotests/positionertest.cpp --- a/containments/desktop/plugins/folder/autotests/positionertest.cpp +++ b/containments/desktop/plugins/folder/autotests/positionertest.cpp @@ -29,6 +29,7 @@ #include "foldermodel.h" #include "positioner.h" +#include "screenmapper.h" QTEST_MAIN(PositionerTest) @@ -58,6 +59,11 @@ void PositionerTest::init() { m_folderModel = new FolderModel(this); + m_folderModel->classBegin(); + m_folderModel->setScreen(0); + m_folderModel->setScreenMapper(ScreenMapper::instance()); + m_folderModel->setUsedByContainment(true); + m_folderModel->componentComplete(); m_positioner = new Positioner(this); m_positioner->setEnabled(true); m_positioner->setFolderModel(m_folderModel); @@ -209,6 +215,100 @@ QCOMPARE(s.count(), 2); } +void PositionerTest::tst_proxyMapping() +{ + auto *screenMapper = ScreenMapper::instance(); + FolderModel secondFolderModel; + secondFolderModel.classBegin(); + secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop ); + secondFolderModel.setUsedByContainment(true); + secondFolderModel.setScreenMapper(screenMapper); + secondFolderModel.setScreen(1); + secondFolderModel.componentComplete(); + Positioner secondPositioner; + secondPositioner.setEnabled(true); + secondPositioner.setFolderModel(&secondFolderModel); + secondPositioner.setPerStripe(3); + + QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); + QVERIFY(s2.wait(1000)); + + QHash expectedSource2ProxyScreen0; + QHash expectedProxy2SourceScreen0; + QHash expectedProxy2SourceScreen1; + QHash expectedSource2ProxyScreen1; + + for (int i = 0; i < m_folderModel->rowCount(); i++) { + expectedSource2ProxyScreen0[i] = i; + expectedProxy2SourceScreen0[i] = i; + } + + // swap items 1 and 2 in the positioner + m_positioner->move({1, 2, 2, 1}); + expectedSource2ProxyScreen0[1] = 2; + expectedSource2ProxyScreen0[2] = 1; + expectedProxy2SourceScreen0[1] = 2; + expectedProxy2SourceScreen0[2] = 1; + + auto savedSource2ProxyScreen0 = expectedSource2ProxyScreen0; + auto savedProxy2SourceScreen0 = expectedProxy2SourceScreen0; + + auto verifyMapping = [](const QHash &actual, const QHash &expected) { + + auto ensureUnique = [](const QHash mapping) { + auto values = mapping.values(); + qSort(values); + auto uniqueValues = values.toSet().toList(); + qSort(uniqueValues); + QVERIFY(uniqueValues == values); + }; + + ensureUnique(actual); + QCOMPARE(actual, expected); + }; + + verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); + verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); + verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); + verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); + + const auto movedItem = m_folderModel->index(1, 0).data(FolderModel::UrlRole).toString(); + + // move the item 1 from source (now in position 2) to the second screen + screenMapper->addMapping(movedItem, 1); + + expectedProxy2SourceScreen1[0] = 0; + expectedSource2ProxyScreen1[0] = 0; + expectedSource2ProxyScreen0.clear(); + expectedProxy2SourceScreen0.clear(); + for (int i = 0; i < m_folderModel->rowCount(); i++) { + // as item 1 disappeared, the mapping of all items after that are shifted + auto proxyIndex = (i <= 1) ? i : i + 1; + expectedProxy2SourceScreen0[proxyIndex] = i; + expectedSource2ProxyScreen0[i] = proxyIndex; + } + + verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); + verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); + verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); + verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); + + // move back the same item to the first screen + screenMapper->addMapping(movedItem, 0); + + // nothing on the second screen + expectedSource2ProxyScreen1.clear(); + expectedProxy2SourceScreen1.clear(); + // first screen should look like in the beginning + expectedSource2ProxyScreen0 = savedSource2ProxyScreen0; + expectedProxy2SourceScreen0 = savedProxy2SourceScreen0; + + verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); + verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); + verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); + verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); +} + void PositionerTest::checkPositions(int perStripe) { QSignalSpy s(m_positioner, &Positioner::positionsChanged); diff --git a/containments/desktop/plugins/folder/autotests/foldermodeltest.h b/containments/desktop/plugins/folder/autotests/screenmappertest.h copy from containments/desktop/plugins/folder/autotests/foldermodeltest.h copy to containments/desktop/plugins/folder/autotests/screenmappertest.h --- a/containments/desktop/plugins/folder/autotests/foldermodeltest.h +++ b/containments/desktop/plugins/folder/autotests/screenmappertest.h @@ -2,6 +2,7 @@ * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * * * * Author: Andras Mantia * + * Work sponsored by the LiMux project of the city of Munich. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -19,39 +20,31 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ -#ifndef FOLDERMODELTEST_H -#define FOLDERMODELTEST_H +#ifndef SCREENMAPPERTEST_H +#define SCREENMAPPERTEST_H #include -class QTemporaryDir; -class FolderModel; +class ScreenMapper; -class FolderModelTest : public QObject +class ScreenMapperTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); - void cleanupTestCase(); - void init(); - void cleanup(); - void tst_listing(); - void tst_listingDescending(); - void tst_listingFolderNotFirst(); - void tst_filterListing(); - void tst_cd(); - void tst_rename_data(); - void tst_rename(); - void tst_selection(); - void tst_defaultValues(); - void tst_actionMenu(); - void tst_lockedChanged(); - -private: - FolderModel *m_folderModel; - QTemporaryDir *m_folderDir; + + void tst_addScreens(); + void tst_removeScreens(); + void tst_addMapping(); + void tst_addRemoveScreenWithItems(); + void tst_addRemoveScreenDifferentPaths(); + +private: + void addScreens(const QString &path); + + ScreenMapper *m_screenMapper; }; -#endif // FOLDERMODELTEST_H +#endif // SCREENMAPPERTEST_H diff --git a/containments/desktop/plugins/folder/autotests/screenmappertest.cpp b/containments/desktop/plugins/folder/autotests/screenmappertest.cpp new file mode 100644 --- /dev/null +++ b/containments/desktop/plugins/folder/autotests/screenmappertest.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** + * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * + * * + * Author: Andras Mantia * + * Work sponsored by the LiMux project of the city of Munich. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "screenmappertest.h" +#include "screenmapper.h" + +#include +#include + +QTEST_MAIN(ScreenMapperTest) + +void ScreenMapperTest::initTestCase() +{ + m_screenMapper = ScreenMapper::instance(); +} + +void ScreenMapperTest::init() +{ + m_screenMapper->cleanup(); +} + +void ScreenMapperTest::tst_addScreens() +{ + const auto path = QStringLiteral("desktop:/"); + QSignalSpy s(m_screenMapper, &ScreenMapper::screensChanged); + m_screenMapper->addScreen(-1, path); + QCOMPARE(s.count(), 0); + m_screenMapper->addScreen(1, path); + QCOMPARE(s.count(), 1); + m_screenMapper->addScreen(0, path); + QCOMPARE(s.count(), 2); + m_screenMapper->addScreen(1, path); + QCOMPARE(s.count(), 2); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0); +} + +void ScreenMapperTest::tst_removeScreens() +{ + const auto path = QStringLiteral("desktop:/"); + addScreens(path); + QSignalSpy s(m_screenMapper, &ScreenMapper::screensChanged); + m_screenMapper->removeScreen(-1, path); + QCOMPARE(s.count(), 0); + m_screenMapper->removeScreen(1, path); + QCOMPARE(s.count(), 1); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0); + m_screenMapper->removeScreen(1, path); + QCOMPARE(s.count(), 1); + m_screenMapper->addScreen(3, path); + QCOMPARE(s.count(), 2); + m_screenMapper->removeScreen(0, path); + QCOMPARE(s.count(), 3); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 2); +} + +void ScreenMapperTest::tst_addMapping() +{ + const auto path = QStringLiteral("desktop:/"); + addScreens(path); + QSignalSpy s(m_screenMapper, &ScreenMapper::screenMappingChanged); + QString file("desktop:/foo%1.txt"); + + for (int i = 0 ; i < 3; i++) { + const QString name = file.arg(i); + m_screenMapper->addMapping(name, i); + QCOMPARE(s.count(), i + 1); + QCOMPARE(m_screenMapper->screenForItem(name), i); + } +} + +void ScreenMapperTest::tst_addRemoveScreenWithItems() +{ + const auto path = QStringLiteral("desktop:/"); + addScreens(path); + QString file("desktop:/foo%1.txt"); + + for (int i = 0 ; i < 3; i++) { + const QString name = file.arg(i); + m_screenMapper->addMapping(name, i); + } + + // remove one screen + m_screenMapper->removeScreen(1, path); + QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0); + QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), -1); + QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2); + + // add removed screen back, items screen is restored + m_screenMapper->addScreen(1, path); + QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0); + QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), 1); + QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2); + + // remove all screens, firstAvailableScreen changes + m_screenMapper->removeScreen(0, path); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 1); + m_screenMapper->removeScreen(1, path); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 2); + m_screenMapper->removeScreen(2, path); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), -1); + + + QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), -1); + QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), -1); + QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), -1); + + // add all screens back, all item's screen is restored + addScreens(path); + QCOMPARE(m_screenMapper->screenForItem(file.arg(0)), 0); + QCOMPARE(m_screenMapper->screenForItem(file.arg(1)), 1); + QCOMPARE(m_screenMapper->screenForItem(file.arg(2)), 2); + + // remove one screen and move its item + const QString movedItem = file.arg(1); + m_screenMapper->removeScreen(1, path); + QCOMPARE(m_screenMapper->screenForItem(movedItem), -1); + m_screenMapper->addMapping(movedItem, 0); + QCOMPARE(m_screenMapper->screenForItem(movedItem), 0); + + // add back the screen, item goes back to the original place + m_screenMapper->addScreen(1, path); + QCOMPARE(m_screenMapper->screenForItem(movedItem), 1); +} + +void ScreenMapperTest::tst_addRemoveScreenDifferentPaths() +{ + const auto path = QStringLiteral("desktop:/Foo"); + const auto path2 = QStringLiteral("desktop:/Foo2"); + m_screenMapper->addScreen(0, path); + QCOMPARE(m_screenMapper->firstAvailableScreen(path), 0); + QCOMPARE(m_screenMapper->firstAvailableScreen(path2), -1); + +} + +void ScreenMapperTest::addScreens(const QString &path) +{ + m_screenMapper->addScreen(0, path); + m_screenMapper->addScreen(1, path); + m_screenMapper->addScreen(2, path); +} diff --git a/containments/desktop/plugins/folder/foldermodel.h b/containments/desktop/plugins/folder/foldermodel.h --- a/containments/desktop/plugins/folder/foldermodel.h +++ b/containments/desktop/plugins/folder/foldermodel.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,8 @@ class DropJob; } +class ScreenMapper; + class DirLister : public KDirLister { Q_OBJECT @@ -71,9 +74,10 @@ void handleError(KIO::Job *job) override; }; -class FOLDERPLUGIN_TESTS_EXPORT FolderModel : public QSortFilterProxyModel +class FOLDERPLUGIN_TESTS_EXPORT FolderModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT + Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged) Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged) @@ -94,6 +98,8 @@ Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged) Q_PROPERTY(QStringList filterMimeTypes READ filterMimeTypes WRITE setFilterMimeTypes NOTIFY filterMimeTypesChanged) Q_PROPERTY(QObject* newMenu READ newMenu CONSTANT) + Q_PROPERTY(ScreenMapper* screenMapper READ screenMapper WRITE setScreenMapper NOTIFY screenMapperChanged) + Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged); public: enum DataRole { @@ -130,6 +136,9 @@ QHash roleNames() const override; static QHash staticRoleNames(); + void classBegin() override; + void componentComplete() override; + QString url() const; void setUrl(const QString &url); @@ -180,6 +189,12 @@ QStringList filterMimeTypes() const; void setFilterMimeTypes(const QStringList &mimeList); + ScreenMapper* screenMapper() const; + void setScreenMapper(ScreenMapper* screenMapper); + + QObject *appletInterface() const; + void setAppletInterface(QObject *appletInterface); + KFileItem rootItem() const; Q_INVOKABLE void up(); @@ -233,6 +248,8 @@ Q_INVOKABLE void undo(); Q_INVOKABLE void refresh(); + void setScreen(int screen); + Q_SIGNALS: void urlChanged() const; void listingCompleted() const; @@ -254,6 +271,9 @@ void filterModeChanged() const; void filterPatternChanged() const; void filterMimeTypesChanged() const; + void screenChanged() const; + void screenMapperChanged() const; + void appletInterfaceChanged() const; void requestRename() const; void move(int x, int y, QList urls); void popupMenuAboutToShow(KIO::DropJob *dropJob, QMimeData *mimeData, int x, int y); @@ -274,6 +294,9 @@ void emptyTrashBin(); void restoreSelectedFromTrash(); void undoTextChanged(const QString &text); + void invalidateIfComplete(); + void invalidateFilterIfComplete(); + void newFileMenuItemCreated(const QUrl &url); private: struct DragImage { @@ -300,6 +323,10 @@ QPoint m_dragHotSpotScrollOffset; bool m_dragInProgress; bool m_urlChangedWhileDragging; + // target filename to target position of a drop event, note that this deliberately + // is not using the URL to easily support desktop:/ URL schemes + QHash m_dropTargetPositions; + QTimer *m_dropTargetPositionsCleanup; QPointer m_previewGenerator; QPointer m_viewAdapter; KActionCollection m_actionCollection; @@ -321,6 +348,11 @@ bool m_filterPatternMatchAll; QSet m_mimeSet; QList m_regExps; + int m_screen = -1; + ScreenMapper *m_screenMapper = nullptr; + QObject *m_appletInterface = nullptr; + bool m_complete; + QPoint m_menuPosition; }; #endif diff --git a/containments/desktop/plugins/folder/foldermodel.cpp b/containments/desktop/plugins/folder/foldermodel.cpp --- a/containments/desktop/plugins/folder/foldermodel.cpp +++ b/containments/desktop/plugins/folder/foldermodel.cpp @@ -24,6 +24,7 @@ #include "foldermodel.h" #include "itemviewadapter.h" #include "positioner.h" +#include "screenmapper.h" #include #include @@ -39,6 +40,8 @@ #include #include #include +#include +#include #include #include @@ -70,10 +73,16 @@ #include #include +#include +#include +#include + #include #include #include +Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel") + DirLister::DirLister(QObject *parent) : KDirLister(parent) { } @@ -96,6 +105,7 @@ m_dirWatch(nullptr), m_dragInProgress(false), m_urlChangedWhileDragging(false), + m_dropTargetPositionsCleanup(new QTimer(this)), m_previewGenerator(nullptr), m_viewAdapter(nullptr), m_actionCollection(this), @@ -109,7 +119,8 @@ m_parseDesktopFiles(false), m_previews(false), m_filterMode(NoFilter), - m_filterPatternMatchAll(true) + m_filterPatternMatchAll(true), + m_complete(false) { //needed to pass the job around with qml qmlRegisterType(); @@ -137,6 +148,44 @@ m_dirModel->setDirLister(dirLister); m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable); + /* + * position dropped items at the desired target position + * delay this via queued connection, such that the row is available and can be mapped + * when we emit the move request + */ + connect(this, &QAbstractItemModel::rowsInserted, + this, [this](const QModelIndex &parent, int first, int last) { + for (int i = first; i <= last; ++i) { + const auto idx = index(i, 0, parent); + const auto url = itemForIndex(idx).url(); + auto it = m_dropTargetPositions.find(url.fileName()); + if (it != m_dropTargetPositions.end()) { + const auto pos = it.value(); + m_dropTargetPositions.erase(it); + setSortMode(-1); + emit move(pos.x(), pos.y(), {url}); + } + } + }); + /* + * Dropped files may not actually show up as new files, e.g. when we overwrite + * an existing file. Or files that fail to be listed by the dirLister, or... + * To ensure we don't grow the map indefinitely, clean it up periodically. + * The cleanup timer is (re)started whenever we modify the map. We use a quite + * high interval of 10s. This should ensure, that we don't accidentally wipe + * the mapping when we actually still want to use it. Since the time between + * adding an entry in the map and it showing up in the model should be + * small, this should rarely, if ever happen. + */ + m_dropTargetPositionsCleanup->setInterval(10000); + m_dropTargetPositionsCleanup->setSingleShot(true); + connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() { + if (!m_dropTargetPositions.isEmpty()) { + qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions; + m_dropTargetPositions.clear(); + } + }); + m_selectionModel = new QItemSelectionModel(this, this); connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &FolderModel::selectionChanged); @@ -154,6 +203,12 @@ FolderModel::~FolderModel() { + if (m_screenMapper) { + // disconnect so we don't handle signals from the screen mapper when + // removeScreen is called + m_screenMapper->disconnect(this); + m_screenMapper->removeScreen(m_screen, url()); + } } QHash< int, QByteArray > FolderModel::roleNames() const @@ -180,6 +235,44 @@ return roleNames; } +void FolderModel::classBegin() +{ +} + +void FolderModel::componentComplete() +{ + m_complete = true; + invalidate(); +} + +void FolderModel::invalidateIfComplete() +{ + if (!m_complete) { + return; + } + + invalidate(); +} + +void FolderModel::invalidateFilterIfComplete() +{ + if (!m_complete) { + return; + } + + invalidateFilter(); +} + +void FolderModel::newFileMenuItemCreated(const QUrl &url) +{ + if (m_screenMapper) { + m_screenMapper->addMapping(url.toString(), m_screen, ScreenMapper::DelayedSignal); + m_dropTargetPositions.insert(url.fileName(), m_menuPosition); + m_menuPosition = {}; + m_dropTargetPositionsCleanup->start(); + } +} + QString FolderModel::url() const { return m_url; @@ -194,6 +287,8 @@ return; } + const auto oldUrl = m_url; + beginResetModel(); m_url = url; m_isDirCache.clear(); @@ -225,6 +320,11 @@ } emit iconNameChanged(); + + if (m_screenMapper) { + m_screenMapper->removeScreen(m_screen, oldUrl); + m_screenMapper->addScreen(m_screen, url); + } } QUrl FolderModel::resolvedUrl() const @@ -333,7 +433,7 @@ if (mode == -1 /* Unsorted */) { setDynamicSortFilter(false); } else { - invalidate(); + invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); setDynamicSortFilter(true); } @@ -353,7 +453,7 @@ m_sortDesc = desc; if (m_sortMode != -1 /* Unsorted */) { - invalidate(); + invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } @@ -372,7 +472,7 @@ m_sortDirsFirst = enable; if (m_sortMode != -1 /* Unsorted */) { - invalidate(); + invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } @@ -463,7 +563,7 @@ if (m_filterMode != (FilterMode)filterMode) { m_filterMode = (FilterMode)filterMode; - invalidateFilter(); + invalidateFilterIfComplete(); emit filterModeChanged(); } @@ -494,7 +594,7 @@ m_regExps.append(rx); } - invalidateFilter(); + invalidateFilterIfComplete(); emit filterPatternChanged(); } @@ -512,12 +612,24 @@ m_mimeSet = set; - invalidateFilter(); + invalidateFilterIfComplete(); emit filterMimeTypesChanged(); } } +void FolderModel::setScreen(int screen) +{ + if (m_screen == screen) + return; + + m_screen = screen; + if (m_usedByContainment && m_screenMapper) { + m_screenMapper->addScreen(screen, url()); + } + emit screenChanged(); +} + KFileItem FolderModel::rootItem() const { return m_dirModel->dirLister()->rootItem(); @@ -762,7 +874,7 @@ { DragImage *image = m_dragImages.value(row); if (!image) { - return QPoint(-1, -1); + return QPoint(0, 0); } return image->cursorOffset; @@ -884,27 +996,24 @@ } } +static bool isDropBetweenSharedViews(const QList &urls, const QUrl &folderUrl) +{ + for (const auto &url : urls) { + if (folderUrl != url.adjusted(QUrl::RemoveFilename)) { + return false; + } + } + return true; +} + void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row) { QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value()); if (!mimeData) { return; } - if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) { - if (m_locked || mimeData->urls().isEmpty()) { - return; - } - - setSortMode(-1); - - emit move(dropEvent->property("x").toInt(), dropEvent->property("y").toInt(), - mimeData->urls()); - - return; - } - QModelIndex idx; KFileItem item; @@ -934,6 +1043,50 @@ dropTargetUrl = item.mostLocalUrl(); } + auto dropTargetFolderUrl = dropTargetUrl; + if (dropTargetFolderUrl.fileName() == QLatin1String(".")) { + // the target URL for desktop:/ is e.g. 'file://home/user/Desktop/.' + dropTargetFolderUrl = dropTargetFolderUrl.adjusted(QUrl::RemoveFilename); + } + + // use dropTargetUrl to resolve desktop:/ to the actual file location which is also used by the mime data + /* QMimeData operates on local URLs, but the dir lister and thus screen mapper and positioner may + * use a fancy scheme like desktop:/ instead. Ensure we always use the latter to properly map URLs, + * i.e. go from file:///home/user/Desktop/file to desktop:/file + */ + auto mappableUrl = [this, dropTargetFolderUrl](const QUrl &url) -> QString { + QString mappedUrl = url.toString(); + if (dropTargetFolderUrl != m_dirModel->dirLister()->url()) { + const auto local = dropTargetFolderUrl.toString(); + const auto internal = m_dirModel->dirLister()->url().toString(); + if (mappedUrl.startsWith(local)) { + mappedUrl.replace(0, local.size(), internal); + } + } + return mappedUrl; + }; + + const int x = dropEvent->property("x").toInt(); + const int y = dropEvent->property("y").toInt(); + const QPoint dropPos = {x, y}; + + if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) { + if (m_locked || mimeData->urls().isEmpty()) { + return; + } + + setSortMode(-1); + + for (const auto &url : mimeData->urls()) { + m_dropTargetPositions.insert(url.fileName(), dropPos); + m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); + m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); + } + emit move(x, y, mimeData->urls()); + + return; + } + if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); @@ -954,37 +1107,88 @@ return; } - QPoint pos; - pos.setX(dropEvent->property("x").toInt()); - pos.setY(dropEvent->property("y").toInt()); - pos = target->mapToScene(pos).toPoint(); - pos = target->window()->mapToGlobal(pos); + if (m_usedByContainment) { + if (isDropBetweenSharedViews(mimeData->urls(), dropTargetFolderUrl)) { + setSortMode(-1); + if (m_screenMapper) { + for (const auto &url : mimeData->urls()) { + m_dropTargetPositions.insert(url.fileName(), dropPos); + m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); + m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); + } + } + m_dropTargetPositionsCleanup->start(); + return; + } + } Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); + auto pos = target->mapToScene(dropPos).toPoint(); + pos = target->window()->mapToGlobal(pos); QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers); ev.setDropAction(proposedAction); KIO::DropJob *dropJob = KIO::drop(&ev, dropTargetUrl); dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true); - const int x = dropEvent->property("x").toInt(); - const int y = dropEvent->property("y").toInt(); // The QMimeData we extract from the DropArea's drop event is deleted as soon as this method // ends but we need to keep a copy for when popupMenuAboutToShow fires. QMimeData *mimeCopy = new QMimeData(); for (const QString &format : mimeData->formats()) { mimeCopy->setData(format, mimeData->data(format)); } - connect(dropJob, static_cast(&KIO::DropJob::popupMenuAboutToShow), this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) { + connect(dropJob, &KIO::DropJob::popupMenuAboutToShow, this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) { emit popupMenuAboutToShow(dropJob, mimeCopy, x, y); mimeCopy->deleteLater(); }); + + /* + * Position files that come from a drag'n'drop event at the drop event + * target position. To do so, we first listen to copy job to figure out + * the target URL. Then we store the position of this drop event in the + * hash and eventually trigger a move request when we get notified about + * the new file event from the source model. + */ + connect(dropJob, &KIO::DropJob::copyJobStarted, this, [this, dropPos, dropTargetUrl](KIO::CopyJob* copyJob) { + auto map = [this, dropPos, dropTargetUrl](const QUrl &targetUrl) { + m_dropTargetPositions.insert(targetUrl.fileName(), dropPos); + m_dropTargetPositionsCleanup->start(); + + if (m_usedByContainment && m_screenMapper) { + // assign a screen for the item before the copy is actually done, so + // filterAcceptsRow doesn't assign the default screen to it + QUrl url = QUrl::fromUserInput(m_url, {}, QUrl::AssumeLocalFile); + // if the folderview's folder is a standard path, just use the targetUrl for mapping + if (targetUrl.toString().startsWith(url.toString())) { + m_screenMapper->addMapping(targetUrl.toString(), m_screen, ScreenMapper::DelayedSignal); + } else if (targetUrl.toString().startsWith(dropTargetUrl.toString())) { + // if the folderview's folder is a special path, like desktop:// , we need to convert + // the targetUrl file:// path to a desktop:/ path for mapping + auto destPath = dropTargetUrl.path(); + auto filePath = targetUrl.path(); + if (filePath.startsWith(destPath)) { + url.setPath(filePath.remove(0, destPath.length())); + m_screenMapper->addMapping(url.toString(), m_screen, ScreenMapper::DelayedSignal); + } + } + } + }; + // remember drop target position for target URL and forget about the source URL + connect(copyJob, &KIO::CopyJob::copyingDone, + this, [this, map](KIO::Job *, const QUrl &, const QUrl &targetUrl, const QDateTime &, bool, bool) { + map(targetUrl); + }); + connect(copyJob, &KIO::CopyJob::copyingLinkDone, + this, [this, map](KIO::Job *, const QUrl &, const QString &, const QUrl &targetUrl) { + map(targetUrl); + }); + }); } void FolderModel::dropCwd(QObject* dropEvent) @@ -1165,6 +1369,9 @@ void FolderModel::evictFromIsDirCache(const KFileItemList& items) { foreach (const KFileItem &item, items) { + if (m_screenMapper) { + m_screenMapper->removeFromMap(item.url().toString()); + } m_isDirCache.remove(item.url()); } } @@ -1286,13 +1493,34 @@ bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + const KDirModel *dirModel = static_cast(sourceModel()); + const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent)); + + if (m_usedByContainment && m_screenMapper) { + const QString name = item.url().toString(); + const int screen = m_screenMapper->screenForItem(name); + // don't do anything if the folderview is not associated with a screen + if (m_screen != -1) { + if (screen == -1) { + // The item is not associated with a screen, probably because this is the first + // time we see it or the folderview was previously used as a regular applet. + // Associated with this folderview if the view is on the first available screen + if (m_screen == m_screenMapper->firstAvailableScreen(url())) { + m_screenMapper->addMapping(name, m_screen, ScreenMapper::DelayedSignal); + } else { + return false; + } + } else if (m_screen != screen) { + // the item belongs to a different screen, filter it out + return false; + } + } + } + if (m_filterMode == NoFilter) { return true; } - const KDirModel *dirModel = static_cast(sourceModel()); - const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent)); - if (m_filterMode == FilterShowMatches) { return (matchPattern(item) && matchMimeType(item)); } else { @@ -1356,6 +1584,8 @@ m_newMenu = new KNewFileMenu(&m_actionCollection, QStringLiteral("newMenu"), QApplication::desktop()); m_newMenu->setModal(false); + connect(m_newMenu, &KNewFileMenu::directoryCreated, this, &FolderModel::newFileMenuItemCreated); + connect(m_newMenu, &KNewFileMenu::fileCreated, this, &FolderModel::newFileMenuItemCreated); m_copyToMenu = new KFileCopyToMenu(nullptr); } @@ -1375,6 +1605,8 @@ if (m_newMenu) { m_newMenu->checkUpToDate(); m_newMenu->setPopupFiles(m_dirModel->dirLister()->url()); + // we need to set here as well, when the menu is shown via AppletInterface::eventFilter + m_menuPosition = QCursor::pos(); } const bool isTrash = (resolvedUrl().scheme() == QLatin1String("trash")); @@ -1548,10 +1780,11 @@ } if (visualParent) { - menu->popup(visualParent->mapToGlobal(QPointF(0, visualParent->height())).toPoint()); + m_menuPosition = visualParent->mapToGlobal(QPointF(0, visualParent->height())).toPoint(); } else { - menu->popup(QCursor::pos()); + m_menuPosition = QCursor::pos(); } + menu->popup(m_menuPosition); connect(menu, &QMenu::aboutToHide, [menu]() { menu->deleteLater(); }); } @@ -1620,6 +1853,66 @@ m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url()); } +ScreenMapper *FolderModel::screenMapper() const +{ + return m_screenMapper; +} + +void FolderModel::setScreenMapper(ScreenMapper *screenMapper) +{ + if (m_screenMapper == screenMapper) + return; + + Q_ASSERT(!m_screenMapper); + + if (m_screenMapper) { + m_screenMapper->disconnect(this); + } + + m_screenMapper = screenMapper; + if (m_screenMapper) { + connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete); + connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete); + } + + invalidateFilterIfComplete(); + emit screenMapperChanged(); +} + +QObject *FolderModel::appletInterface() const +{ + return m_appletInterface; +} + +void FolderModel::setAppletInterface(QObject *appletInterface) +{ + if (m_appletInterface != appletInterface) { + Q_ASSERT(!m_appletInterface); + + m_appletInterface = appletInterface; + + if (appletInterface) { + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + + if (applet) { + Plasma::Containment *containment = applet->containment(); + + if (containment) { + Plasma::Corona *corona = containment->corona(); + + if (corona && m_screenMapper) { + m_screenMapper->setCorona(corona); + } + setScreen(containment->screen()); + connect(containment, &Plasma::Containment::screenChanged, this, &FolderModel::setScreen); + } + } + } + + emit appletInterfaceChanged(); + } +} + void FolderModel::moveSelectedToTrash() { if (!m_selectionModel->hasSelection()) { diff --git a/containments/desktop/plugins/folder/folderplugin.cpp b/containments/desktop/plugins/folder/folderplugin.cpp --- a/containments/desktop/plugins/folder/folderplugin.cpp +++ b/containments/desktop/plugins/folder/folderplugin.cpp @@ -32,15 +32,28 @@ #include "viewpropertiesmenu.h" #include "wheelinterceptor.h" #include "shortcut.h" +#include "screenmapper.h" +#include +#include static QObject *menuHelperSingletonProvider(QQmlEngine *engine, QJSEngine *jsEngine) { Q_UNUSED(engine); Q_UNUSED(jsEngine); return new MenuHelper(); } +static QObject *screenMapperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(scriptEngine); + + QObject *mapper = ScreenMapper::instance(); + engine->setObjectOwnership(mapper, QQmlEngine::CppOwnership); + return mapper; +} + + void FolderPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.private.desktopcontainment.folder")); @@ -58,5 +71,6 @@ qmlRegisterType(uri, 0, 1, "ViewPropertiesMenu"); qmlRegisterType(uri, 0, 1, "WheelInterceptor"); qmlRegisterType(uri, 0, 1, "ShortCut"); + qmlRegisterSingletonType(uri, 0, 1, "ScreenMapper", screenMapperProvider); } 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/containments/desktop/plugins/folder/positioner.h b/containments/desktop/plugins/folder/positioner.h --- a/containments/desktop/plugins/folder/positioner.h +++ b/containments/desktop/plugins/folder/positioner.h @@ -76,6 +76,15 @@ int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; +#ifdef BUILD_TESTING + QHash proxyToSourceMapping() const { + return m_proxyToSource; + } + QHash sourceToProxyMapping() const { + return m_sourceToProxy; + } +#endif + Q_SIGNALS: void enabledChanged() const; void folderModelChanged() const; @@ -128,6 +137,7 @@ QHash m_proxyToSource; QHash m_sourceToProxy; + bool m_beginInsertRowsCalled = false; // used to sync the amount of begin/endInsertRows calls }; #endif diff --git a/containments/desktop/plugins/folder/positioner.cpp b/containments/desktop/plugins/folder/positioner.cpp --- a/containments/desktop/plugins/folder/positioner.cpp +++ b/containments/desktop/plugins/folder/positioner.cpp @@ -399,7 +399,10 @@ } if (!fromIndices.contains(to) && !isBlank(to)) { - continue; + // find the next blank space + while (!isBlank(to) && from != to) { + to++; + } } toIndices[i] = to; @@ -422,6 +425,10 @@ const int newCount = rowCount(); if (newCount > oldCount) { + if (m_beginInsertRowsCalled) { + endInsertRows(); + m_beginInsertRowsCalled = false; + } beginInsertRows(QModelIndex(), oldCount, newCount - 1); endInsertRows(); } @@ -507,14 +514,28 @@ if (m_enabled) { if (m_proxyToSource.isEmpty()) { if (!m_pendingPositions) { - emit beginInsertRows(parent, start, end); + beginInsertRows(parent, start, end); + m_beginInsertRowsCalled = true; initMaps(end + 1); } return; } + // When new rows are inserted, they might go in the beginning or in the middle. + // In this case we must update first the existing proxy->source and source->proxy + // mapping, otherwise the proxy items will point to the wrong source item. + int count = end - start + 1; + m_sourceToProxy.clear(); + for (auto it = m_proxyToSource.begin(); it != m_proxyToSource.end(); ++it) { + int sourceIdx = *it; + if (sourceIdx >= start) { + *it += count; + } + m_sourceToProxy[*it] = it.key(); + } + int free = -1; int rest = -1; @@ -535,6 +556,7 @@ int remainder = (end - rest); beginInsertRows(parent, firstNew, firstNew + remainder); + m_beginInsertRowsCalled = true; for (int i = 0; i <= remainder; ++i) { updateMaps(firstNew + i, rest + i); @@ -544,6 +566,8 @@ } } else { emit beginInsertRows(parent, start, end); + beginInsertRows(parent, start, end); + m_beginInsertRowsCalled = true; } } @@ -614,7 +638,10 @@ if (!m_ignoreNextTransaction) { if (!m_pendingPositions) { - emit endInsertRows(); + if (m_beginInsertRowsCalled) { + endInsertRows(); + m_beginInsertRowsCalled = false; + } } else { applyPositions(); } diff --git a/containments/desktop/plugins/folder/screenmapper.h b/containments/desktop/plugins/folder/screenmapper.h new file mode 100644 --- /dev/null +++ b/containments/desktop/plugins/folder/screenmapper.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * + * * + * Author: Andras Mantia * + * Work sponsored by the LiMux project of the city of Munich. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ +#ifndef SCREENMAPPER_H +#define SCREENMAPPER_H + +#include +#include +#include + +#include "folderplugin_private_export.h" + +class QTimer; + +namespace Plasma { + class Corona; +} + +class FOLDERPLUGIN_TESTS_EXPORT ScreenMapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList screenMapping READ screenMapping WRITE setScreenMapping NOTIFY screenMappingChanged) + +public: + enum MappingSignalBehavior { + DelayedSignal = 0, + ImmediateSignal + }; + + static ScreenMapper *instance(); + ~ScreenMapper() override = default; + + QStringList screenMapping() const; + void setScreenMapping(const QStringList &mapping); + + int screenForItem(const QString &name) const; + void addMapping(const QString &name, int screen, MappingSignalBehavior behavior = ImmediateSignal); + void removeFromMap(const QString &name); + void setCorona(Plasma::Corona *corona); + + void addScreen(int screenId, const QString &path); + void removeScreen(int screenId, const QString &path); + int firstAvailableScreen(const QString &path) const; + void removeItemFromDisabledScreen(const QString &name); + +#ifdef BUILD_TESTING + void cleanup(); +#endif + +Q_SIGNALS: + void screenMappingChanged() const; + void screensChanged() const; + +private: + ScreenMapper(QObject *parent = nullptr); + + QHash m_screenItemMap; + QHash m_itemsOnDisabledScreensMap; + QHash m_firstScreenForPath; // first available screen for a path + QHash m_screensPerPath; // screen per registered path + QVector m_availableScreens; + Plasma::Corona *m_corona = nullptr; + QTimer *m_screenMappingChangedTimer = nullptr; +}; + +#endif // SCREENMAPPER_H diff --git a/containments/desktop/plugins/folder/screenmapper.cpp b/containments/desktop/plugins/folder/screenmapper.cpp new file mode 100644 --- /dev/null +++ b/containments/desktop/plugins/folder/screenmapper.cpp @@ -0,0 +1,255 @@ +/*************************************************************************** + * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * + * * + * Author: Andras Mantia * + * Work sponsored by the LiMux project of the city of Munich. * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ +#include "screenmapper.h" + +#include +#include + +#include +#include +#include + +ScreenMapper *ScreenMapper::instance() +{ + static ScreenMapper *s_instance = new ScreenMapper(); + return s_instance; +} + +ScreenMapper::ScreenMapper(QObject *parent) + : QObject(parent) + , m_screenMappingChangedTimer(new QTimer(this)) + +{ + connect(m_screenMappingChangedTimer, &QTimer::timeout, + this, &ScreenMapper::screenMappingChanged); + + connect(this, &ScreenMapper::screenMappingChanged, this, [this] { + if (!m_corona) + return; + + auto config = m_corona->config(); + KConfigGroup group(config, QLatin1String("ScreenMapping")); + group.writeEntry(QLatin1String("screenMapping"), screenMapping()); + config->sync(); + }); + + // used to compress screenMappingChanged signals when addMapping is called multiple times, + // eg. from FolderModel::filterAcceptRows. The timer interval is an arbitrary number, + // that doesn't delay too much the signal, but still compresses as much as possible + m_screenMappingChangedTimer->setInterval(100); + m_screenMappingChangedTimer->setSingleShot(true); +} + +void ScreenMapper::removeScreen(int screenId, const QString &path) +{ + if (screenId < 0 || !m_availableScreens.contains(screenId)) + return; + + QUrl screenUrl = QUrl::fromUserInput(path, {}, QUrl::AssumeLocalFile); + const auto screenPathWithScheme = screenUrl.url(); + // store the original location for the items + auto it = m_screenItemMap.constBegin(); + while (it != m_screenItemMap.constEnd()) { + const auto name = it.key(); + if (it.value() == screenId && name.startsWith(screenPathWithScheme)) { + m_itemsOnDisabledScreensMap[screenId].append(name); + } + ++it; + } + + m_availableScreens.removeAll(screenId); + + const auto newFirstScreen = std::min_element(m_availableScreens.constBegin(), m_availableScreens.constEnd()); + auto pathIt = m_screensPerPath.find(path); + if (pathIt != m_screensPerPath.end() && pathIt.value() > 0) { + int firstScreen = m_firstScreenForPath.value(path, -1); + if (firstScreen == screenId) { + m_firstScreenForPath[path] = (newFirstScreen == m_availableScreens.constEnd()) ? -1 : *newFirstScreen; + } + *pathIt = pathIt.value() - 1; + } else if (path.isEmpty()) { + // The screen got completely removed, not only its path changed. + // If the removed screen was the first screen for a desktop path, the first screen for that path + // needs to be updated. + for (auto it = m_firstScreenForPath.begin(); it != m_firstScreenForPath.end(); ++it) { + if (*it == screenId) { + *it = *newFirstScreen; + + // we have now the path for the screen that was removed, so adjust it + pathIt = m_screensPerPath.find(it.key()); + if (pathIt != m_screensPerPath.end()) { + *pathIt = pathIt.value() - 1; + } + } + } + } + + emit screensChanged(); +} + +void ScreenMapper::addScreen(int screenId, const QString &path) +{ + if (screenId < 0 || m_availableScreens.contains(screenId)) + return; + + QUrl screenUrl = QUrl::fromUserInput(path, {}, QUrl::AssumeLocalFile); + const auto screenPathWithScheme = screenUrl.url(); + const bool isEmpty = (path.isEmpty() || screenUrl.path() == "/"); + // restore the stored locations + auto it = m_itemsOnDisabledScreensMap.find(screenId); + if (it != m_itemsOnDisabledScreensMap.end()) { + auto items = it.value(); + for (const auto &name: it.value()) { + // add the items to the new screen, if they are on a disabled screen and their + // location is below the new screen's path + if (isEmpty || name.startsWith(screenPathWithScheme)) { + addMapping(name, screenId, DelayedSignal); + items.removeAll(name); + } + } + if (items.isEmpty()) { + m_itemsOnDisabledScreensMap.erase(it); + } else { + *it = items; + } + } + + m_availableScreens.append(screenId); + + // path is empty when a new screen appears that has no folderview base path associated with + if (!path.isEmpty()) { + auto it = m_screensPerPath.find(path); + int firstScreen = m_firstScreenForPath.value(path, -1); + if (firstScreen == -1 || screenId < firstScreen) { + m_firstScreenForPath[path] = screenId; + } + if (it == m_screensPerPath.end()) { + m_screensPerPath[path] = 1; + } else { + *it = it.value() + 1; + } + } + + emit screensChanged(); +} + +void ScreenMapper::addMapping(const QString &name, int screen, MappingSignalBehavior behavior) +{ + m_screenItemMap[name] = screen; + if (behavior == DelayedSignal) { + m_screenMappingChangedTimer->start(); + } else { + emit screenMappingChanged(); + } +} + +void ScreenMapper::removeFromMap(const QString &name) +{ + m_screenItemMap.remove(name); + m_screenMappingChangedTimer->start(); +} + +int ScreenMapper::firstAvailableScreen(const QString &path) const +{ + return m_firstScreenForPath.value(path, -1); +} + +void ScreenMapper::removeItemFromDisabledScreen(const QString &name) +{ + for (auto it = m_itemsOnDisabledScreensMap.begin(); + it != m_itemsOnDisabledScreensMap.end(); ++it) { + auto names = &(*it); + names->removeAll(name); + } +} + +#ifdef BUILD_TESTING +void ScreenMapper::cleanup() +{ + m_screenItemMap.clear(); + m_itemsOnDisabledScreensMap.clear(); + m_firstScreenForPath.clear(); + m_screensPerPath.clear(); + m_availableScreens.clear(); +} +#endif + +void ScreenMapper::setCorona(Plasma::Corona *corona) +{ + if (m_corona != corona) { + Q_ASSERT(!m_corona); + + m_corona = corona; + if (m_corona) { + connect(m_corona, &Plasma::Corona::screenRemoved, this, [this] (int screenId) { + removeScreen(screenId, {}); + }); + connect(m_corona, &Plasma::Corona::screenAdded, this, [this] (int screenId) { + addScreen(screenId, {}); + }); + + auto config = m_corona->config(); + KConfigGroup group(config, QLatin1String("ScreenMapping")); + const QStringList mapping = group.readEntry(QLatin1String("screenMapping"), QStringList{}); + setScreenMapping(mapping); + } + } +} + +QStringList ScreenMapper::screenMapping() const +{ + QStringList result; + result.reserve(m_screenItemMap.count() * 2); + auto it = m_screenItemMap.constBegin(); + while (it != m_screenItemMap.constEnd()) { + result.append(it.key()); + result.append(QString::number(it.value())); + ++it; + } + + return result; +} + +void ScreenMapper::setScreenMapping(const QStringList &mapping) +{ + QHash newMap; + const int count = mapping.count(); + newMap.reserve(count / 2); + for (int i = 0; i < count - 1; i += 2) { + if (i + 1 < count) { + newMap[mapping[i]] = mapping[i + 1].toInt(); + } + } + + if (m_screenItemMap != newMap) { + m_screenItemMap = newMap; + emit screenMappingChanged(); + } +} + +int ScreenMapper::screenForItem(const QString &name) const +{ + int screen = m_screenItemMap.value(name, -1); + if (!m_availableScreens.contains(screen)) + screen = -1; + + return screen; +} diff --git a/containments/panel/contents/ui/main.qml b/containments/panel/contents/ui/main.qml --- a/containments/panel/contents/ui/main.qml +++ b/containments/panel/contents/ui/main.qml @@ -246,7 +246,9 @@ //BEGIN components Component { id: appletContainerComponent - Item { + // This loader conditionally manages the BusyIndicator, it's not + // loading the applet. The applet becomes a regular child item. + Loader { id: container visible: false property bool animationsEnabled: true @@ -281,27 +283,21 @@ property int oldY: y property Item applet + onAppletChanged: { if (!applet) { destroy(); } } + active: applet && applet.busy + sourceComponent: PlasmaComponents.BusyIndicator {} + Layout.onMinimumWidthChanged: movingForResize = true; Layout.onMinimumHeightChanged: movingForResize = true; Layout.onMaximumWidthChanged: movingForResize = true; Layout.onMaximumHeightChanged: movingForResize = true; - Loader { - z: 1000 - anchors.centerIn: parent - active: applet && applet.busy - sourceComponent: PlasmaComponents.BusyIndicator { - width: Math.min(container.width, container.height) - height: width - } - } - onXChanged: { if (movingForResize) { movingForResize = false; diff --git a/desktoppackage/contents/configuration/ConfigCategoryDelegate.qml b/desktoppackage/contents/configuration/ConfigCategoryDelegate.qml --- a/desktoppackage/contents/configuration/ConfigCategoryDelegate.qml +++ b/desktoppackage/contents/configuration/ConfigCategoryDelegate.qml @@ -58,7 +58,7 @@ //END functions //BEGIN connections - onClicked: { + onPressed: { categoriesScroll.forceActiveFocus() if (current) { diff --git a/desktoppackage/contents/views/Panel.qml b/desktoppackage/contents/views/Panel.qml --- a/desktoppackage/contents/views/Panel.qml +++ b/desktoppackage/contents/views/Panel.qml @@ -54,7 +54,7 @@ prefix = ""; return; } - prefix = [prefix, ""]; + prefix = [pre, ""]; } onContainmentChanged: { diff --git a/doc/kcontrol/colors/index.docbook b/doc/kcontrol/colors/index.docbook --- a/doc/kcontrol/colors/index.docbook +++ b/doc/kcontrol/colors/index.docbook @@ -14,8 +14,8 @@ -2016-09-20 -Plasma 5.8 +2017-11-07 +Plasma 5.11 KDE @@ -80,16 +80,16 @@ assigning colors. - Apply inactive window color effects + Apply effects to inactive windows — If checked, state effects (see below) will be applied to inactive windows. This can help visually identify active versus inactive windows, and may have aesthetic value, depending on your taste. However, some users feel that it causes distracting "flickering" since windows must be repainted when they become inactive. Unlike desktop effects, color state effects do not require compositing support and will work on all systems, however they will only work on &kde; applications. - Inactive selection changes color + Use different colors for inactive selections — If checked, the current selection in elements which do not have input focus will be drawn using a different color. This can assist visual identification of the element with input focus in some applications, diff --git a/doc/kcontrol/icons/index.docbook b/doc/kcontrol/icons/index.docbook --- a/doc/kcontrol/icons/index.docbook +++ b/doc/kcontrol/icons/index.docbook @@ -15,8 +15,8 @@ -2016-09-21 -Plasma 5.8 +2017-11-07 +Plasma 5.11 KDE @@ -221,7 +221,7 @@ You can also choose animated icons. Many of the icons have animations associated with them. Enable the checkbox labelled -Animate Icons, to enable this effect, but note +Enable icon animations, to enable this effect, but note that it may appear slow or jerky if your graphics card is old or you are low on memory. diff --git a/doc/kcontrol/kcmaccess/index.docbook b/doc/kcontrol/kcmaccess/index.docbook --- a/doc/kcontrol/kcmaccess/index.docbook +++ b/doc/kcontrol/kcmaccess/index.docbook @@ -69,14 +69,14 @@ For those users who have difficulty hearing the System bell, or those -users who have a silent computer, &kde; offers the visible bell. This +users who have a silent computer, &kde; offers the visual bell. This provides a visual signal (inverting the screen or flashing a color across it) when the system bell would normally sound. -To use the visible bell, first place a mark in the check box labeled -Use visible bell. +To use the visual bell, first place a mark in the check box labeled +Use visual bell. diff --git a/doc/kcontrol/kcmstyle/index.docbook b/doc/kcontrol/kcmstyle/index.docbook --- a/doc/kcontrol/kcmstyle/index.docbook +++ b/doc/kcontrol/kcmstyle/index.docbook @@ -14,8 +14,8 @@ -2017-04-01 -Plasma 5.9 +2017-11-07 +Plasma 5.11 KDE @@ -73,12 +73,12 @@ -Show icons on buttons +Show icons in buttons If this option is selected, action buttons (like OK and Apply) will have a small icon located within them to act as a visual reference. If this option is not selected, then only text -will appear on the button. +will appear in the button. @@ -93,7 +93,7 @@ -Main toolbar text, Secondary toolbar text +Main toolbar text location, Secondary toolbar text location These drop down boxes lets you determine where on the button in both toolbars the text name of the button will appear as the default. diff --git a/kaccess/kaccess.notifyrc b/kaccess/kaccess.notifyrc --- a/kaccess/kaccess.notifyrc +++ b/kaccess/kaccess.notifyrc @@ -177,7 +177,7 @@ Comment[bg]=Клавиш-модификатор (напр. Shift или Ctrl) промени състоянието си и е активен Comment[bn]=একটি মডিফায়ার কী (e.g. Shift বা Ctrl) অবস্থা পরিবর্তন করে এখন সক্রিয় হয়েছে Comment[bs]=Modifikatorski taster (npr. Shift ili Ctrl) promijenio je stanje i sada je aktivan -Comment[ca]=Ha canviat l'estat d'una tecla modificadora (p.ex. Maj o Ctrl) i ara està activa +Comment[ca]=Ha canviat l'estat d'una tecla modificadora (p. ex. Maj o Ctrl) i ara està activa Comment[ca@valencia]=Ha canviat l'estat d'una tecla modificadora (p.ex. Maj o Ctrl) i ara està activa Comment[cs]=Modifikátor klávesnice (Shift nebo Ctrl) změnil stav a je právě aktivní Comment[csb]=Klawisza zjinaczi (Shift abò Control) zmienia swój sztatus ë je aktiwòwónô @@ -334,7 +334,7 @@ Comment[bg]=Клавиш-модификатор (напр. Shift или Ctrl) промени състоянието си и е изключен Comment[bn]=একটি মডিফায়ার কী (e.g. Shift বা Ctrl) অবস্থা পরিবর্তন করে এখন নিষ্ক্রীয় হয়েছে Comment[bs]=Modifikatorski taster (npr. Shift ili Ctrl) je promijenio stanje i nije više aktivan -Comment[ca]=Ha canviat l'estat d'una tecla modificadora (p.ex. Maj o Ctrl) i ara està inactiva +Comment[ca]=Ha canviat l'estat d'una tecla modificadora (p. ex. Maj o Ctrl) i ara està inactiva Comment[ca@valencia]=Ha canviat l'estat d'una tecla modificadora (p.ex. Maj o Ctrl) i ara està inactiva Comment[cs]=Modifikátor klávesnice (Shift nebo Ctrl) změnil stav a je právě neaktivní Comment[csb]=Klawisza zjinaczi (Shift abò Control) zmienia swój sztatus ë nie je ju aktiwnô @@ -491,7 +491,7 @@ Comment[bg]=Клавиш-модификатор (напр. Shift или Ctrl) е заключен и е активен за всички последващи натискания на клавиши Comment[bn]=একটি মডিফায়ার কী (e.g. Shift বা Ctrl) লক করা হয়েছে এবং পরবর্তী সবকটি কী-র (key) জন্য সক্রিয় থাকবে Comment[bs]=Modifikatorski taster (npr. Shift ili Ctrl) upravo je zaključan i od sada aktivan za sve pritiske na tastere -Comment[ca]=S'ha fixat alguna tecla modificadora (p.ex. Maj o Ctrl) i ara està activa per a totes les tecles que es premin a continuació +Comment[ca]=S'ha fixat alguna tecla modificadora (p. ex. Maj o Ctrl) i ara està activa per a totes les tecles que es premin a continuació Comment[ca@valencia]=S'ha fixat alguna tecla modificadora (p.ex. Maj o Ctrl) i ara està activa per a totes les tecles que es premen a continuació Comment[cs]=Modifikátor klávesnice (např. Shift nebo Ctrl) je zamčený a je nyní aktivní pro všechny následující stisknuté klávesy Comment[csb]=Klawisza zjinaczi (Shift abò Control) òsta zablokòwónô ë je terô dlô pòsobnegò wcësniącô klawiszów aktiwnô @@ -649,7 +649,7 @@ Comment[bg]=Клавиш за превключване на режим (напр. Caps Lock или Num Lock) промени състоянието си и е активен Comment[bn]=একটি লক কী (e.g. Caps Lock বা Num Lock) অবস্থা পরিবর্তন করে এখন সক্রিয় হয়েছে Comment[bs]=Zaključavajući taster (npr. CapsLock ili NumLock) promijenio je stanje i sada je aktivan -Comment[ca]=Alguna tecla de fixació (p.ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està activa +Comment[ca]=Alguna tecla de fixació (p. ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està activa Comment[ca@valencia]=Alguna tecla de fixació (p.ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està activa Comment[cs]=Klávesa pro přepnutí stavu (např. CapsLock nebo NumLock) byla stisknuta a je nyní aktivní Comment[csb]=Klawisza blokadë (Caps Lock abò Num Lock) zmienia swój sztatus ë je terô aktiwnô @@ -807,7 +807,7 @@ Comment[bg]=Клавиш за превключване на режим (напр. Caps Lock или Num Lock) промени състоянието си и е неактивен Comment[bn]=একটি লক কী (e.g. Caps Lock বা Num Lock) অবস্থা পরিবর্তন করে এখন নিষ্ক্রীয় হয়েছে Comment[bs]=Zaključavajući taster (npr. CapsLock ili NumLock) promijenio je stanje i više nije aktivan -Comment[ca]=Alguna tecla de fixació (p.ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està inactiva +Comment[ca]=Alguna tecla de fixació (p. ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està inactiva Comment[ca@valencia]=Alguna tecla de fixació (p.ex. Bloq Maj o Bloq Núm) ha canviat el seu estat i ara està inactiva Comment[cs]=Klávesa pro přepnutí stavu (např. CapsLock nebo NumLock) byla stisknuta a je nyní neaktivní Comment[csb]=Klawisza blokadë (Caps Lock abò Num Lock) zmienia swój sztatus ë nie je ju aktiwnô @@ -893,7 +893,7 @@ Name[el]=Τα κολλημένα πλήκτρα ενεργοποιήθηκαν ή απενεργοποιήθηκαν Name[en_GB]=Sticky keys has been enabled or disabled Name[eo]=Fiksaj klavoj validiĝis aŭ malvalidiĝis -Name[es]=Las teclas pegajosas se han activado o desactivado +Name[es]=Las teclas adhesivas se han activado o desactivado Name[et]=Kleepuvad klahvid on keelatud või lubatud Name[eu]=Tekla itsaskorrak gaitu edo desgaitu dira Name[fi]=Alas jäävät näppäimet on otettu käyttöön tai poistettu käytöstä @@ -969,7 +969,7 @@ Comment[el]=Τα κολλημένα πλήκτρα ενεργοποιήθηκαν ή απενεργοποιήθηκαν Comment[en_GB]=Sticky keys has been enabled or disabled Comment[eo]=Fiksaj klavoj validiĝis aŭ malvalidiĝis -Comment[es]=Las teclas pegajosas se han activado o desactivado +Comment[es]=Las teclas adhesivas se han activado o desactivado Comment[et]=Kleepuvad klahvid on keelatud või lubatud Comment[eu]=Tekla itsaskorrak gaitu edo desgaitu dira Comment[fi]=Alas jäävät näppäimet on otettu käyttöön tai poistettu käytöstä diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt --- a/kcms/CMakeLists.txt +++ b/kcms/CMakeLists.txt @@ -14,7 +14,7 @@ add_subdirectory( keyboard ) endif() -if (EVDEV_FOUND AND X11_Xinput_FOUND) +if (EVDEV_FOUND AND XORGLIBINPUT_FOUND AND X11_Xinput_FOUND) add_subdirectory( input ) endif() @@ -32,6 +32,7 @@ add_subdirectory( keys ) add_subdirectory( ksmserver ) add_subdirectory( lookandfeel ) +add_subdirectory( nightcolor ) add_subdirectory( hardware ) add_subdirectory( desktoppaths ) diff --git a/kcms/access/accessibility.ui b/kcms/access/accessibility.ui --- a/kcms/access/accessibility.ui +++ b/kcms/access/accessibility.ui @@ -103,16 +103,16 @@ - Visible Bell + Visual Bell - This option will turn on the "visible bell", i.e. a visible notification shown every time that normally just a bell would occur. This is especially useful for deaf people. + This option will turn on the "visual bell", i.e. a visual notification shown every time that normally just a bell would occur. This is especially useful for deaf people. - &Use visible bell + &Use visual bell diff --git a/kcms/access/kcmaccess.cpp b/kcms/access/kcmaccess.cpp --- a/kcms/access/kcmaccess.cpp +++ b/kcms/access/kcmaccess.cpp @@ -508,7 +508,6 @@ */ Q_DECL_EXPORT void kcminit_access() { - KConfig config(QStringLiteral("kaccessrc"), KConfig::NoGlobals); KToolInvocation::startServiceByDesktopName(QStringLiteral("kaccess")); } } diff --git a/kcms/access/kcmaccess.desktop b/kcms/access/kcmaccess.desktop --- a/kcms/access/kcmaccess.desktop +++ b/kcms/access/kcmaccess.desktop @@ -154,48 +154,28 @@ Comment[zh_CN]=辅助功能选项 Comment[zh_TW]=無障礙輔助選項 -X-KDE-Keywords=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys -X-KDE-Keywords[bs]=pristup,pristupačnost,gluh,oštećen,sluh,gubitak sluha,zvono,zvučno zvono,vidljivo zvono,tastatura,tipke,ljepljive tipke,odskočne tipke,usporene tipke,navigacija uz pomoć miša,numerički dio, -X-KDE-Keywords[ca]=accés,accessibilitat,sord,discapacitat,oïda,pèrdua d'oïda,campana,campana audible,campana visible,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[ca@valencia]=accés,accessibilitat,sord,discapacitat,oïda,pèrdua d'oïda,campana,campana audible,campana visible,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[da]=tilgang,tilgængelighed,døv,hæmmet,handicappet,hørelse,hørehæmmet,klokke,hørbar klokke,synlig klokke,tastatur,taster,sticky keys,elastiske taster,bounce,langsomme taster,musenavigation,numerisk tastatur,aktiveringsgestusser,gestusser,klæbende,ændringstaster,låsetaster +X-KDE-Keywords=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys +X-KDE-Keywords[ca]=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[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[de]=Behinderung,Maussteuerung,Signale,Tastatur,Tasten,Klebende Tasten,Taubheit,Verlangsamte Tasten,Zahlenblock,Zugang,Zugangshilfen,Zahlentasten,Gesten aktivieren,Sondertasten,Sperrtasten -X-KDE-Keywords[el]=πρόσβαση,προσβασιμότητα,βαρήκοος,πρόβλημα,ακοής,απώλεια ακοής,κουδούνι,ηχητικό κουδούνι,οπτικό κουδούνι,πληκτρολόγιο,πλήκτρα,κολλημένα πλήκτρα,πλήκτρα αναπήδησης,αργά πλήκτρα,πλοήγηση με ποντίκι,αριθμητικό πληκτρολόγιο,numpad,νεύματα ενεργοποίησης,νεύματα,κολλημένο,πλήκτρα τροποποιητή,τροποποιητής,πλήκτρα κλειδώματος -X-KDE-Keywords[en_GB]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys -X-KDE-Keywords[es]=acceso,accesibilidad,sordera,descapacitado,oído,pérdida de oído,campana,campana audible,campana visible,teclado,teclas,teclas pegajosas,repetición de teclas,teclas lentas,navegación con ratón,teclado numérico,gestos de activación,gestos,pegajoso,teclas modificadoras,modificador,teclas de bloqueo -X-KDE-Keywords[et]=hõlbustus,kasutusmugavus,kurdid,tummad,kuulmine,kuulmiskaotus,puuetega,kell,kuuldav kell,nähtav kell,klaviatuur,klahvid,kleepuvad klahvid,põrkuvad klahvid,aeglased klahvid,hiirega liikumine,numbriklahvistik,aktiveerimisžestid,žestid,muuteklahvid,lukustusklahvid -X-KDE-Keywords[eu]=sarbide,irisgarritasuna,gor,baliaezintasun,entzumen,entzumena galdu,kanpai,kanpai entzungarri,kanpai ikusgarri,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[fi]=esteettömyys,kuuro,heikentynyt,heikko,kuulo,varoitus,äänimerkki,visuaalinen äänimerkki,näppäimistö,näppäimet,alas jäävät näppäimet,tahmeat näppäimet,ponnahdusnäppäimet,hitaat näppäimet,hiirinäppäimet,numeronäppäimistö +X-KDE-Keywords[en_GB]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys +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[fr]=accès, accessibilité, sourd, affaiblis, audition, perte d'audition, cloche, cloche audible, cloche visuelle, clavier, touches, touches collantes, touche rebondissante, touches lentes, navigation de la souris, pavé numérique, gestes d'activation, gestes, collant, touches modifiantes, modificateur, touche de verrouillage -X-KDE-Keywords[ga]=rochtain,inrochtaineacht,bodhar,lagaithe,cloigín,cloigín inchloiste,clog infheicthe,Méarchlár,eochracha,eochracha greamaitheacha,eochracha preabtha,eochracha go mall,nascleanúint luiche,eochaircheap uimhriúil,gothaí,greamaitheach,mionthraitheoir,eochracha glasála -X-KDE-Keywords[gl]=acceso, accesibilidade, xordo, discapacitado, minusválido, audición, auditiva, campá audíbel, campá visíbel, teclado, tecla pegañentas, teclas lentas, navegación co rato, teclado numérico, acenos, teclas modificadoras -X-KDE-Keywords[hu]=kezelés,akadálymentesítés,süket,károsodott,hallás,halláskárosodás,csengő,hallható csengő,látható csengő,Billentyűzet,billentyűk,ragadós billentyűk,pattogó billentyűk,lassú billentyűk,egérnavigáció,numerikus billentyűzet -X-KDE-Keywords[ia]=accesso,accessibilitate,surde,debilitate,audito, perdita de audito,campana,campana audibile,campana visibile,Claviero,claves,claves collose,claves saltante,claves lente,navigation de mus,pad numeric, gestos de activation,gestos,collose, claves modificator, modificator, claves blocante -X-KDE-Keywords[id]=akses,aksesibilitas,tuli,gangguan,pendengaran,kehilangan pendengaran,bel,bel terdengar,bel tampak,Papan Ketik,tombol,tombol lekat,tombol pantul,tombol pelan,navigasi tetikus,papan numerik,numpad,gerakan aktivasi,gerakan,gerakan,lengket,tombol pemodifikasi,pemodifikasi,tombol mengunci +X-KDE-Keywords[hu]=kezelés,akadálymentesítés,süket,károsodott,hallás,halláskárosodás,csengő,hallható csengő,látható csengő,vizuális csengő,Billentyűzet,billentyűk,ragadós billentyűk,pattogó billentyűk,lassú billentyűk,egérnavigáció,numerikus billentyűzet 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[kk]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys -X-KDE-Keywords[km]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys -X-KDE-Keywords[ko]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,접근,접근성,청각장애,청각 장애,시각장애,시각 장애,키보드,키,고정키,튕김키,느린키,마우스키,숫자패드 -X-KDE-Keywords[mr]=एक्सेस, एक्सेसिबिलिटी, कर्णबधीर, विकलांग, ऐकणे, कमी ऐकू येणे, घंटा, घंटानाद , दृश्यघंटा, कि-बोर्ड, कीज, स्टिकी किज, बाउन्स किज, स्लो किज, माऊस दिशानिर्देश, न्यूम पॅड, एक्टिव्हेशन गेस्चर्स, गेस्चर्स, स्टिकी, मॉडीफायर कीज, मॉडीफायर, लॉकिंग किज, -X-KDE-Keywords[nb]=tilgang,tilgjengelighet,døv,hemmet,hørsel, hørselstap,bjelle,hørbar bjelle,synlig bjelle,Tastatur,låsetaster,faste taster,trege taster,musnavigering, talltastatur,tallboks,aktiveringsbevegelser,bevegelser,valgtaster -X-KDE-Keywords[nds]=Togang,Toganghülp,doof,kiekbehinnert,Hören,Pingel,Ogenpingel,Tastatuur,Tasten,backige Tasten,jumpen Tasten,langsame Tasten,Muusstüern,Tallenblock,Tekens,Anmaaktekens,backig,Sünnertasten,Wesseltekens,Fastsetttasten -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,modificator,vergrendeltoets -X-KDE-Keywords[nn]=tilgjenge,tilgjengelegheit,døv,funksjonshemma,handikappa,høyrsle,høyrsletap,bjølle,høyrbar bjølle,synleg bjølle,tastatur,låsetastar,faste tastar,trege tastar,musnavigering,taltastatur,numerisk tastatur,aktiveringsrørsler,musrørsler,muserørsler,rørsler,valtastar,låsetastar -X-KDE-Keywords[pl]=dostęp,dostępność,głuchota,upośledzenie,dzwonek,słyszalny dzwonek,widoczny dzwonek,Klawiatura,klawisze,lepkie klawisze,odbijające klawisze,powolne klawisze, ruchy myszą,klawiatura numeryczna +X-KDE-Keywords[ko]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,접근,접근성,청각장애,청각 장애,시각장애,시각 장애,키보드,키,고정키,튕김키,느린키,마우스키,숫자패드,제스처 +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[pl]=dostęp,dostępność,głuchota,upośledzenie,dzwonek,słyszalny dzwonek,widoczny dzwonek,słyszalny dzwonek,Klawiatura,klawisze,lepkie klawisze,odbijające klawisze,powolne klawisze, ruchy myszą,klawiatura numeryczna 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[pt_BR]=acesso,acessibilidade,surdo,deficiente,audição,campainha,campainha audível,campainha visível,Teclado,teclas,teclas de aderência,teclas lentas,teclas de repercussão,navegação do mouse,teclado numérico numérico,gestos de ativação,gestos,fixo,teclas modificadoras,modificadora,teclas de bloqueio -X-KDE-Keywords[ru]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,доступ,доступность,глухота,слух,ослабленный,слабый,потеря слуха,звонок,сигнал,звуковой сигнал,визуальный сигнал,видимый сигнал,клавиатура,кнопки,клавиши,залипание,залипающие клавиши,липкие клавиши,прыгающие клавиши,«прыгающие» клавиши,замедленные клавиши,навигация с помощью мыши,управление мышью,цифровая клавиатура,жесты активации,жесты,клавиши-модификаторы,модификаторы,модификатор,клавиши индикаторов -X-KDE-Keywords[sk]=prístup,prístupnosť,hluchý,opitý,sluch,strata sluchu,zvonček,hlasný zvonček,viditeľný zvonček,Klávesnica,klávesy,lepkavé klávesy,skákajúce klávesy,pomalé klávesy,navigácia myšou,numerická klávesnica,numpad,aktivačné gestá,lepkavé,modifikačné klávesy,modifikátor,zamykacie klávesy -X-KDE-Keywords[sl]=dostop,dostopnost,gluh,oviran,sluh,slušno,izguba sluha,zvonec,slišen zvonec,viden zvonec,tipkovnica,tipke,lepljive tipke,odbijajoče tipke,počasne tipke,krmarjenje z miško,številčnica,številske tipke,kretnje za vklop,kretnje,lepljivo,spremenilne tipke,zaklepne tipke -X-KDE-Keywords[sr]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,приступ,приступачност,глув,оштећен,слух,губитак,звоно,чујно,видно,тастатура,тастери,лепљиви,одскачући,спори,нумеричка,активација,гестови,лепљив,модификаторски,тастери,закључавање -X-KDE-Keywords[sr@ijekavian]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,приступ,приступачност,глув,оштећен,слух,губитак,звоно,чујно,видно,тастатура,тастери,лепљиви,одскачући,спори,нумеричка,активација,гестови,лепљив,модификаторски,тастери,закључавање -X-KDE-Keywords[sr@ijekavianlatin]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,pristup,pristupačnost,gluv,oštećen,sluh,gubitak,zvono,čujno,vidno,tastatura,tasteri,lepljivi,odskačući,spori,numerička,aktivacija,gestovi,lepljiv,modifikatorski,tasteri,zaključavanje -X-KDE-Keywords[sr@latin]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,pristup,pristupačnost,gluv,oštećen,sluh,gubitak,zvono,čujno,vidno,tastatura,tasteri,lepljivi,odskačući,spori,numerička,aktivacija,gestovi,lepljiv,modifikatorski,tasteri,zaključavanje -X-KDE-Keywords[sv]=åtkomst,handikappstöd,döva,funktionshindrade,hörsel,hörselförlust,summer,hörbar summer,synlig summer,Tangentbord,tangenter,klistriga tangenter,studsande tangenter,långsamma tangenter,musnavigering,numeriskt tangentbord,aktiveringsgester,gester,klistrig,väljartangenter,väljare,låstangenter -X-KDE-Keywords[tr]=erişim,erişebilirlik,sağır,bozukluk,işitme,kayıp,zil,işitilebilir zil,görünen zil,Klayve,tuşlar,yapışkan tuşlar,sıçrama tuşları,yavaş tuşlar,fare gezinmesi,rakamlar klavyesi,jestler,yapışkan,hızlandırma tuşları,hızlandırıcı,kilitleme tuşları -X-KDE-Keywords[uk]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,доступ,доступність,спеціальні можливості,глухий,слух,враження,вади,звук,сповіщення,видиме сповіщення,візуальне сповіщення,клавіатура,клавіші,клавіша,липкі клавіші,утримування,повільні клавіші,миша,навігація,цифри,цифровий блок,слух,втрата слуху,жести активування,жести,липка,липкі клавіші,клавіші-модифікатори,модифікатори,клавіші фіксування,фіксування,блокування -X-KDE-Keywords[x-test]=xxaccessxx,xxaccessibilityxx,xxdeafxx,xximpairedxx,xxhearingxx,xxhearing lossxx,xxbellxx,xxaudible bellxx,xxvisible bellxx,xxKeyboardxx,xxkeysxx,xxsticky keysxx,xxbounce keysxx,xxslow keysxx,xxmouse navigationxx,xxnum padxx,xxnumpadxx,xxactivation gesturesxx,xxgesturesxx,xxstickyxx,xxmodifier keysxx,xxmodifierxx,xxlocking keysxx -X-KDE-Keywords[zh_CN]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,访问,可访问性,耳聋,聋,听力,听力丧失,铃声,可视铃声,按键,残疾人,残疾,聋哑,键盘,粘滞键,鼠标,小键盘,撞击键,鼠标导航,数字键盘,激活手势,手势,粘滞,修饰键,锁定键 -X-KDE-Keywords[zh_TW]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys +X-KDE-Keywords[pt_BR]=acesso,acessibilidade,surdo,deficiente,audição,campainha,campainha audível,campainha visível,campainha visual,Teclado,teclas,teclas de aderência,teclas lentas,teclas de repercussão,navegação do mouse,teclado numérico numérico,gestos de ativação,gestos,fixo,teclas modificadoras,modificadora,teclas de bloqueio +X-KDE-Keywords[sr]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,приступ,приступачност,глув,оштећен,слух,губитак,звоно,чујно,видно,тастатура,тастери,лепљиви,одскачући,спори,нумеричка,активација,гестови,лепљив,модификаторски,тастери,закључавање +X-KDE-Keywords[sr@ijekavian]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,приступ,приступачност,глув,оштећен,слух,губитак,звоно,чујно,видно,тастатура,тастери,лепљиви,одскачући,спори,нумеричка,активација,гестови,лепљив,модификаторски,тастери,закључавање +X-KDE-Keywords[sr@ijekavianlatin]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,pristup,pristupačnost,gluv,oštećen,sluh,gubitak,zvono,čujno,vidno,tastatura,tasteri,lepljivi,odskačući,spori,numerička,aktivacija,gestovi,lepljiv,modifikatorski,tasteri,zaključavanje +X-KDE-Keywords[sr@latin]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,pristup,pristupačnost,gluv,oštećen,sluh,gubitak,zvono,čujno,vidno,tastatura,tasteri,lepljivi,odskačući,spori,numerička,aktivacija,gestovi,lepljiv,modifikatorski,tasteri,zaključavanje +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 +X-KDE-Keywords[uk]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,доступ,доступність,спеціальні можливості,глухий,слух,враження,вади,звук,сповіщення,видиме сповіщення,візуальне сповіщення,видимий дзвінок,візуальний дзвінок,клавіатура,клавіші,клавіша,липкі клавіші,утримування,повільні клавіші,миша,навігація,цифри,цифровий блок,слух,втрата слуху,жести активування,жести,липка,липкі клавіші,клавіші-модифікатори,модифікатори,клавіші фіксування,фіксування,блокування +X-KDE-Keywords[x-test]=xxaccessxx,xxaccessibilityxx,xxdeafxx,xximpairedxx,xxhearingxx,xxhearing lossxx,xxbellxx,xxaudible bellxx,xxvisible bellxx,xxvisual bellxx,xxKeyboardxx,xxkeysxx,xxsticky keysxx,xxbounce keysxx,xxslow keysxx,xxmouse navigationxx,xxnum padxx,xxnumpadxx,xxactivation gesturesxx,xxgesturesxx,xxstickyxx,xxmodifier keysxx,xxmodifierxx,xxlocking keysxx +X-KDE-Keywords[zh_CN]=access,accessibility,deaf,impaired,hearing,hearing loss,bell,audible bell,visible bell,visual bell,Keyboard,keys,sticky keys,bounce keys,slow keys,mouse navigation,num pad,numpad,activation gestures,gestures,sticky,modifier keys,modifier,locking keys,通用辅助,聋,听力受损,响铃,视觉铃声,键盘,按键,粘滞键,鼠标导航,数字键盘,手势,修饰键,锁定键 Categories=Qt;KDE;X-KDE-settings-accessibility; diff --git a/kcms/activities/qml/activitiesTab/ActivitiesView.qml b/kcms/activities/qml/activitiesTab/ActivitiesView.qml --- a/kcms/activities/qml/activitiesTab/ActivitiesView.qml +++ b/kcms/activities/qml/activitiesTab/ActivitiesView.qml @@ -45,6 +45,7 @@ onClicked: ActivitySettings.newActivity(); enabled: !dialogCreateActivityLoader.itemVisible + visible: ActivitySettings.newActivityAuthorized } Loader { @@ -159,6 +160,8 @@ iconName: "edit-delete" onClicked: ActivitySettings.deleteActivity(model.id); + + visible: ActivitySettings.newActivityAuthorized } visible: !dialogDeleteLoader.itemVisible diff --git a/kcms/autostart/autostart.desktop b/kcms/autostart/autostart.desktop --- a/kcms/autostart/autostart.desktop +++ b/kcms/autostart/autostart.desktop @@ -151,11 +151,15 @@ X-KDE-Keywords[ca]=Gestor de l'inici automàtic,inici automàtic,inici,inici del sistema,inici del plasma,cron X-KDE-Keywords[ca@valencia]=Gestor de l'inici automàtic,inici automàtic,inici,inici del sistema,inici del plasma,cron X-KDE-Keywords[da]=autostart,opstart,systemstart,plasma start,cron +X-KDE-Keywords[de]=Autostartverwaltung,Autostart,Startvorgang,Systemstart,Plasma-Start,Cron +X-KDE-Keywords[en_GB]=Autostart Manager,autostart,startup,system startup,plasma start,cron X-KDE-Keywords[es]=Gestor de inicio automático,inicio automático,inicio,inicio del sistema,inicio de plasma,cron X-KDE-Keywords[eu]=Hasiera automatikoaren kudeatzailea,hasiera automatikoa,abioa,sistemaren abioa,plasma abiatu,cron X-KDE-Keywords[fr]=Gestionnaire de démarrage, démarrage automatique, démarrage, démarrage du système, démarrage de plasma, cron +X-KDE-Keywords[hu]=Automatikus indítás kezelő,automatikus indítás,indulás,rendszerindulás,plasma indulás,cron X-KDE-Keywords[id]=Pengelola Start Otomatis,start otomatis,jalankan,jalankan sistem,jalankan plasma,cron X-KDE-Keywords[it]=Gestore avvio automatico,avvio automatico,avvio,avvio del sistema,avvio di plasma,cron +X-KDE-Keywords[ko]=Autostart Manager,autostart,startup,system startup,plasma start,cron,자동 시작,자동시작,시작프로그램,시스템 시작,plasma 시작 X-KDE-Keywords[nl]=Autostartbeheerder,autostarten,opstarten,opstarten systeem,opstarten plasma,cron X-KDE-Keywords[pl]=Menadżer autostartu,autostart,uruchamianie,uruchamianie systemu,plasma,kde start,cron X-KDE-Keywords[pt]=Gestor de arranque,arranque,início,arranque do sistema,início do plasma,cron diff --git a/kcms/colors/colors.desktop b/kcms/colors/colors.desktop --- a/kcms/colors/colors.desktop +++ b/kcms/colors/colors.desktop @@ -121,6 +121,7 @@ Comment[id]=Skema Warna Aplikasi Comment[it]=Schema di colore delle applicazioni Comment[ja]=アプリケーションカラースキーム +Comment[ko]=프로그램 색 배열 Comment[lt]=Programos spalvų derinys Comment[nb]=Fargeoppsett for program Comment[nl]=Toepassing Kleurenschema diff --git a/kcms/colors/scmeditoroptions.ui b/kcms/colors/scmeditoroptions.ui --- a/kcms/colors/scmeditoroptions.ui +++ b/kcms/colors/scmeditoroptions.ui @@ -14,14 +14,14 @@ - Apply inactive window color &effects + Apply &effects to inactive windows - In&active selection changes color + Use different colors for in&active selections 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/desktoptheme/kcm.h b/kcms/desktoptheme/kcm.h --- a/kcms/desktoptheme/kcm.h +++ b/kcms/desktoptheme/kcm.h @@ -35,6 +35,7 @@ Q_OBJECT Q_PROPERTY(QStandardItemModel *desktopThemeModel READ desktopThemeModel CONSTANT) Q_PROPERTY(QString selectedPlugin READ selectedPlugin WRITE setSelectedPlugin NOTIFY selectedPluginChanged) + Q_PROPERTY(bool canEditThemes READ canEditThemes CONSTANT) public: enum Roles { @@ -51,6 +52,7 @@ QString selectedPlugin() const; void setSelectedPlugin(const QString &plugin); + bool canEditThemes() const; Q_INVOKABLE void getNewThemes(); Q_INVOKABLE void installThemeFromFile(const QUrl &file); @@ -60,6 +62,8 @@ Q_INVOKABLE int indexOf(const QString &themeName) const; + Q_INVOKABLE void editTheme(const QString &themeName); + Q_SIGNALS: void selectedPluginChanged(const QString &plugin); void showInfoMessage(const QString &infoMessage); @@ -78,6 +82,7 @@ QStringList m_pendingRemoval; Plasma::Theme *m_defaultTheme; QHash m_themes; + bool m_haveThemeExplorerInstalled; }; Q_DECLARE_LOGGING_CATEGORY(KCM_DESKTOP_THEME) diff --git a/kcms/desktoptheme/kcm.cpp b/kcms/desktoptheme/kcm.cpp --- a/kcms/desktoptheme/kcm.cpp +++ b/kcms/desktoptheme/kcm.cpp @@ -44,6 +44,7 @@ KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args) , m_defaultTheme(new Plasma::Theme(this)) + , m_haveThemeExplorerInstalled(false) { //This flag seems to be needed in order for QQuickWidget to work //see https://bugreports.qt-project.org/browse/QTBUG-40765 @@ -63,6 +64,8 @@ roles[ThemeNameRole] = QByteArrayLiteral("themeName"); roles[IsLocalRole] = QByteArrayLiteral("isLocal"); m_model->setItemRoleNames(roles); + + m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty(); } KCMDesktopTheme::~KCMDesktopTheme() @@ -243,6 +246,16 @@ setSelectedPlugin(QStringLiteral("default")); } +bool KCMDesktopTheme::canEditThemes() const +{ + return m_haveThemeExplorerInstalled; +} + +void KCMDesktopTheme::editTheme(const QString &theme) +{ + QProcess::startDetached(QStringLiteral("plasmathemeexplorer -t ") % theme); +} + void KCMDesktopTheme::updateNeedsSave() { setNeedsSave(!m_pendingRemoval.isEmpty() || m_selectedPlugin != m_defaultTheme->themeName()); diff --git a/kcms/desktoptheme/package/contents/ui/main.qml b/kcms/desktoptheme/package/contents/ui/main.qml --- a/kcms/desktoptheme/package/contents/ui/main.qml +++ b/kcms/desktoptheme/package/contents/ui/main.qml @@ -25,6 +25,7 @@ import org.kde.kcm 1.0 import org.kde.kirigami 2.0 // for units +import org.kde.plasma.components 2.0 as PlasmaComponents //the round toolbutton Item { implicitWidth: Units.gridUnit * 20 @@ -77,11 +78,25 @@ } } - Item { + MouseArea { anchors { fill: parent margins: Units.smallSpacing * 2 } + hoverEnabled: true + onClicked: { + grid.currentIndex = index + kcm.selectedPlugin = model.pluginName + } + + Timer { + interval: 1000 + running: parent.containsMouse && !parent.pressedButtons + onTriggered: { + Tooltip.showText(parent, Qt.point(parent.mouseX, parent.mouseY), model.themeName); + } + } + ThemePreview { id: preview anchors { @@ -92,6 +107,27 @@ } themeName: model.pluginName } + + PlasmaComponents.ToolButton { + anchors { + bottom: preview.bottom + right: preview.right + margins: units.smallSpacing + } + iconSource: "document-edit" + tooltip: i18("Edit theme") + flat: false + onClicked: kcm.editTheme(model.pluginName) + visible: kcm.canEditThemes + opacity: parent.containsMouse ? 1 : 0 + Behavior on opacity { + PropertyAnimation { + duration: units.longDuration + easing.type: Easing.OutQuad + } + } + } + QtControls.Label { id: label anchors { @@ -122,21 +158,6 @@ } } } - MouseArea { - anchors.fill: parent - hoverEnabled: true - onClicked: { - grid.currentIndex = index - kcm.selectedPlugin = model.pluginName - } - Timer { - interval: 1000 - running: parent.containsMouse && !parent.pressedButtons - onTriggered: { - Tooltip.showText(parent, Qt.point(parent.mouseX, parent.mouseY), model.themeName); - } - } - } } } Timer { diff --git a/kcms/formats/kcmformats.cpp b/kcms/formats/kcmformats.cpp --- a/kcms/formats/kcmformats.cpp +++ b/kcms/formats/kcmformats.cpp @@ -330,7 +330,7 @@ const QString numberExample = nloc.toString(1000.01); const QString timeExample = i18n("%1 (long format)", tloc.toString(QDateTime::currentDateTime())) + QStringLiteral("\n") + i18n("%1 (short format)", tloc.toString(QDateTime::currentDateTime(), QLocale::ShortFormat)); - const QString currencyExample = cloc.toCurrencyString(24); + const QString currencyExample = cloc.toCurrencyString(24.00); QString measurementSetting; if (mloc.measurementSystem() == QLocale::ImperialUKSystem) { diff --git a/kcms/formats/kcmformatswidget.ui b/kcms/formats/kcmformatswidget.ui --- a/kcms/formats/kcmformatswidget.ui +++ b/kcms/formats/kcmformatswidget.ui @@ -235,7 +235,7 @@ - <b>Examples</b> + <b>Description</b> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter diff --git a/kcms/icons/icons.cpp b/kcms/icons/icons.cpp --- a/kcms/icons/icons.cpp +++ b/kcms/icons/icons.cpp @@ -99,7 +99,7 @@ lbl->setBuddy(mpSizeBox); grid->addWidget(mpSizeBox, 0, 1, Qt::AlignLeft); - mpAnimatedCheck = new QCheckBox(i18n("Animate icons"), m_pTab1); + mpAnimatedCheck = new QCheckBox(i18n("Enable icon animations"), m_pTab1); connect(mpAnimatedCheck, &QCheckBox::toggled, this, &KIconConfig::slotAnimatedCheck); grid->addWidget(mpAnimatedCheck, 2, 0, 1, 2, Qt::AlignLeft); grid->setRowStretch(3, 10); diff --git a/kcms/input/CMakeLists.txt b/kcms/input/CMakeLists.txt --- a/kcms/input/CMakeLists.txt +++ b/kcms/input/CMakeLists.txt @@ -7,55 +7,39 @@ add_subdirectory( pics ) - -########### next target ############### - -set(kapplymousetheme_SRCS kapplymousetheme.cpp ) - - -add_executable(kapplymousetheme ${kapplymousetheme_SRCS}) - -target_link_libraries(kapplymousetheme ${X11_Xrender_LIB} ${X11_X11_LIB}) -if (X11_Xcursor_FOUND) - target_link_libraries(kapplymousetheme ${X11_Xcursor_LIB}) - target_include_directories(kapplymousetheme PRIVATE ${X11_Xcursor_INCLUDE_PATH}) -endif () - -install(TARGETS kapplymousetheme ${INSTALL_TARGETS_DEFAULT_ARGS}) +## Add common files here. +set(kcminput_backend_SRCS + mousebackend.cpp + mousesettings.cpp + logging.cpp) +set(kcminput_backend_LIBS) +include(backends/x11.cmake) ########### next target ############### -set(kcm_input_PART_SRCS mouse.cpp main.cpp) +set(kcm_input_PART_SRCS + mouse.cpp + main.cpp + ${kcminput_backend_SRCS} +) set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) ki18n_wrap_ui(kcm_input_PART_SRCS kcmmouse.ui) qt5_add_dbus_interface(kcm_input_PART_SRCS ${klauncher_xml} klauncher_iface) -add_library(kcm_input MODULE ${kcm_input_PART_SRCS}) - -include_directories(${X11_X11_INCLUDE_PATH} - ${X11_Xinput_INCLUDE_PATH} - ${Evdev_INCLUDE_DIRS}) +add_library(kcm_input MODULE ${kcm_input_PART_SRCS} ${kcminput_backend_SRCS}) target_link_libraries(kcm_input Qt5::DBus - Qt5::X11Extras KF5::KCMUtils KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::KDELibs4Support - ${X11_X11_LIB} - ${X11_Xinput_LIB} + ${kcminput_backend_LIBS} ) -if (X11_Xcursor_FOUND) - target_link_libraries(kcm_input ${X11_Xcursor_LIB}) -endif () -if (X11_Xfixes_FOUND) - target_link_libraries(kcm_input ${X11_Xfixes_LIB}) -endif () install(TARGETS kcm_input DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/kcms/input/backends/x11.cmake b/kcms/input/backends/x11.cmake new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11.cmake @@ -0,0 +1,43 @@ +# // krazy:excludeall=copyright,license + +set(kcminput_backend_SRCS + ${kcminput_backend_SRCS} + backends/x11/x11mousebackend.cpp +) + +set(kcminput_backend_LIBS + Qt5::X11Extras + ${X11_X11_LIB} + ${X11_Xinput_LIB} + ${kcminput_backend_LIBS} +) + +include_directories(${X11_X11_INCLUDE_PATH} + ${X11_Xinput_INCLUDE_PATH} + ${Evdev_INCLUDE_DIRS} + ${XORGLIBINPUT_INCLUDE_DIRS}) + +if (X11_Xcursor_FOUND) + set(kcminput_backend_LIBS + ${X11_Xcursor_LIB} + ${kcminput_backend_LIBS} + ) + include_directories(${X11_Xcursor_INCLUDE_PATH}) +endif () + + +set(kapplymousetheme_SRCS + backends/x11/kapplymousetheme.cpp) + +add_executable(kapplymousetheme ${kapplymousetheme_SRCS} ${kcminput_backend_SRCS}) + +target_link_libraries(kapplymousetheme + Qt5::Gui + Qt5::DBus + KF5::CoreAddons + KF5::ConfigCore + KF5::I18n + ${kcminput_backend_LIBS} +) + +install(TARGETS kapplymousetheme ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kcms/input/backends/x11/kapplymousetheme.cpp b/kcms/input/backends/x11/kapplymousetheme.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/kapplymousetheme.cpp @@ -0,0 +1,54 @@ +/* + * main.cpp + * + * Copyright (c) 1999 Matthias Hoelzer-Kluepfel + * Copyright (c) 2005 Lubos Lunak + * Copyright (c) 2017 Xuetian Weng + * + * Requires the Qt widget libraries, available at no cost at + * http://www.troll.no/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include +#include + +#include "mousebackend.h" + +int main( int argc, char* argv[] ) +{ + int ret = 0; + QGuiApplication app(argc, argv); + if( argc != 3 ) + return 1; + QString theme = QFile::decodeName(argv[ 1 ]); + QString size = QFile::decodeName(argv[ 2 ]); + auto backend = MouseBackend::implementation(); + if (!backend || !backend->isValid()) { + return 2; + } + + // Note: If you update this code, update main.cpp as well. + + // use a default value for theme only if it's not configured at all, not even in X resources + if(theme.isEmpty() && backend->currentCursorTheme().isEmpty()) + { + theme = "breeze_cursors"; + ret = 10; // means to switch to default + } + + backend->applyCursorTheme(theme, size.toInt()); + return ret; +} diff --git a/kcms/input/backends/x11/x11mousebackend.h b/kcms/input/backends/x11/x11mousebackend.h new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/x11mousebackend.h @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef XLIBMOUSEBACKEND_H +#define XLIBMOUSEBACKEND_H + +#include "mousebackend.h" + +#include +#include + +class X11MouseBackend : public MouseBackend +{ + Q_OBJECT +public: + X11MouseBackend(QObject *parent = nullptr); + ~X11MouseBackend(); + + bool isValid() const override { return m_dpy != nullptr; } + + void load() override; + bool supportScrollPolarity() override; + QStringList supportedAccelerationProfiles() override; + QString accelerationProfile() override; + double accelRate() override; + MouseHanded handed() override; + int threshold() override; + void apply(const MouseSettings & settings, bool force) override; + + QString currentCursorTheme() override; + void applyCursorTheme(const QString &name, int size) override; + +private: + void initAtom(); + bool evdevApplyReverseScroll(int deviceid, bool reverse); + bool libinputApplyReverseScroll(int deviceid, bool reverse); + void libinputApplyAccelerationProfile(int deviceid, QString profile); + + Atom m_libinputAccelProfileAvailableAtom; + Atom m_libinputAccelProfileEnabledAtom; + Atom m_libinputNaturalScrollAtom; + + Atom m_evdevWheelEmulationAtom; + Atom m_evdevScrollDistanceAtom; + Atom m_evdevWheelEmulationAxesAtom; + + Atom m_touchpadAtom; + // We may still need to do something on non-X11 platform due to Xwayland. + Display* m_dpy; + bool m_platformX11; + int m_numButtons = 1; + MouseHanded m_handed = MouseHanded::NotSupported; + double m_accelRate = 1.0; + int m_threshold = 0; + int m_middleButton = -1; + QStringList m_supportedAccelerationProfiles; + QString m_accelerationProfile; +}; + +#endif // XLIBMOUSEBACKEND_H diff --git a/kcms/input/backends/x11/x11mousebackend.cpp b/kcms/input/backends/x11/x11mousebackend.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/x11mousebackend.cpp @@ -0,0 +1,482 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "x11mousebackend.h" +#include "mousesettings.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_XCURSOR +# include +#include +#endif + +static const char PROFILE_NONE[] = I18N_NOOP("None"); +static const char PROFILE_ADAPTIVE[] = I18N_NOOP("Adaptive"); +static const char PROFILE_FLAT[] = I18N_NOOP("Flat"); + +struct ScopedXDeleter { + static inline void cleanup(void* pointer) + { + if (pointer) { + XFree(pointer); + } + } +}; + +template +static void XI2ForallPointerDevices(Display* dpy, const Callback& callback) +{ + int ndevices_return; + XIDeviceInfo* info = XIQueryDevice(dpy, XIAllDevices, &ndevices_return); + if (!info) { + return; + } + for (int i = 0; i < ndevices_return; ++i) { + if ((info + i)->use == XISlavePointer) { + callback(info + i); + } + } + XIFreeDeviceInfo(info); +} + +template +static void XIForallPointerDevices(Display* dpy, const Callback& callback) +{ + int ndevices_return; + XDeviceInfo* info = XListInputDevices(dpy, &ndevices_return); + if (!info) { + return; + } + for (int i = 0; i < ndevices_return; ++i) { + if (info[i].use == IsXPointer || info[i].use == IsXExtensionPointer) { + callback(info + i); + } + } + XFreeDeviceList(info); +} + +X11MouseBackend::X11MouseBackend(QObject* parent) : MouseBackend(parent), m_dpy(nullptr) +{ + m_platformX11 = QX11Info::isPlatformX11(); + if (m_platformX11) { + m_dpy = QX11Info::display(); + } else { + // let's hope we have a compatibility system like Xwayland ready + m_dpy = XOpenDisplay(nullptr); + } + initAtom(); +} + +void X11MouseBackend::initAtom() +{ + if (!m_dpy) { + return; + } + + m_libinputAccelProfileAvailableAtom = XInternAtom(m_dpy, LIBINPUT_PROP_ACCEL_PROFILES_AVAILABLE, True); + m_libinputAccelProfileEnabledAtom = XInternAtom(m_dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True); + m_libinputNaturalScrollAtom = XInternAtom(m_dpy, LIBINPUT_PROP_NATURAL_SCROLL, True); + + m_evdevScrollDistanceAtom = XInternAtom(m_dpy, EVDEV_PROP_SCROLL_DISTANCE, True); + m_evdevWheelEmulationAtom = XInternAtom(m_dpy, EVDEV_PROP_WHEEL, True); + m_evdevWheelEmulationAxesAtom = XInternAtom(m_dpy, EVDEV_PROP_WHEEL_AXES, True); + + m_touchpadAtom = XInternAtom(m_dpy, XI_TOUCHPAD, True); +} + + +X11MouseBackend::~X11MouseBackend() +{ + if (!m_platformX11 && m_dpy) { + XCloseDisplay(m_dpy); + } +} + +bool X11MouseBackend::supportScrollPolarity() +{ + return m_numButtons >= 5; +} + +QStringList X11MouseBackend::supportedAccelerationProfiles() +{ + return m_supportedAccelerationProfiles; +} + +QString X11MouseBackend::accelerationProfile() +{ + return m_accelerationProfile; +} + +double X11MouseBackend::accelRate() +{ + return m_accelRate; +} + +MouseHanded X11MouseBackend::handed() +{ + return m_handed; +} + +int X11MouseBackend::threshold() +{ + return m_threshold; +} + +void X11MouseBackend::load() +{ + if (!m_dpy) { + return; + } + + m_accelRate = 1.0; + int accel_num, accel_den; + XGetPointerControl(m_dpy, &accel_num, &accel_den, &m_threshold); + m_accelRate = double(accel_num) / double(accel_den); + + // get settings from X server + unsigned char map[256]; + m_numButtons = XGetPointerMapping(m_dpy, map, 256); + m_middleButton = -1; + + m_handed = MouseHanded::NotSupported; + // ## keep this in sync with KGlobalSettings::mouseSettings + if (m_numButtons == 2) { + if (map[0] == 1 && map[1] == 2) { + m_handed = MouseHanded::Right; + } else if (map[0] == 2 && map[1] == 1) { + m_handed = MouseHanded::Left; + } + } else if (m_numButtons >= 3) { + m_middleButton = map[1]; + if (map[0] == 1 && map[2] == 3) { + m_handed = MouseHanded::Right; + } else if (map[0] == 3 && map[2] == 1) { + m_handed = MouseHanded::Left; + } + } + + m_supportedAccelerationProfiles.clear(); + bool adaptiveAvailable = false; + bool flatAvailable = false; + bool adaptiveEnabled = false; + bool flatEnabled = false; + XI2ForallPointerDevices(m_dpy, [&] (XIDeviceInfo *info) { + int deviceid = info->deviceid; + Status status; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char *_data = nullptr; + //data returned is an 2 byte boolean + status = XIGetProperty(m_dpy, deviceid, m_libinputAccelProfileAvailableAtom, 0, 2, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + QScopedArrayPointer data(_data); + _data = nullptr; + if (status != Success || type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) { + return; + } + adaptiveAvailable = adaptiveAvailable || data[0]; + flatAvailable = flatAvailable || data[1]; + + //data returned is an 2 byte boolean + status = XIGetProperty(m_dpy, deviceid, m_libinputAccelProfileEnabledAtom, 0, 2, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + data.reset(_data); + _data = nullptr; + if (status != Success || type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) { + return; + } + adaptiveEnabled = adaptiveEnabled || data[0]; + flatEnabled = flatEnabled || data[1]; + }); + + if (adaptiveAvailable) { + m_supportedAccelerationProfiles << PROFILE_ADAPTIVE; + } + if (flatAvailable) { + m_supportedAccelerationProfiles << PROFILE_FLAT; + } + if (adaptiveAvailable || flatAvailable) { + m_supportedAccelerationProfiles << PROFILE_NONE; + } + + m_accelerationProfile = PROFILE_NONE; + if (adaptiveEnabled) { + m_accelerationProfile = PROFILE_ADAPTIVE; + } else if (flatEnabled) { + m_accelerationProfile = PROFILE_FLAT; + } +} + +void X11MouseBackend::apply(const MouseSettings& settings, bool force) +{ + // 256 might seems extreme, but X has already been known to return 32, + // and we don't want to truncate things. Xlib limits the table to 256 bytes, + // so it's a good upper bound.. + unsigned char map[256]; + XGetPointerMapping(m_dpy, map, 256); + + if (settings.handedEnabled && (settings.handedNeedsApply || force)) { + if (m_numButtons == 1) { + map[0] = (unsigned char) 1; + } else if (m_numButtons == 2) { + if (settings.handed == MouseHanded::Right) { + map[0] = (unsigned char) 1; + map[1] = (unsigned char) 3; + } else { + map[0] = (unsigned char) 3; + map[1] = (unsigned char) 1; + } + } else { // 3 buttons and more + if (settings.handed == MouseHanded::Right) { + map[0] = (unsigned char) 1; + map[1] = (unsigned char) m_middleButton; + map[2] = (unsigned char) 3; + } else { + map[0] = (unsigned char) 3; + map[1] = (unsigned char) m_middleButton; + map[2] = (unsigned char) 1; + } + } + + int retval; + if (m_numButtons >= 1) { + while ((retval = XSetPointerMapping(m_dpy, map, + m_numButtons)) == MappingBusy) + /* keep trying until the pointer is free */ + { }; + } + + // apply reverseScrollPolarity for all non-touchpad pointer, touchpad + // are belong to kcm touchpad. + XIForallPointerDevices(m_dpy, [this, &settings](XDeviceInfo * info) { + int deviceid = info->id; + if (info->type == m_touchpadAtom) { + return; + } + if (libinputApplyReverseScroll(deviceid, settings.reverseScrollPolarity)) { + return; + } + evdevApplyReverseScroll(deviceid, settings.reverseScrollPolarity); + }); + + } + + XI2ForallPointerDevices(m_dpy, [&] (XIDeviceInfo *info) { + libinputApplyAccelerationProfile(info->deviceid, settings.currentAccelProfile); + }); + + XChangePointerControl(m_dpy, + true, true, int(qRound(settings.accelRate * 10)), 10, settings.thresholdMove); + + XFlush(m_dpy); +} + +QString X11MouseBackend::currentCursorTheme() +{ + if (!m_dpy) { + return QString(); + } + + QByteArray name = XGetDefault(m_dpy, "Xcursor", "theme"); +#ifdef HAVE_XCURSOR + if (name.isEmpty()) { + name = QByteArray(XcursorGetTheme(m_dpy)); + } +#endif + return QFile::decodeName(name); +} + +void X11MouseBackend::applyCursorTheme(const QString& theme, int size) +{ +#ifdef HAVE_XCURSOR + + // Apply the KDE cursor theme to ourselves + if (m_dpy) { + return; + } + if (!theme.isEmpty()) { + XcursorSetTheme(m_dpy, QFile::encodeName(theme)); + } + + if (size >= 0) { + XcursorSetDefaultSize(m_dpy, size); + } + + // Load the default cursor from the theme and apply it to the root window. + Cursor handle = XcursorLibraryLoadCursor(m_dpy, "left_ptr"); + XDefineCursor(m_dpy, DefaultRootWindow(m_dpy), handle); + XFreeCursor(m_dpy, handle); // Don't leak the cursor + XFlush(m_dpy); +#endif +} + +bool X11MouseBackend::evdevApplyReverseScroll(int deviceid, bool reverse) +{ + // Check atom availability first. + if (m_evdevWheelEmulationAtom == None || m_evdevScrollDistanceAtom == None || + m_evdevWheelEmulationAxesAtom == None) { + return false; + } + Status status; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char* _data = nullptr; + //data returned is an 1 byte boolean + status = XIGetProperty(m_dpy, deviceid, m_evdevWheelEmulationAtom, 0, 1, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + QScopedArrayPointer data(_data); + _data = nullptr; + if (status != Success) { + return false; + } + + // pointer device without wheel emulation + if (type_return != XA_INTEGER || data == NULL || *data == False) { + status = XIGetProperty(m_dpy, deviceid, m_evdevScrollDistanceAtom, 0, 3, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + data.reset(_data); + _data = nullptr; + // negate scroll distance + if (status == Success && type_return == XA_INTEGER && + format_return == 32 && num_items_return == 3) { + int32_t* vals = (int32_t*)data.data(); + for (unsigned long i = 0; i < num_items_return; ++i) { + int32_t val = *(vals + i); + *(vals + i) = (int32_t)(reverse ? -abs(val) : abs(val)); + } + XIChangeProperty(m_dpy, deviceid, m_evdevScrollDistanceAtom, XA_INTEGER, + 32, XIPropModeReplace, data.data(), 3); + } + } else { // wheel emulation used, reverse wheel axes + status = XIGetProperty(m_dpy, deviceid, m_evdevWheelEmulationAxesAtom, 0, 4, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + data.reset(_data); + _data = nullptr; + if (status == Success && type_return == XA_INTEGER && + format_return == 8 && num_items_return == 4) { + // when scroll direction is not reversed, + // up button id should be smaller than down button id, + // up/left are odd elements, down/right are even elements + for (int i = 0; i < 2; ++i) { + unsigned char odd = data[i * 2]; + unsigned char even = data[i * 2 + 1]; + unsigned char max_elem = std::max(odd, even); + unsigned char min_elem = std::min(odd, even); + data[i * 2] = reverse ? max_elem : min_elem; + data[i * 2 + 1] = reverse ? min_elem : max_elem; + } + XIChangeProperty(m_dpy, deviceid, m_evdevWheelEmulationAxesAtom, XA_INTEGER, + 8, XIPropModeReplace, data.data(), 4); + } + } + + return true; +} + +bool X11MouseBackend::libinputApplyReverseScroll(int deviceid, bool reverse) +{ + // Check atom availability first. + if (m_libinputNaturalScrollAtom == None) { + return false; + } + Status status; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char *_data = nullptr; + //data returned is an 1 byte boolean + status = XIGetProperty(m_dpy, deviceid, m_libinputNaturalScrollAtom, 0, 1, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return false; + } + QScopedArrayPointer data(_data); + _data = nullptr; + if (type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 1) { + return false; + } + unsigned char natural = reverse ? 1 : 0; + XIChangeProperty(m_dpy, deviceid, m_libinputNaturalScrollAtom, XA_INTEGER, + 8, XIPropModeReplace, &natural, 1); + return true; +} + +void X11MouseBackend::libinputApplyAccelerationProfile(int deviceid, QString profile) +{ + unsigned char profileData[2]; + if (profile == PROFILE_NONE) { + profileData[0] = profileData[1] = 0; + } else if (profile == PROFILE_ADAPTIVE) { + profileData[0] = 1; + profileData[1] = 0; + } else if (profile == PROFILE_FLAT) { + profileData[0] = 0; + profileData[1] = 1; + } + Status status; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + + unsigned char *_data = nullptr; + //data returned is an 1 byte boolean + status = XIGetProperty(m_dpy, deviceid, m_libinputAccelProfileAvailableAtom, 0, 2, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return; + } + QScopedArrayPointer data(_data); + _data = nullptr; + if (type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) { + return; + } + // Check availability for profile. + if (profileData[0] > data[0] || profileData[1] > data[1]) { + return; + } + XIChangeProperty(m_dpy, deviceid, m_libinputAccelProfileEnabledAtom, XA_INTEGER, + 8, XIPropModeReplace, profileData, 2); +} diff --git a/kcms/input/kapplymousetheme.cpp b/kcms/input/kapplymousetheme.cpp deleted file mode 100644 --- a/kcms/input/kapplymousetheme.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * main.cpp - * - * Copyright (c) 1999 Matthias Hoelzer-Kluepfel - * Copyright (c) 2005 Lubos Lunak - * - * Requires the Qt widget libraries, available at no cost at - * http://www.troll.no/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include - -#include -#include -#include - -#ifdef HAVE_XCURSOR -# include -#endif - -static Display* dpy; -Display* display() { return dpy; } -Window appRootWindow() { return DefaultRootWindow( dpy ); } - -static bool isEmpty( const char* str ) - { - if( str == NULL ) - return true; - while( isspace( *str )) - ++str; - return *str == '\0'; - } - -int main( int argc, char* argv[] ) - { - if( argc != 3 ) - return 1; - dpy = XOpenDisplay( NULL ); - if( dpy == NULL ) - return 2; - int ret = 0; -#ifdef HAVE_XCURSOR - const char* theme = argv[ 1 ]; - const char* size = argv[ 2 ]; - - // Note: If you update this code, update kapplymousetheme as well. - - // use a default value for theme only if it's not configured at all, not even in X resources - if( isEmpty( theme ) - && isEmpty( XGetDefault( display(), "Xcursor", "theme" )) - && isEmpty( XcursorGetTheme( display()))) - { - theme = "breeze_cursors"; - ret = 10; // means to switch to default - } - - // Apply the KDE cursor theme to ourselves - if( !isEmpty( theme )) - XcursorSetTheme(display(), theme ); - - if (!isEmpty( size )) - XcursorSetDefaultSize(display(), atoi( size )); - - // Load the default cursor from the theme and apply it to the root window. - Cursor handle = XcursorLibraryLoadCursor(display(), "left_ptr"); - XDefineCursor(display(), appRootWindow(), handle); - XFreeCursor(display(), handle); // Don't leak the cursor - -#else - ( void ) argv; -#endif - XCloseDisplay( dpy ); - return ret; - } diff --git a/kcms/input/kcmmouse.ui b/kcms/input/kcmmouse.ui --- a/kcms/input/kcmmouse.ui +++ b/kcms/input/kcmmouse.ui @@ -202,7 +202,7 @@ - + Pointer acceleration: @@ -212,7 +212,7 @@ - + Pointer threshold: @@ -222,7 +222,7 @@ - + Double click interval: @@ -232,7 +232,7 @@ - + Drag start time: @@ -242,7 +242,7 @@ - + Drag start distance: @@ -252,7 +252,7 @@ - + Mouse wheel scrolls by: @@ -262,7 +262,7 @@ - + @@ -293,7 +293,7 @@ - + @@ -312,7 +312,7 @@ - + @@ -340,7 +340,7 @@ - + @@ -365,7 +365,7 @@ - + @@ -387,7 +387,7 @@ - + @@ -409,6 +409,26 @@ + + + + Acceleration Profile: + + + accelProfileComboBox + + + + + + + + 0 + 0 + + + + diff --git a/kcms/input/logging.h b/kcms/input/logging.h new file mode 100644 --- /dev/null +++ b/kcms/input/logging.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KCM_INPUT_LOGGING_H +#define KCM_INPUT_LOGGING_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(KCM_INPUT) +#endif diff --git a/kcms/input/logging.cpp b/kcms/input/logging.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/logging.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "logging.h" + +Q_LOGGING_CATEGORY(KCM_INPUT, "kcm_input") diff --git a/kcms/input/main.cpp b/kcms/input/main.cpp --- a/kcms/input/main.cpp +++ b/kcms/input/main.cpp @@ -21,89 +21,60 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include - -#include +#include +#include #include -#include "mouse.h" -#include +#include "mousesettings.h" +#include "mousebackend.h" #include -#include -#ifdef HAVE_XCURSOR -# include -#endif - extern "C" { - Q_DECL_EXPORT void kcminit_mouse() - { - KConfig *config = new KConfig("kcminputrc", KConfig::NoGlobals ); - - Display *dpy = nullptr; - const bool platformX11 = QX11Info::isPlatformX11(); - if (platformX11) { - dpy = QX11Info::display(); - } else { - // let's hope we have a compatibility system like Xwayland ready - dpy = XOpenDisplay(nullptr); - } - - MouseSettings settings; - settings.load(config, dpy); - settings.apply(true); // force - -#ifdef HAVE_XCURSOR - KConfigGroup group = config->group("Mouse"); - QString theme = group.readEntry("cursorTheme", QString()); - QString size = group.readEntry("cursorSize", QString()); - - // Note: If you update this code, update kapplymousetheme as well. - - // use a default value for theme only if it's not configured at all, not even in X resources - if( theme.isEmpty() - && (!dpy || - (QByteArray( XGetDefault( dpy, "Xcursor", "theme" )).isEmpty() - && QByteArray( XcursorGetTheme( dpy)).isEmpty()))) + Q_DECL_EXPORT void kcminit_mouse() { - theme = "breeze_cursors"; + KConfig* config = new KConfig("kcminputrc", KConfig::NoGlobals); + + auto backend = MouseBackend::implementation(); + + MouseSettings settings; + settings.load(config, backend); + settings.apply(backend, true); // force + + KConfigGroup group = config->group("Mouse"); + QString theme = group.readEntry("cursorTheme", QString()); + QString size = group.readEntry("cursorSize", QString()); + if (backend) { + int intSize = -1; + if (size.isEmpty()) { + bool ok; + uint value = size.toUInt(&ok); + if (ok) { + intSize = value; + } + } + // Note: If you update this code, update kapplymousetheme as well. + + // use a default value for theme only if it's not configured at all, not even in X resources + if (theme.isEmpty() && backend->currentCursorTheme().isEmpty()) { + theme = "breeze_cursors"; + } + backend->applyCursorTheme(theme, intSize); + } + + // Tell klauncher to set the XCURSOR_THEME and XCURSOR_SIZE environment + // variables when launching applications. + OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), + QStringLiteral("/KLauncher"), + QDBusConnection::sessionBus()); + if (!theme.isEmpty()) { + klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), theme); + } + if (!size.isEmpty()) { + klauncher.setLaunchEnv(QStringLiteral("XCURSOR_SIZE"), size); + } + + delete config; } - - // Apply the KDE cursor theme to ourselves - if (dpy) { - if( !theme.isEmpty()) - XcursorSetTheme(dpy, QFile::encodeName(theme)); - - if (!size.isEmpty()) - XcursorSetDefaultSize(dpy, size.toUInt()); - - // Load the default cursor from the theme and apply it to the root window. - Cursor handle = XcursorLibraryLoadCursor(dpy, "left_ptr"); - XDefineCursor(dpy, QX11Info::appRootWindow(), handle); - XFreeCursor(dpy, handle); // Don't leak the cursor - } - - // Tell klauncher to set the XCURSOR_THEME and XCURSOR_SIZE environment - // variables when launching applications. - OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), - QStringLiteral("/KLauncher"), - QDBusConnection::sessionBus()); - if(!theme.isEmpty()) - klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), theme); - if( !size.isEmpty()) - klauncher.setLaunchEnv(QStringLiteral("XCURSOR_SIZE"), size); - -#endif - - if (!platformX11) { - XFlush(dpy); - XCloseDisplay(dpy); - } - - delete config; - } } - - diff --git a/kcms/input/mouse.h b/kcms/input/mouse.h --- a/kcms/input/mouse.h +++ b/kcms/input/mouse.h @@ -31,45 +31,20 @@ #ifndef __MOUSECONFIG_H__ #define __MOUSECONFIG_H__ -#include - #include #include -#include #include "ui_kcmmouse.h" - -#define RIGHT_HANDED 0 -#define LEFT_HANDED 1 +#include "mousesettings.h" class QCheckBox; class QDoubleSpinBox; class QSlider; class QSpinBox; class QTabWidget; -class MouseSettings -{ -public: - void save(KConfig *); - void load(KConfig *, Display *dpy = QX11Info::display()); - void apply(bool force=false); - -public: - int num_buttons; - int middle_button; - bool handedEnabled; - bool m_handedNeedsApply; - int handed; - double accelRate; - int thresholdMove; - int doubleClickInterval; - int dragStartTime; - int dragStartDist; - bool singleClick; - int wheelScrollLines; - bool reverseScrollPolarity; -}; +class MouseSettings; +class MouseBackend; class MouseConfig : public KCModule, public Ui::KCMMouse { @@ -93,14 +68,15 @@ private: double getAccel(); int getThreshold(); - int getHandedness(); + MouseHanded getHandedness(); void setAccel(double); void setThreshold(int); - void setHandedness(int); + void setHandedness(MouseHanded); MouseSettings *settings; + + MouseBackend *backend; }; #endif - diff --git a/kcms/input/mouse.cpp b/kcms/input/mouse.cpp --- a/kcms/input/mouse.cpp +++ b/kcms/input/mouse.cpp @@ -65,56 +65,40 @@ #include #include "mouse.h" +#include "mousebackend.h" +#include "mousesettings.h" -#include -#include -#include -#include -#include #include -#include -#include #undef Below #include "../migrationlib/kdelibs4config.h" K_PLUGIN_FACTORY(MouseConfigFactory, registerPlugin(); // mouse ) -K_EXPORT_PLUGIN(MouseConfigFactory("kcminput")) MouseConfig::MouseConfig(QWidget *parent, const QVariantList &args) - : KCModule(parent, args) + : KCModule(parent, args), + backend(MouseBackend::implementation()) { setupUi(this); - handedGroup->setId(rightHanded, 0); - handedGroup->setId(leftHanded, 1); + handedGroup->setId(rightHanded, static_cast(MouseHanded::Right)); + handedGroup->setId(leftHanded, static_cast(MouseHanded::Left)); connect(handedGroup, SIGNAL(buttonClicked(int)), this, SLOT(changed())); connect(handedGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotHandedChanged(int))); connect(doubleClick, SIGNAL(clicked()), SLOT(changed())); - connect(singleClick, SIGNAL(clicked()), this, SLOT(changed())); - // Only allow setting reversing scroll polarity if we have scroll buttons - unsigned char map[20]; - if (QX11Info::isPlatformX11() && XGetPointerMapping(QX11Info::display(), map, 20) >= 5) - { - cbScrollPolarity->setEnabled(true); - cbScrollPolarity->show(); - } - else - { - cbScrollPolarity->setEnabled(false); - cbScrollPolarity->hide(); - } + connect(singleClick, SIGNAL(clicked()), this, SLOT(changed())); connect(cbScrollPolarity, SIGNAL(clicked()), this, SLOT(changed())); connect(cbScrollPolarity, SIGNAL(clicked()), this, SLOT(slotScrollPolarityChanged())); connect(accel, SIGNAL(valueChanged(double)), this, SLOT(changed())); connect(thresh, SIGNAL(valueChanged(int)), this, SLOT(changed())); connect(thresh, SIGNAL(valueChanged(int)), this, SLOT(slotThreshChanged(int))); + connect(accelProfileComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changed())); slotThreshChanged(thresh->value()); // It would be nice if the user had a test field. @@ -192,33 +176,65 @@ } -int MouseConfig::getHandedness() +MouseHanded MouseConfig::getHandedness() { if (rightHanded->isChecked()) - return RIGHT_HANDED; + return MouseHanded::Right; else - return LEFT_HANDED; + return MouseHanded::Left; } -void MouseConfig::setHandedness(int val) +void MouseConfig::setHandedness(MouseHanded val) { rightHanded->setChecked(false); leftHanded->setChecked(false); - if (val == RIGHT_HANDED) { + if (val == MouseHanded::Right) { rightHanded->setChecked(true); mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_rh.png")); } else { leftHanded->setChecked(true); mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_lh.png")); } - settings->m_handedNeedsApply = true; + settings->handedNeedsApply = true; } void MouseConfig::load() { KConfig config("kcminputrc"); - settings->load(&config); + settings->load(&config, backend); + + // settings->load will trigger backend->load so information will be avaialbe + // here. + // Only allow setting reversing scroll polarity if we have scroll buttons + if (backend) { + if (backend->supportScrollPolarity()) + { + cbScrollPolarity->setEnabled(true); + cbScrollPolarity->show(); + } + else + { + cbScrollPolarity->setEnabled(false); + cbScrollPolarity->hide(); + } + } + + auto accelerationProfiles = backend->supportedAccelerationProfiles(); + accelProfileComboBox->setEnabled(!accelerationProfiles.isEmpty()); + accelProfileComboBox->setVisible(!accelerationProfiles.isEmpty()); + accelerationProfileLabel->setEnabled(!accelerationProfiles.isEmpty()); + accelerationProfileLabel->setVisible(!accelerationProfiles.isEmpty()); + accelProfileComboBox->clear(); + int idx = 0; + for (const auto &profile : accelerationProfiles) { + accelProfileComboBox->addItem(i18n(profile.toUtf8().constData()), profile); + if (profile == settings->currentAccelProfile) { + accelProfileComboBox->setCurrentIndex(idx); + } + idx++; + } + rightHanded->setEnabled(settings->handedEnabled); leftHanded->setEnabled(settings->handedEnabled); @@ -280,8 +296,9 @@ settings->wheelScrollLines = wheelScrollLines->value(); settings->singleClick = !doubleClick->isChecked(); settings->reverseScrollPolarity = cbScrollPolarity->isChecked(); + settings->currentAccelProfile = accelProfileComboBox->itemData(accelProfileComboBox->currentIndex()).toString(); - settings->apply(); + settings->apply(backend); KConfig config("kcminputrc"); settings->save(&config); @@ -310,7 +327,7 @@ { setThreshold(2); setAccel(2); - setHandedness(RIGHT_HANDED); + setHandedness(MouseHanded::Right); cbScrollPolarity->setChecked(false); doubleClickInterval->setValue(400); dragStartTime->setValue(500); @@ -325,107 +342,19 @@ mk_time_to_max->setValue(5000); mk_max_speed->setValue(1000); mk_curve->setValue(0); - + checkAccess(); changed(); } /** No descriptions */ void MouseConfig::slotHandedChanged(int val) { - if (val==RIGHT_HANDED) + if (val == static_cast(MouseHanded::Right)) mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_rh.png")); else mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_lh.png")); - settings->m_handedNeedsApply = true; -} - -void MouseSettings::load(KConfig *config, Display *dpy) -{ - // TODO: what's a good threshold default value - int threshold = 0; - int h = RIGHT_HANDED; - double accel = 1.0; - if (QX11Info::isPlatformX11()) { - int accel_num, accel_den; - - XGetPointerControl(dpy, &accel_num, &accel_den, &threshold); - accel = float(accel_num) / float(accel_den); - - // get settings from X server - unsigned char map[20]; - num_buttons = XGetPointerMapping(dpy, map, 20); - - handedEnabled = true; - - // ## keep this in sync with KGlobalSettings::mouseSettings - if (num_buttons == 1) - { - /* disable button remapping */ - handedEnabled = false; - } - else if (num_buttons == 2) - { - if ((int)map[0] == 1 && (int)map[1] == 2) - h = RIGHT_HANDED; - else if ((int)map[0] == 2 && (int)map[1] == 1) - h = LEFT_HANDED; - else - /* custom button setup: disable button remapping */ - handedEnabled = false; - } - else - { - middle_button = (int)map[1]; - if ((int)map[0] == 1 && (int)map[2] == 3) - h = RIGHT_HANDED; - else if ((int)map[0] == 3 && (int)map[2] == 1) - h = LEFT_HANDED; - else - { - /* custom button setup: disable button remapping */ - handedEnabled = false; - } - } - } else { - // other platforms - handedEnabled = true; - } - - KConfigGroup group = config->group("Mouse"); - double a = group.readEntry("Acceleration", -1.0); - if (a == -1) - accelRate = accel; - else - accelRate = a; - - int t = group.readEntry("Threshold", -1); - if (t == -1) - thresholdMove = threshold; - else - thresholdMove = t; - - QString key = group.readEntry("MouseButtonMapping"); - if (key == "RightHanded") - handed = RIGHT_HANDED; - else if (key == "LeftHanded") - handed = LEFT_HANDED; -#ifdef __GNUC__ -#warning was key == NULL how was this working? is key.isNull() what the coder meant? -#endif - else if (key.isNull()) - handed = h; - reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false); - m_handedNeedsApply = false; - - // SC/DC/AutoSelect/ChangeCursor - group = config->group("KDE"); - doubleClickInterval = group.readEntry("DoubleClickInterval", 400); - dragStartTime = group.readEntry("StartDragTime", 500); - dragStartDist = group.readEntry("StartDragDist", 4); - wheelScrollLines = group.readEntry("WheelScrollLines", 3); - - singleClick = group.readEntry("SingleClick", KDE_DEFAULT_SINGLECLICK); + settings->handedNeedsApply = true; } void MouseConfig::slotThreshChanged(int value) @@ -443,172 +372,9 @@ wheelScrollLines->setSuffix(i18np(" line", " lines", value)); } -void MouseSettings::apply(bool force) -{ - if (!QX11Info::isPlatformX11()) { - return; - } - XChangePointerControl(QX11Info::display(), - true, true, int(qRound(accelRate*10)), 10, thresholdMove); - - // 256 might seems extreme, but X has already been known to return 32, - // and we don't want to truncate things. Xlib limits the table to 256 bytes, - // so it's a good upper bound.. - unsigned char map[256]; - num_buttons = XGetPointerMapping(QX11Info::display(), map, 256); - - int remap=(num_buttons>=1); - if (handedEnabled && (m_handedNeedsApply || force)) { - if (num_buttons == 1) - { - map[0] = (unsigned char) 1; - } - else if (num_buttons == 2) - { - if (handed == RIGHT_HANDED) - { - map[0] = (unsigned char) 1; - map[1] = (unsigned char) 3; - } - else - { - map[0] = (unsigned char) 3; - map[1] = (unsigned char) 1; - } - } - else // 3 buttons and more - { - if (handed == RIGHT_HANDED) - { - map[0] = (unsigned char) 1; - map[1] = (unsigned char) middle_button; - map[2] = (unsigned char) 3; - } - else - { - map[0] = (unsigned char) 3; - map[1] = (unsigned char) middle_button; - map[2] = (unsigned char) 1; - } - } - - int retval; - if (remap) { - while ((retval=XSetPointerMapping(QX11Info::display(), map, - num_buttons)) == MappingBusy) - /* keep trying until the pointer is free */ - { }; - } - - // apply reverseScrollPolarity - Display *dpy = QX11Info::display(); - Atom prop_wheel_emulation = XInternAtom(dpy, EVDEV_PROP_WHEEL, True); - Atom prop_scroll_distance = XInternAtom(dpy, EVDEV_PROP_SCROLL_DISTANCE, True); - Atom prop_wheel_emulation_axes = XInternAtom(dpy, EVDEV_PROP_WHEEL_AXES, True); - int ndevices_return; - XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &ndevices_return); - if (!info) { - return; - } - for (int i = 0; i < ndevices_return; ++i) { - if ((info + i)->use == XISlavePointer) { - int deviceid = (info + i)->deviceid; - Status status; - Atom type_return; - int format_return; - unsigned long num_items_return; - unsigned long bytes_after_return; - - unsigned char *data = nullptr; - unsigned char *data2 = nullptr; - //data returned is an 1 byte boolean - status = XIGetProperty(dpy, deviceid, prop_wheel_emulation, 0, 1, - False, XA_INTEGER, &type_return, &format_return, - &num_items_return, &bytes_after_return, &data); - if (status != Success) { - continue; - } - - // pointer device without wheel emulation - if (type_return != XA_INTEGER || data == NULL || *data == False) { - status = XIGetProperty(dpy, deviceid, prop_scroll_distance, 0, 3, - False, XA_INTEGER, &type_return, &format_return, - &num_items_return, &bytes_after_return, &data2); - // negate scroll distance - if (status == Success && type_return == XA_INTEGER && - format_return == 32 && num_items_return == 3) { - int32_t *vals = (int32_t*)data2; - for (unsigned long i=0; isync(); - - Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("Mouse"), "kcminputrc"); - Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("KDE"), "kdeglobals"); - - KGlobalSettings::self()->emitChange(KGlobalSettings::SettingsChanged, KGlobalSettings::SETTINGS_MOUSE); -} - void MouseConfig::slotScrollPolarityChanged() { - settings->m_handedNeedsApply = true; + settings->handedNeedsApply = true; } #include "mouse.moc" diff --git a/kcms/input/mouse.desktop b/kcms/input/mouse.desktop --- a/kcms/input/mouse.desktop +++ b/kcms/input/mouse.desktop @@ -152,8 +152,8 @@ 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@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[ca]=Ratolí,Acceleració de ratolí,Llindar de ratolí,Botons de ratolí,Selecció,Ombra de cursor,Dispositius d'entrada,Mapatge de botó,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í,Sensibilitat del 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 X-KDE-Keywords[el]=ποντίκι,επιτάχυνση ποντικιού,κατώφλι ποντικιού,κουμπιά ποντικιού,επιλογή,σχήμα δρομέα,συσκευές εισόδου,χαρτογράφηση κουμπιών,κλικ,εικονίδια,ανάδραση,δείκτες,έλξη,διπλό κλικ,μονό κλικ,χαρτογράφηση,δεξιόχειρας,αριστερόχειρας,συσκευή δείκτη,τροχός ποντικιού,εξομοίωση ποντικιού,πλοήγηση ποντικιού,έλξη και απόθεση ποντικιού,κύλιση ποντικιού,ευαισθησία ποντικιού,κίνηση ποντικιού με το αριθμητικό πληκτρολόγιο,εξομοίωση ποντικιού αριθμητικού πληκτρολογίου diff --git a/kcms/input/mousebackend.h b/kcms/input/mousebackend.h new file mode 100644 --- /dev/null +++ b/kcms/input/mousebackend.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MOUSEBACKEND_H +#define MOUSEBACKEND_H + +#include +#include "mousesettings.h" + +class MouseBackend : public QObject +{ + Q_OBJECT +protected: + explicit MouseBackend(QObject *parent) : QObject(parent) {} + +public: + static MouseBackend *implementation(); + + virtual bool isValid() const = 0; + + // This function will be called before query any property below, thus it + // can be used to save some round trip. + virtual void load() = 0; + virtual void apply(const MouseSettings &settings, bool force) = 0; + + // Return the value from display server or compositor if applicable. + virtual bool supportScrollPolarity() = 0; + virtual QStringList supportedAccelerationProfiles() = 0; + virtual QString accelerationProfile() = 0; + virtual double accelRate() = 0; + virtual int threshold() = 0; + virtual MouseHanded handed() = 0; + + virtual QString currentCursorTheme() = 0; + virtual void applyCursorTheme(const QString &name, int size) = 0; +}; + +#endif // MOUSEBACKEND_H diff --git a/kcms/input/mousebackend.cpp b/kcms/input/mousebackend.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/mousebackend.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mousebackend.h" + +#include "backends/x11/x11mousebackend.h" +#include "logging.h" + +#include +#include + +#include + +MouseBackend *MouseBackend::implementation() +{ + //There are multiple possible backends, always use X11 backend for now. + static QThreadStorage> backend; + if (!backend.hasLocalData()) { + qCDebug(KCM_INPUT) << "Using X11 backend"; + backend.setLocalData(QSharedPointer(new X11MouseBackend)); + } + return backend.localData().data(); + +#if 0 + qCCritical(KCM_INPUT) << "Not able to select appropriate backend."; + return nullptr; +#endif +} diff --git a/kcms/input/mousesettings.h b/kcms/input/mousesettings.h new file mode 100644 --- /dev/null +++ b/kcms/input/mousesettings.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MOUSESETTINGS_H +#define MOUSESETTINGS_H + +#include + +class MouseBackend; + +enum class MouseHanded { + Right = 0, + Left = 1, + NotSupported = -1 +}; + +struct MouseSettings +{ + void save(KConfig *); + void load(KConfig *, MouseBackend*); + void apply(MouseBackend*, bool force = false); + + bool handedEnabled; + bool handedNeedsApply; + MouseHanded handed; + double accelRate; + int thresholdMove; + int doubleClickInterval; + int dragStartTime; + int dragStartDist; + bool singleClick; + int wheelScrollLines; + bool reverseScrollPolarity; + QString currentAccelProfile; +}; + +#endif // MOUSESETTINGS_H diff --git a/kcms/input/mousesettings.cpp b/kcms/input/mousesettings.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/mousesettings.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Xuetian Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mousesettings.h" + +#include "mousebackend.h" +#include +#include +#include +#include + +#include "../migrationlib/kdelibs4config.h" + +void MouseSettings::apply(MouseBackend* backend, bool force) +{ + if (!backend) { + return; + } + + backend->apply(*this, force); + handedNeedsApply = false; +} + +void MouseSettings::load(KConfig *config, MouseBackend *backend) +{ + // TODO: what's a good threshold default value + int threshold = 0; + handed = MouseHanded::Right; + double accel = 1.0; + QString profile; + if (backend) { + backend->load(); + auto handedOnServer = backend->handed(); + handedEnabled = handedOnServer != MouseHanded::NotSupported; + if (handedEnabled) { + handed = handedOnServer; + } + accel = backend->accelRate(); + threshold = backend->threshold(); + profile = backend->accelerationProfile(); + } + + KConfigGroup group = config->group("Mouse"); + double a = group.readEntry("Acceleration", -1.0); + if (a == -1) + accelRate = accel; + else + accelRate = a; + + int t = group.readEntry("Threshold", -1); + if (t == -1) + thresholdMove = threshold; + else + thresholdMove = t; + + QString key = group.readEntry("MouseButtonMapping"); + if (key == "RightHanded") + handed = MouseHanded::Right; + else if (key == "LeftHanded") + handed = MouseHanded::Left; + reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false); + currentAccelProfile = group.readEntry("AccelerationProfile"); + if (currentAccelProfile.isEmpty()) { + currentAccelProfile = profile; + } + handedNeedsApply = false; + + // SC/DC/AutoSelect/ChangeCursor + group = config->group("KDE"); + doubleClickInterval = group.readEntry("DoubleClickInterval", 400); + dragStartTime = group.readEntry("StartDragTime", 500); + dragStartDist = group.readEntry("StartDragDist", 4); + wheelScrollLines = group.readEntry("WheelScrollLines", 3); + + singleClick = group.readEntry("SingleClick", true); +} + +// see KGlobalSettings::emitChange +enum ChangeType { PaletteChanged = 0, FontChanged, StyleChanged, + SettingsChanged, IconChanged, CursorChanged, + ToolbarStyleChanged, ClipboardConfigChanged, + BlockShortcuts, NaturalSortingChanged + }; +enum SettingsCategory { SETTINGS_MOUSE, SETTINGS_COMPLETION, SETTINGS_PATHS, + SETTINGS_POPUPMENU, SETTINGS_QT, SETTINGS_SHORTCUTS, + SETTINGS_LOCALE, SETTINGS_STYLE + }; + +static void emitChange(ChangeType changeType, int arg) +{ + // see KGlobalSettings::emitChange + QDBusMessage message = QDBusMessage::createSignal("/KGlobalSettings", "org.kde.KGlobalSettings", "notifyChange"); + QList args; + args.append(static_cast(changeType)); + args.append(arg); + message.setArguments(args); + QDBusConnection::sessionBus().send(message); +} + +void MouseSettings::save(KConfig *config) +{ + KSharedConfig::Ptr kcminputProfile = KSharedConfig::openConfig("kcminputrc"); + KConfigGroup kcminputGroup(kcminputProfile, "Mouse"); + kcminputGroup.writeEntry("Acceleration",accelRate); + kcminputGroup.writeEntry("Threshold",thresholdMove); + if (handed == MouseHanded::Right) { + kcminputGroup.writeEntry("MouseButtonMapping",QString("RightHanded")); + } else { + kcminputGroup.writeEntry("MouseButtonMapping",QString("LeftHanded")); + } + kcminputGroup.writeEntry("ReverseScrollPolarity", reverseScrollPolarity); + kcminputGroup.writeEntry("AccelerationProfile", currentAccelProfile); + kcminputGroup.sync(); + + KSharedConfig::Ptr profile = KSharedConfig::openConfig("kdeglobals"); + KConfigGroup group(profile, "KDE"); + group.writeEntry("DoubleClickInterval", doubleClickInterval, KConfig::Persistent); + group.writeEntry("StartDragTime", dragStartTime, KConfig::Persistent); + group.writeEntry("StartDragDist", dragStartDist, KConfig::Persistent); + group.writeEntry("WheelScrollLines", wheelScrollLines, KConfig::Persistent); + group.writeEntry("SingleClick", singleClick, KConfig::Persistent); + + group.sync(); + config->sync(); + + Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("Mouse"), "kcminputrc"); + Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("KDE"), "kdeglobals"); + + emitChange(SettingsChanged, SETTINGS_MOUSE); +} diff --git a/kcms/keyboard/kcm_keyboard.desktop b/kcms/keyboard/kcm_keyboard.desktop --- a/kcms/keyboard/kcm_keyboard.desktop +++ b/kcms/keyboard/kcm_keyboard.desktop @@ -154,7 +154,7 @@ X-KDE-Keywords=Keyboard,Keyboard repeat,Click volume,Input Devices,repeat,volume,NumLock,NumPad,Keyboard type,Keyboard model,Keyboard layout,Key layout,Language,Alternate Keyboard,Keyboard switching,Ctrl Key,Caps Lock,Esperanto,Circumflex,Kill X Server,LED Keyboard,Compose Key X-KDE-Keywords[bs]=Tastatura,ponavljanje tastature,kliknute glasnoću,umetnuti uređaj,ponoviti,glasnoća,NumLock,NumPad,tip tastature,model tastature,izgled tastature,izgled tipki,jezik,rezervna tastatura,uključivanje tastature,Ctrl tipka,velika slova,esperanto,cirkumfleks,ukloniti X server,LED tastatura,sastavljene tipke -X-KDE-Keywords[ca]=Teclat,Repetició de teclat,Volum de clic,Dispositius d'entrada,repetició,volum,Bloq Núm,NumPad,Tipus de teclat,Model de teclat,Disposició de teclat,Idioma,Teclat alternatiu,Commutació de teclat,Tecla Ctrl,Bloq Maj,Esperanto,Circumflex,Matar servidor X,LED de teclat,Tecla compose +X-KDE-Keywords[ca]=Teclat,Repetició de teclat,Volum de clic,Dispositius d'entrada,repetició,volum,Bloq Núm,NumPad,Tipus de teclat,Model de teclat,Disposició de teclat,Idioma,Teclat alternatiu,Commutació de teclat,Tecla Ctrl,Bloq Maj,Esperanto,Circumflex,Matar servidor X,LED de teclat,Tecla de composició X-KDE-Keywords[ca@valencia]=Teclat,Repetició de teclat,Volum de clic,Dispositius d'entrada,repetició,volum,Bloq Núm,NumPad,Tipus de teclat,Model de teclat,Disposició de teclat,Idioma,Teclat alternatiu,Commutació de teclat,Tecla Ctrl,Bloq Maj,Esperanto,Circumflex,Matar servidor X,LED de teclat,Tecla compose X-KDE-Keywords[da]=Tastatur,keyboard,tastaturgentagelse,klikvolume,inputenheder,gentag,lydstyrke,NumLock,numerisk tastatur,tastaturtype,tastaturmodel,tastaturlayout,tastelayout,sprog,alternativt tastatur,skift af tastatur,Ctrl-tast,lås skift,caps lock,Esperanto,Circumflex,dræb X Server,LED-tastatur,Compose-tast X-KDE-Keywords[de]=Tastatur,Tastenwiederholung,Klicklautstärke,Eingabegeräte,Wiederholung,Lautstärke,Zahlen-Feststelltaste,Zahlenblock,Tastaturtyp,Tastaturmodell,Tastaturlayout,Tastenlayout,Sprache,Alternative Tastatur,Tastaturwechsel,Tastaturbelegung,Strg-Taste,Feststelltaste,Esperanto,Zirkumflex,X-Server beenden,Tastatur LED,Compose-Taste 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/krdb/krdb_libpathwipe.upd b/kcms/krdb/krdb_libpathwipe.upd --- a/kcms/krdb/krdb_libpathwipe.upd +++ b/kcms/krdb/krdb_libpathwipe.upd @@ -1,3 +1,3 @@ +Version=5 Id=LibraryPathWipeOut Script=krdb_clearlibrarypath -Version=5 \ No newline at end of file diff --git a/kcms/lookandfeel/CMakeLists.txt b/kcms/lookandfeel/CMakeLists.txt --- a/kcms/lookandfeel/CMakeLists.txt +++ b/kcms/lookandfeel/CMakeLists.txt @@ -73,18 +73,13 @@ add_executable(lookandfeeltool ${lookandfeeltool_SRCS}) target_link_libraries(lookandfeeltool -# Qt5::Gui -# Qt5::Widgets -# KF5::Package -# KF5::I18n - - KF5::KIOWidgets + KF5::KIOWidgets KF5::CoreAddons KF5::KCMUtils KF5::I18n #TODO:kpackage -KF5::Plasma -KF5::PlasmaQuick + KF5::Plasma + KF5::PlasmaQuick KF5::KDELibs4Support KF5::Declarative KF5::QuickAddons diff --git a/kcms/lookandfeel/kcm.cpp b/kcms/lookandfeel/kcm.cpp --- a/kcms/lookandfeel/kcm.cpp +++ b/kcms/lookandfeel/kcm.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include @@ -369,6 +371,41 @@ QStringLiteral("reloadConfig")); QDBusConnection::sessionBus().send(message); } + + //autostart + if (m_resetDefaultLayout) { + //remove all the old package to autostart + { + KSharedConfigPtr oldConf = KSharedConfig::openConfig(m_package.filePath("defaults")); + cg = KConfigGroup(oldConf, QStringLiteral("Autostart")); + const QStringList autostartServices = cg.readEntry("Services", QStringList()); + + if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { + for (const QString &serviceFile : autostartServices) { + KService service(serviceFile + QStringLiteral(".desktop")); + KAutostart as(serviceFile); + as.setAutostarts(false); + //FIXME: quite ugly way to stop things, and what about non KDE things? + QProcess::startDetached(QStringLiteral("kquitapp5"), {QStringLiteral("--service"), service.property(QStringLiteral("X-DBUS-ServiceName")).toString()}); + } + } + } + //Set all the stuff in the new lnf to autostart + { + cg = KConfigGroup(conf, QStringLiteral("Autostart")); + const QStringList autostartServices = cg.readEntry("Services", QStringList()); + + for (const QString &serviceFile : autostartServices) { + KService service(serviceFile + QStringLiteral(".desktop")); + KAutostart as(serviceFile); + as.setCommand(service.exec()); + as.setAutostarts(true); + if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { + KRun::runApplication(service, {}, nullptr); + } + } + } + } } //TODO: option to enable/disable apply? they don't seem required by UI design diff --git a/kcms/lookandfeel/lnftool.cpp b/kcms/lookandfeel/lnftool.cpp --- a/kcms/lookandfeel/lnftool.cpp +++ b/kcms/lookandfeel/lnftool.cpp @@ -91,6 +91,7 @@ kcm->setResetDefaultLayout(parser.isSet(_resetLayout)); kcm->setSelectedPlugin(parser.value(_apply)); kcm->save(); + delete kcm; } return 0; diff --git a/kcms/nightcolor/CMakeLists.txt b/kcms/nightcolor/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/CMakeLists.txt @@ -0,0 +1,24 @@ +# KI18N Translation Domain for this library +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_nightcolor\") + +set(kcm_nightcolor_SRCS + kcm.cpp +) + +add_library(kcm_nightcolor MODULE ${kcm_nightcolor_SRCS}) + +target_link_libraries(kcm_nightcolor + KF5::QuickAddons + KF5::I18n + + PW::LibColorCorrect +) + +kcoreaddons_desktop_to_json(kcm_nightcolor "kcm_nightcolor.desktop" SERVICE_TYPES kcmodule.desktop) + +#this desktop file is installed only for retrocompatibility with sycoca +install(FILES kcm_nightcolor.desktop DESTINATION ${SERVICES_INSTALL_DIR}) + +install(TARGETS kcm_nightcolor DESTINATION ${PLUGIN_INSTALL_DIR}/kcms) + +kpackage_install_package(package kcm_nightcolor kcms) diff --git a/kcms/nightcolor/Messages.sh b/kcms/nightcolor/Messages.sh new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_nightcolor.pot diff --git a/kcms/nightcolor/kcm.h b/kcms/nightcolor/kcm.h new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/kcm.h @@ -0,0 +1,44 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +#ifndef _KCM_NIGHTCOLOR_H +#define _KCM_NIGHTCOLOR_H + +#include + +namespace ColorCorrect { + +class KCMNightColor : public KQuickAddons::ConfigModule +{ + Q_OBJECT +public: + KCMNightColor(QObject* parent, const QVariantList& args); + ~KCMNightColor() {} + +public Q_SLOTS: + void load(); + void save(); + void defaults(); + +Q_SIGNALS: + void loadRelay(); + void saveRelay(); + void defaultsRelay(); +}; + +} + +#endif // _KCM_NIGHTCOLOR_H diff --git a/kcms/nightcolor/kcm.cpp b/kcms/nightcolor/kcm.cpp new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/kcm.cpp @@ -0,0 +1,55 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +#include "kcm.h" + +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(KCMNightColorFactory, "kcm_nightcolor.json", registerPlugin();) + +namespace ColorCorrect { + +KCMNightColor::KCMNightColor(QObject *parent, const QVariantList& args) + : KQuickAddons::ConfigModule(parent, args) +{ + KAboutData* about = new KAboutData(QStringLiteral("kcm_nightcolor"), i18n("Night Color"), + QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); + about->addAuthor(i18n("Roman Gilg"), QString(), QStringLiteral("subdiff@gmail.com")); + setAboutData(about); + setButtons(Apply | Default); +} + +void KCMNightColor::load() +{ + emit loadRelay(); +} + +void KCMNightColor::defaults() +{ + emit defaultsRelay(); +} + +void KCMNightColor::save() +{ + emit saveRelay(); +} + +} + +#include "kcm.moc" diff --git a/kcms/nightcolor/kcm_nightcolor.desktop b/kcms/nightcolor/kcm_nightcolor.desktop new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/kcm_nightcolor.desktop @@ -0,0 +1,65 @@ +[Desktop Entry] +Icon=preferences-desktop-display-nightcolor +Exec=kcmshell5 kcm_nightcolor + +Type=Service +X-KDE-ServiceTypes=KCModule + +X-DocPath=https://userbase.kde.org/TODOX + +X-KDE-Library=kcm_nightcolor +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=display +X-KDE-Weight=120 + +X-KDE-OnlyShowOnQtPlatforms=wayland + +Name=Night Color +Name[ca]=Color de nit +Name[ca@valencia]=Color de nit +Name[es]=Color nocturno +Name[fr]=Couleur de nuit +Name[it]=Colore notturno +Name[nl]=Nachtkleur +Name[pt]=Cor Nocturna +Name[pt_BR]=Cor noturna +Name[sr]=Ноћна боја +Name[sr@ijekavian]=Ноћна боја +Name[sr@ijekavianlatin]=Noćna boja +Name[sr@latin]=Noćna boja +Name[sv]=Nattfärg +Name[uk]=Нічні кольори +Name[x-test]=xxNight Colorxx +Comment=Adjust color temperature at night to reduce eye strain +Comment[ca]=Ajusta la temperatura del color per la nit per reduir la tensió ocular +Comment[ca@valencia]=Ajusta la temperatura del color per la nit per reduir la tensió ocular +Comment[es]=Ajustar la temperatura del color durante la noche para reducir la fatiga visual +Comment[fr]=Ajuster la température des couleurs durant la nuit pour réduire la fatigue des yeux +Comment[nl]=Kleurtemperatuur bij nacht aanpassen on vermoeidheid van de ogen te vermijden +Comment[pt]=Ajustar a temperatura da cor à noite para reduzir o cansaço dos olhos +Comment[sr]=Подесите температуру боје током ноћи ради мањег напрезања очију +Comment[sr@ijekavian]=Подесите температуру боје током ноћи ради мањег напрезања очију +Comment[sr@ijekavianlatin]=Podesite temperaturu boje tokom noći radi manjeg naprezanja očiju +Comment[sr@latin]=Podesite temperaturu boje tokom noći radi manjeg naprezanja očiju +Comment[sv]=Justera färgtemperatur på natten för att reducera ögonbelastning +Comment[uk]=Коригування температури кольорів для зменшення навантаження на очі вночі +Comment[x-test]=xxAdjust color temperature at night to reduce eye strainxx + +X-KDE-Keywords=kwin,window,manager,night,colors,redshift,eyes +X-KDE-Keywords[ca]=kwin,finestra,gestor,nit,colors,desplaçament cap al roig,ulls +X-KDE-Keywords[ca@valencia]=kwin,finestra,gestor,nit,colors,desplaçament cap al roig,ulls +X-KDE-Keywords[es]=kwin,ventana,gestor,noche,colores,corrimiento al rojo,ojos +X-KDE-Keywords[fr]=kwin, gestionnaire, fenêtres, couleurs, nuit, colors, redshift, yeux +X-KDE-Keywords[it]=kwin,finestra,gestore,notte,colori,spostamento verso il rosso,occhi +X-KDE-Keywords[nl]=kwin,venster,manager,nacht,kleuren,roodverschuiving,ogen +X-KDE-Keywords[pt]=kwin,janela,gestor,noite,cores,desvio do vermelho,olhos +X-KDE-Keywords[pt_BR]=kwin,janela,gerenciador,noite,noturno,cores,deslocamento de vermelho,olhos +X-KDE-Keywords[sr]=kwin,window,manager,night,colors,redshift,eyes,К‑вин,прозор,менаџер,ноћ,ноћни,боја,црвени помак,очи +X-KDE-Keywords[sr@ijekavian]=kwin,window,manager,night,colors,redshift,eyes,К‑вин,прозор,менаџер,ноћ,ноћни,боја,црвени помак,очи +X-KDE-Keywords[sr@ijekavianlatin]=kwin,window,manager,night,colors,redshift,eyes,KWin,prozor,menadžer,noć,noćni,boja,crveni pomak,oči +X-KDE-Keywords[sr@latin]=kwin,window,manager,night,colors,redshift,eyes,KWin,prozor,menadžer,noć,noćni,boja,crveni pomak,oči +X-KDE-Keywords[sv]=kwin,fönster,hanterare,natt,färger,rödskift,ögon +X-KDE-Keywords[uk]=kwin,window,manager,night,colors,redshift,eyes,вікно,керування,ніч,кольори,очі +X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxnightxx,xxcolorsxx,xxredshiftxx,xxeyesxx +Categories=Qt;KDE;X-KDE-settings-nightcolor diff --git a/kcms/nightcolor/package/contents/ui/LocationsAutoView.qml b/kcms/nightcolor/package/contents/ui/LocationsAutoView.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/LocationsAutoView.qml @@ -0,0 +1,53 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls + +GridLayout { + columns: 2 + rowSpacing: units.smallSpacing + columnSpacing: units.smallSpacing + enabled: activator.checked + + property double latitude + property double longitude + + onLatitudeChanged: latitudeField.backend = latitude; + onLongitudeChanged: longitudeField.backend = longitude; + + Controls.Label { + text: i18n("Latitude") + Layout.alignment: Qt.AlignRight + enabled: false + } + NumberField { + id: latitudeField + backend: locator.latitude + enabled: false + } + Controls.Label { + text: i18n("Longitude") + Layout.alignment: Qt.AlignRight + enabled: false + } + NumberField { + id: longitudeField + backend: locator.longitude + enabled: false + } +} diff --git a/kcms/nightcolor/package/contents/ui/LocationsFixedView.qml b/kcms/nightcolor/package/contents/ui/LocationsFixedView.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/LocationsFixedView.qml @@ -0,0 +1,75 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls + +GridLayout { + columns: 2 + rowSpacing: units.smallSpacing + columnSpacing: units.smallSpacing + enabled: activator.checked + + Connections { + target: root + onReset: reset() + } + + function reset() { + latitudeFixedField.backend = cA.latitudeFixed; + longitudeFixedField.backend = cA.longitudeFixed; + } + + Controls.Label { + text: i18n("Latitude") + Layout.alignment: Qt.AlignRight + } + NumberField { + id: latitudeFixedField + backend: cA.latitudeFixedStaged + validator: DoubleValidator {bottom: -90; top: 90; decimals: 10} + onBackendChanged: { + cA.latitudeFixedStaged = backend; + manualLocationsViewRow.change(); + calcNeedsSave(); + } + } + + Controls.Label { + text: i18n("Longitude") + Layout.alignment: Qt.AlignRight + } + NumberField { + id: longitudeFixedField + backend: cA.longitudeFixedStaged + validator: DoubleValidator {bottom: -180; top: 180; decimals: 10} + onBackendChanged: { + cA.longitudeFixedStaged = backend; + manualLocationsViewRow.change(); + calcNeedsSave(); + } + } + Controls.Button { + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignHCenter + text: i18n("Detect location") + onClicked: { + latitudeFixedField.backend = locator.latitude; + longitudeFixedField.backend = locator.longitude; + } + } +} diff --git a/kcms/nightcolor/package/contents/ui/NumberField.qml b/kcms/nightcolor/package/contents/ui/NumberField.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/NumberField.qml @@ -0,0 +1,48 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Controls 1.4 as Controls + +Controls.TextField { + property double backend + + maximumLength: 10 + horizontalAlignment: TextInput.AlignHCenter + + inputMethodHints: Qt.ImhFormattedNumbersOnly + + text: backend + + onBackendChanged: { + text = backend; + } + + onTextChanged: { + var textFloat = parseFloat(text); + if (textFloat == undefined || isNaN(textFloat)) { + return; + } + backend = textFloat; + } + + onFocusChanged: { + var textFloat = parseFloat(text); + if (!focus && (textFloat == undefined || isNaN(textFloat))) { + text = backend; + } + } +} diff --git a/kcms/nightcolor/package/contents/ui/TimeField.qml b/kcms/nightcolor/package/contents/ui/TimeField.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/TimeField.qml @@ -0,0 +1,71 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls + +Controls.TextField { + id: field + + property date backend + horizontalAlignment: TextInput.AlignHCenter + + onBackendChanged: readIn() + + function getNormedDate() { + var nD = new Date(); + nD.setHours(backend.getHours()); + nD.setMinutes(backend.getMinutes()); + return nD; + } + + function readIn() { + if (!backend) { + return; + } + + var hours = backend.getHours(); + var minutes = backend.getMinutes(); + if (hours < 10) { + hours = "0" + hours; + } + if (minutes < 10) { + minutes = "0" + minutes; + } + + text = hours + ":" + minutes; + } + + function submit() { + if (text.length != 5) { + return; + } + var hours = text.slice(0, 2); + var minutes = text.slice(3, 5); + + var date = new Date(); + date.setHours(hours); + date.setMinutes(minutes); + + backend = date; + } + + inputMethodHints: Qt.ImhPreferNumbers + validator: RegExpValidator { regExp: /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/ } + + onEditingFinished: submit() +} diff --git a/kcms/nightcolor/package/contents/ui/TimingsView.qml b/kcms/nightcolor/package/contents/ui/TimingsView.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/TimingsView.qml @@ -0,0 +1,78 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls + +GridLayout { + columns: 2 + rowSpacing: units.smallSpacing + columnSpacing: units.smallSpacing + enabled: activator.checked + + property double latitude + property double longitude + + property var morningTimings: sunCalc.getMorningTimings(latitude, longitude) + property var eveningTimings: sunCalc.getEveningTimings(latitude, longitude) + + function reset() { + morningTimings = sunCalc.getMorningTimings(latitude, longitude); + eveningTimings = sunCalc.getEveningTimings(latitude, longitude); + } + + Controls.Label { + text: i18n("Sunrise begins") + Layout.alignment: Qt.AlignRight + enabled: false + } + TimeField { + id: mornBeginField + backend: morningTimings.begin + enabled: false + } + Controls.Label { + text: i18n("and ends") + Layout.alignment: Qt.AlignRight + enabled: false + } + TimeField { + id: mornEndField + backend: morningTimings.end + enabled: false + } + Controls.Label { + text: i18n("Sunset begins") + Layout.alignment: Qt.AlignRight + enabled: false + } + TimeField { + id: evenBeginField + backend: eveningTimings.begin + enabled: false + } + Controls.Label { + text: i18n("and ends") + Layout.alignment: Qt.AlignRight + enabled: false + } + TimeField { + id: evenEndField + backend: eveningTimings.end + enabled: false + } +} diff --git a/kcms/nightcolor/package/contents/ui/main.qml b/kcms/nightcolor/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/contents/ui/main.qml @@ -0,0 +1,362 @@ +/******************************************************************** +Copyright 2017 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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. If not, see . +*********************************************************************/ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls + +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +import org.kde.colorcorrect 0.1 as CC + +Item { + id: root + + implicitHeight: units.gridUnit * 20 + + property int error: cA.error + + property bool defaultRequested: false + + CC.CompositorAdaptor { + id: cA + } + + CC.Geolocator { + id: locator + } + + CC.SunCalc { + id: sunCalc + } + + function calcNeedsSave() { + kcm.needsSave = cA.checkStaged(); + // If the user changes something manually again after + // Default was triggered, only allow a normal + // configuration change again: + defaultRequested = false; + } + + Connections { + target: kcm + onLoadRelay: cA.reloadData() + onSaveRelay: defaultRequested ? cA.sendConfigurationAll() : cA.sendConfiguration(); + onDefaultsRelay: { + if (!cA.nightColorAvailable) { + return; + } + activator.checked = cA.activeDefault; + tempSlider.value = cA.nightTemperatureDefault; + modeSwitcher.currentIndex = cA.modeDefault; + + // set everything else to default as well + cA.latitudeFixedStaged = cA.latitudeFixedDefault; + cA.longitudeFixedStaged = cA.longitudeFixedDefault; + + cA.morningBeginFixedStaged = cA.morningBeginFixedDefault; + cA.eveningBeginFixedStaged = cA.eveningBeginFixedDefault; + cA.transitionTimeStaged = cA.transitionTimeDefault; + + kcm.needsSave = cA.checkStagedAll(); + defaultRequested = true; + } + } + + Connections { + target: cA + onDataUpdated: calcNeedsSave() + onStagedDataReset: { + activator.checked = cA.active; + tempSlider.value = cA.nightTemperature; + modeSwitcher.currentIndex = cA.mode; + reset(); + calcNeedsSave(); + } + } + signal reset() + + Item { + id: main + width: childrenRect.width + height: childrenRect.height + + enabled: cA.nightColorAvailable + + Controls.CheckBox { + id: activator + text: i18n("Activate Night Color") + checked: cA.active + + onCheckedChanged: { + cA.activeStaged = checked; + calcNeedsSave(); + } + } + + ColumnLayout { + id: mainControls + anchors.top: activator.bottom + anchors.topMargin: units.largeSpacing + + enabled: activator.checked + + Controls.Label { + text: i18n("Night Color Temperature: ") + tempSlider.value + i18n(" K") + } + + Controls.Slider { + id: tempSlider + implicitWidth: units.gridUnit * 15 + + minimumValue: cA.minimalTemperature + maximumValue: cA.neutralTemperature + value: cA.nightTemperature + stepSize: 100 + + onValueChanged: { + cA.nightTemperatureStaged = value; + calcNeedsSave(); + } + } + + RowLayout { + spacing: units.largeSpacing + Controls.Label { + text: i18n("Operation mode:") + } + Controls.ComboBox { + id: modeSwitcher + width: theme.mSize(theme.defaultFont).width * 9 + model: [i18n("Automatic"), + i18n("Location"), + i18n("Times")] + currentIndex: cA.mode + onCurrentIndexChanged: { + cA.modeStaged = currentIndex; + advancedControlLoader.updatePage(currentIndex); + calcNeedsSave(); + } + } + } + } + + Loader { + id: advancedControlLoader + + anchors.top: mainControls.bottom + anchors.topMargin: units.largeSpacing + + function updatePage(index) { + sourceComponent = undefined; + var page; + if (index == CC.CompositorAdaptor.ModeLocation) { + page = manualLocationsView; + } else if (index == CC.CompositorAdaptor.ModeTimings) { + page = manualTimingsView; + } else { + page = automaticView; + } + + sourceComponent = page; + } + } + + Component { + id: automaticView + Row { + spacing: units.largeSpacing + + Loader { + sourceComponent: TimingsView { + latitude: locator.latitude + longitude: locator.longitude + } + } + Loader { + sourceComponent: LocationsAutoView { + latitude: locator.latitude + longitude: locator.longitude + } + } + } + } + + Component { + id: manualLocationsView + + Row { + id: manualLocationsViewRow + spacing: units.largeSpacing + + signal change() + + Loader { + sourceComponent: TimingsView { + latitude: cA.latitudeFixedStaged + longitude: cA.longitudeFixedStaged + + Connections { + target: manualLocationsViewRow + onChange: { + reset(); + } + } + } + } + Loader { + sourceComponent: LocationsFixedView {} + } + } + } + + Component { + id: manualTimingsView + Column { + spacing: units.smallSpacing + + GridLayout { + id: manualTimingsViewGrid + + columns: 3 + rowSpacing: units.smallSpacing + columnSpacing: units.smallSpacing + enabled: activator.checked && cA.timingsEnabled + + Connections { + target: root + onReset: { + mornBeginManField.backend = cA.morningBeginFixed; + evenBeginManField.backend = cA.eveningBeginFixed; + transTimeField.backend = cA.transitionTime; + } + } + + Controls.Label { + text: i18n("Sunrise begins") + Layout.alignment: Qt.AlignRight + } + TimeField { + id: mornBeginManField + backend: cA.morningBeginFixedStaged + onBackendChanged: {cA.morningBeginFixedStaged = backend; + calcNeedsSave(); + } + } + Controls.Label { + enabled: false + text: i18n("(HH:MM)") + } + Controls.Label { + text: i18n("Sunset begins") + Layout.alignment: Qt.AlignRight + } + TimeField { + id: evenBeginManField + backend: cA.eveningBeginFixedStaged + onBackendChanged: {cA.eveningBeginFixedStaged = backend; + calcNeedsSave(); + } + } + Controls.Label { + enabled: false + text: i18n("(HH:MM)") + } + Controls.Label { + text: i18n("Transition duration") + Layout.alignment: Qt.AlignRight + } + NumberField { + id: transTimeField + backend: cA.transitionTimeStaged + onBackendChanged: {cA.transitionTimeStaged = backend; + calcNeedsSave(); + } + + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator {bottom: 1; top: 600;} // less than 12 hours (in minutes: 720) + } + Controls.Label { + enabled: false + text: i18n("(In minutes - min. 1, max. 600)") + } + } + Controls.Label { + id: manualTimingsError1 + anchors.horizontalCenter: manualTimingsViewGrid.horizontalCenter + visible: evenBeginManField.getNormedDate() - mornBeginManField.getNormedDate() <= 0 + + font.italic: true + text: i18n("Error: Morning not before evening.") + } + Controls.Label { + id: manualTimingsError2 + anchors.horizontalCenter: manualTimingsViewGrid.horizontalCenter + visible: { + if (manualTimingsError1.visible) { + return false; + } + var trTime = transTimeField.backend * 60 * 1000; + var mor = mornBeginManField.getNormedDate(); + var eve = evenBeginManField.getNormedDate(); + + return eve - mor <= trTime || eve - mor >= 86400000 - trTime; + } + font.italic: true + text: i18n("Error: Transition time overlaps.") + } + } + } + } + + // error message as overlay + Item { + width: 0.8 * main.width + height: 0.8 * main.height + anchors.centerIn: main + + visible: error != CC.CompositorAdaptor.ErrorCodeSuccess + + Rectangle { + anchors.centerIn: parent + width: errorMessage.contentWidth * 1.1 + height: errorMessage.contentHeight * 1.1 + color: theme.backgroundColor + opacity: 0.8 + + border { + width: units.devicePixelRatio + color: theme.textColor + } + } + + PlasmaExtras.Heading { + id: errorMessage + anchors.fill: parent + + level: 4 + text: cA.errorText + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + wrapMode: Text.Wrap + textFormat: Text.PlainText + } + } +} diff --git a/kcms/nightcolor/package/metadata.desktop b/kcms/nightcolor/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/nightcolor/package/metadata.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Name=Night Color +Name[ca]=Color de nit +Name[ca@valencia]=Color de nit +Name[es]=Color nocturno +Name[fr]=Couleur de nuit +Name[it]=Colore notturno +Name[nl]=Nachtkleur +Name[pt]=Cor Nocturna +Name[pt_BR]=Cor noturna +Name[sr]=Ноћна боја +Name[sr@ijekavian]=Ноћна боја +Name[sr@ijekavianlatin]=Noćna boja +Name[sr@latin]=Noćna boja +Name[sv]=Nattfärg +Name[uk]=Нічні кольори +Name[x-test]=xxNight Colorxx +Comment=Adjust color temperatur at night to reduce eye strain +Comment[ca]=Ajusta la temperatura del color per la nit per reduir la tensió ocular. +Comment[ca@valencia]=Ajusta la temperatura del color per la nit per reduir la tensió ocular. +Comment[es]=Ajustar la temperatura del color durante la noche para reducir la fatiga visual +Comment[fr]=Ajuster la température des couleurs durant la nuit pour réduire la fatigue des yeux +Comment[it]=Regola la temperatura dei colori di notte per ridurre l'affaticamento degli occhi +Comment[nl]=Kleurtemperatuur bij nacht aanpassen on vermoeidheid van de ogen te vermijden +Comment[pt]=Ajusta a temperatura da cor à noite para reduzir o cansaço dos olhos +Comment[pt_BR]=Ajustar a temperatura de cor a noite para reduzir a tensão visual +Comment[sr]=Подесите температуру боје током ноћи ради мањег напрезања очију +Comment[sr@ijekavian]=Подесите температуру боје током ноћи ради мањег напрезања очију +Comment[sr@ijekavianlatin]=Podesite temperaturu boje tokom noći radi manjeg naprezanja očiju +Comment[sr@latin]=Podesite temperaturu boje tokom noći radi manjeg naprezanja očiju +Comment[sv]=Justera färgtemperatur på natten för att reducera ögonbelastning +Comment[uk]=Коригування температури кольорів для зменшення навантаження на очі вночі +Comment[x-test]=xxAdjust color temperatur at night to reduce eye strainxx + +Icon=preferences-desktop +Encoding=UTF-8 +Keywords= +Type=Service +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Roman Gilg +X-KDE-PluginInfo-Email=subdiff@gmail.com +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_nightcolor +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/phonon/CMakeLists.txt b/kcms/phonon/CMakeLists.txt --- a/kcms/phonon/CMakeLists.txt +++ b/kcms/phonon/CMakeLists.txt @@ -43,7 +43,7 @@ include_directories(${GLIB2_INCLUDE_DIR} ${PULSEAUDIO_INCLUDE_DIR} ${CANBERRA_INCLUDE_DIRS}) - set(kcmphonon_LIBS ${kcmphonon_LIBS} ${GLIB2_LIBRARIES} ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${CANBERRA_LIBRARIES}) + set(kcmphonon_LIBS ${kcmphonon_LIBS} ${GLIB2_LIBRARIES} ${PulseAudio_LIBRARIES} ${PulseAudio_MAINLOOP_LIBRARY} ${CANBERRA_LIBRARIES}) endif() add_library(kcm_phonon MODULE ${kcmphonon_SRCS}) 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 @@ -58,10 +58,19 @@ Comment=Configure Search Bar 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[de]=Suchleiste einrichten +Comment[en_GB]=Configure Search Bar Comment[es]=Configurar la barra de búsqueda +Comment[eu]=Konfiguratu bilaketa-barra +Comment[fr]=Configurer la barre de recherche +Comment[hu]=Keresősáv beállítása Comment[id]=Konfigurasi Bilah Pencarian Comment[it]=Configura la barra di ricerca +Comment[ko]=검색 표시줄 설정 Comment[nl]=Zoekbalk configureren +Comment[pl]=Ustawienia paska wyszukiwania Comment[pt]=Configurar a Barra de Pesquisa Comment[pt_BR]=Configura a barra de pesquisa Comment[ru]=Настройка строки поиска и запуска @@ -71,8 +80,10 @@ Comment[sr@ijekavianlatin]=Podešavanje trake pretrage Comment[sr@latin]=Podešavanje trake pretrage Comment[sv]=Anpassa sökrad +Comment[tr]=Arama Çubuğunu Yapılandır Comment[uk]=Налаштування панелі пошуку Comment[x-test]=xxConfigure Search Barxx +Comment[zh_CN]=配置搜索栏 X-KDE-Keywords=Search, File, Baloo, Runner, krunner X-KDE-Keywords[ar]=ابحث,ملف,بالو,مشغّل,مشغّلك X-KDE-Keywords[bs]=Pretraži,fajl,Baloo,Trkač,krunner diff --git a/kcms/solid_actions/device-actions/solid-device-OpticalDisc.desktop b/kcms/solid_actions/device-actions/solid-device-OpticalDisc.desktop --- a/kcms/solid_actions/device-actions/solid-device-OpticalDisc.desktop +++ b/kcms/solid_actions/device-actions/solid-device-OpticalDisc.desktop @@ -332,7 +332,7 @@ Name[nl]=Schijftype Name[nn]=Disktype Name[pa]=ਡਿਸਕ ਕਿਸਮ -Name[pl]=Typ dysku +Name[pl]=Rodzaj dysku Name[pt]=Tipo de Disco Name[pt_BR]=Tipo de disco Name[ro]=Tipul discului @@ -404,7 +404,7 @@ Name[nl]=FS-type Name[nn]=Filsystemtype Name[pa]=Fs ਕਿਸਮ -Name[pl]=Typ systemu plików +Name[pl]=Rodzaj systemu plików Name[pt]=Tipo de SF Name[pt_BR]=Tipo de sistema de arquivos Name[ro]=Tipul SF diff --git a/kcms/solid_actions/device-actions/solid-device-StorageVolume.desktop b/kcms/solid_actions/device-actions/solid-device-StorageVolume.desktop --- a/kcms/solid_actions/device-actions/solid-device-StorageVolume.desktop +++ b/kcms/solid_actions/device-actions/solid-device-StorageVolume.desktop @@ -46,7 +46,7 @@ Name[nl]=FS-type Name[nn]=Filsystemtype Name[pa]=Fs ਕਿਸਮ -Name[pl]=Typ systemu plików +Name[pl]=Rodzaj systemu plików Name[pt]=Tipo de SF Name[pt_BR]=Tipo de sistema de arquivos Name[ro]=Tipul SF diff --git a/kcms/solid_actions/solid-device-type.desktop b/kcms/solid_actions/solid-device-type.desktop --- a/kcms/solid_actions/solid-device-type.desktop +++ b/kcms/solid_actions/solid-device-type.desktop @@ -45,7 +45,7 @@ Name[nl]=Solid-apparaattype Name[nn]=Type Solid-eining Name[pa]=ਸਾਲਡ ਜੰਤਰ ਕਿਸਮ -Name[pl]=Typ urządzenia Solid +Name[pl]=Rodzaj urządzenia Solid Name[pt]=Tipo de Dispositivo do Solid Name[pt_BR]=Tipo de dispositivo do Solid Name[ro]=Tip dispozitiv solid diff --git a/kcms/style/finetuning.ui b/kcms/style/finetuning.ui --- a/kcms/style/finetuning.ui +++ b/kcms/style/finetuning.ui @@ -25,7 +25,7 @@ - + Qt::Vertical @@ -41,7 +41,7 @@ - Show icons on buttons: + Show icons in b&uttons: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -60,7 +60,7 @@ - Main toolbar text: + Main &toolbar text location: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -97,7 +97,7 @@ - Secondary toolbar text: + Secondary toolbar text &location: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -147,60 +147,6 @@ - - - - Menubar - - - - - - Menubar style: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - comboMenubarStyle - - - - - - - - In application - - - - - Title bar button - - - - - Application Menu widget - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - @@ -214,22 +160,14 @@ - - - KComboBox QComboBox
kcombobox.h
- - KMessageWidget - QFrame -
kmessagewidget.h
-
diff --git a/kcms/style/kcmstyle.cpp b/kcms/style/kcmstyle.cpp --- a/kcms/style/kcmstyle.cpp +++ b/kcms/style/kcmstyle.cpp @@ -242,12 +242,9 @@ connect(fineTuningUi.cbIconsInMenus, &QAbstractButton::toggled, this, &KCMStyle::setEffectsDirty); connect(fineTuningUi.comboToolbarIcons, SIGNAL(activated(int)), this, SLOT(setEffectsDirty())); connect(fineTuningUi.comboSecondaryToolbarIcons, SIGNAL(activated(int)), this, SLOT(setEffectsDirty())); - connect(fineTuningUi.comboMenubarStyle, SIGNAL(activated(int)), this, SLOT(setEffectsDirty())); addWhatsThis(); - fineTuningUi.menuBarMessageWidget->hide(); - // Insert the pages into the tabWidget tabWidget->addTab(page1, i18nc("@title:tab", "&Applications")); tabWidget->addTab(page2, i18nc("@title:tab", "&Fine Tuning")); @@ -377,50 +374,6 @@ toolbarStyleGroup.writeEntry("ToolButtonStyleOtherToolbars", toolbarButtonText(fineTuningUi.comboSecondaryToolbarIcons->currentIndex())); - // menubar page - KConfigGroup menuBarStyleGroup(&_config, "Appmenu Style"); - - QString style = menuBarStyleText(fineTuningUi.comboMenubarStyle->currentIndex()); - - QString previous = menuBarStyleGroup.readEntry("Style", "InApplication"); - menuBarStyleGroup.writeEntry("Style", style); - _config.sync(); - - // The old KCM used to mess with autoloading depending on whether menu was enabled or not - // since it was always disabled, the kded module would never autoload breaking global menu - // for users without an obvious reason why it won't work. - QDBusMessage method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kded5"), - QStringLiteral("/kded"), - QStringLiteral("org.kde.kded5"), - QStringLiteral("setModuleAutoloading")); - method.setArguments({QStringLiteral("appmenu"), true}); - QDBusConnection::sessionBus().asyncCall(method); - - method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kded5"), - QStringLiteral("/kded"), - QStringLiteral("org.kde.kded5"), - QStringLiteral("loadModule")); - method.setArguments({QStringLiteral("appmenu")}); - QDBusConnection::sessionBus().asyncCall(method); - - // since we load the module async, this call will fail if the module wasn't loaded - // but since it will init itself when it loads, this isn't too bad - QDBusConnection::sessionBus().asyncCall( - QDBusMessage::createMethodCall(QStringLiteral("org.kde.kappmenu"), - QStringLiteral("/KAppMenu"), - QStringLiteral("org.kde.kappmenu"), - QStringLiteral("reconfigure") - ) - ); - - const bool showMenuInApplication = (style == QLatin1String("InApplication")); - - if (previous == QLatin1String("InApplication") && !showMenuInApplication) { - fineTuningUi.menuBarMessageWidget->setMessageType(KMessageWidget::Information); - fineTuningUi.menuBarMessageWidget->setText(i18n("Your changes will take effect only on application restart.")); - fineTuningUi.menuBarMessageWidget->animatedShow(); - } - // Export the changes we made to qtrc, and update all qt-only // applications on the fly, ensuring that we still follow the user's // export fonts/colors settings. @@ -508,7 +461,6 @@ // Effects fineTuningUi.comboToolbarIcons->setCurrentIndex(toolbarButtonIndex(QStringLiteral("TextBesideIcon"))); fineTuningUi.comboSecondaryToolbarIcons->setCurrentIndex(toolbarButtonIndex(QStringLiteral("TextBesideIcon"))); - fineTuningUi.comboMenubarStyle->setCurrentIndex(menuBarStyleIndex(QStringLiteral("InApplication"))); fineTuningUi.cbIconsOnButtons->setChecked(true); fineTuningUi.cbIconsInMenus->setChecked(true); emit changed(true); @@ -762,10 +714,6 @@ tbIcon = configGroup.readEntry("ToolButtonStyleOtherToolbars", "TextBesideIcon"); fineTuningUi.comboSecondaryToolbarIcons->setCurrentIndex(toolbarButtonIndex(tbIcon)); - configGroup = config.group("Appmenu Style"); - QString menuBarStyle = configGroup.readEntry("Style", "InApplication"); - fineTuningUi.comboMenubarStyle->setCurrentIndex(menuBarStyleIndex(menuBarStyle)); - configGroup = config.group("KDE"); fineTuningUi.cbIconsOnButtons->setChecked(configGroup.readEntry("ShowIconsOnPushButtons", true)); fineTuningUi.cbIconsInMenus->setChecked(configGroup.readEntry("ShowIconsInMenuItems", true)); diff --git a/kcms/style/style.desktop b/kcms/style/style.desktop --- a/kcms/style/style.desktop +++ b/kcms/style/style.desktop @@ -108,10 +108,15 @@ X-KDE-Keywords[ca]=estil,estils,aparença,estri,icones,barres d'eines,text,ressaltat,aplicacions,aplicacions del KDE,tema,plasma,menu,menu global X-KDE-Keywords[ca@valencia]=estil,estils,aparença,estri,icones,barres d'eines,text,ressaltat,aplicacions,aplicacions del KDE,tema,plasma,menu,menu global X-KDE-Keywords[da]=stil,style,udseende,kontrol,widget,ikoner,værktøjslinjer,tekst,fremhævning,programmer,KDE-programmer,tema,plasma,menu,global menu +X-KDE-Keywords[de]=Stile,Design,Themes,Schema,Elemente,Bildschirmelemente,Icons,Bedienelemente,Schriften,Symbole,Werkzeugleisten,Text,Hervorhebungen,Knöpfe,Anwendungen,Programme,KDE-Programme,Menü,Globales Menü +X-KDE-Keywords[en_GB]=style,styles,look,widget,icons,toolbars,text,highlight,apps,KDE applications,theme,plasma,menu,global menu X-KDE-Keywords[es]=estilo,estilos,apariencia,controles,iconos,barras de herramientas,texto,resaltado,aplicaciones,aplicaciones de KDE,tema,plasma,menú,menú global +X-KDE-Keywords[eu]=estilo,estiloak,itxura,trepeta,ikonoak,tresna-barrak,testua,nabarmentzea,aplikazioak,KDE aplikazioak,gaia,plasma,menu,menu orokorra X-KDE-Keywords[fr]=style, styles, apparence, composant graphique, icônes, barres d'outil, texte, mise en valeur, applications, applications KDE, thème, plasma, menu, menu global +X-KDE-Keywords[hu]=stílus,stílusok,kinézet,widget,ikonok,eszköztárak,szöveg,kiemelés,alkalmazások,KDe alkalmazások,téma,plazma,menü,globális menü X-KDE-Keywords[id]=gaya,gaya,look,widget,ikon,bilah alat,teks,sorot,apl,aplikasi KDE,tema,plasma,menu,menu global X-KDE-Keywords[it]=stile,stili,aspetto,oggetto,icone,barre degli strumenti,testo,evidenziazione,applicazioni,applicazioni KDE,tema,plasma,menu,menu globale +X-KDE-Keywords[ko]=style,styles,look,widget,icons,toolbars,text,highlight,apps,KDE applications,theme,plasma,menu,global menu,스타일,모양,위젯,아이콘,툴바,도구 모음,강조,프로그램,앱,KDE 프로그램,테마,전역 메뉴,메뉴 X-KDE-Keywords[nl]=stijl,stijlen,uiterlijk,widget,pictogrammen,werkbalk,tekst,accentuering,apps,KDE-toepassingen,thema,plasma, menu,globaal menu X-KDE-Keywords[pl]=style,styl,wygląd,element interfejsu,ikony,paski narzędzi,tekst,podświetlenie,programy,programy KDE,motyw,plazma,menu,globalne menu X-KDE-Keywords[pt]=estilo,estilos,aparência,elemento,item,ícones,barras de ferramentas,texto,realce,aplicações,aplicações do KDE,tema,plasma,menu global diff --git a/kcms/touchpad/src/kcm/kcm_touchpad.desktop b/kcms/touchpad/src/kcm/kcm_touchpad.desktop --- a/kcms/touchpad/src/kcm/kcm_touchpad.desktop +++ b/kcms/touchpad/src/kcm/kcm_touchpad.desktop @@ -7,7 +7,7 @@ X-KDE-ServiceTypes=KCModule,KCModuleInit X-KDE-Init-Symbol=touchpad -X-KDE-Init-Phase=0 +X-KDE-Init-Phase=1 X-KDE-Library=kded_touchpad X-KDE-PluginKeyword=kcm X-KDE-ParentApp=kcontrol diff --git a/kcms/workspaceoptions/mainpage.ui b/kcms/workspaceoptions/mainpage.ui --- a/kcms/workspaceoptions/mainpage.ui +++ b/kcms/workspaceoptions/mainpage.ui @@ -17,7 +17,7 @@ - Show Informational Tips + Display informational tooltips on mouse hover true @@ -40,7 +40,7 @@ - Visual feedback for status changes + Display visual feedback for status changes diff --git a/org.kde.plasmashell.metainfo.xml b/org.kde.plasmashell.metainfo.xml --- a/org.kde.plasmashell.metainfo.xml +++ b/org.kde.plasmashell.metainfo.xml @@ -42,31 +42,32 @@ KDE Plasma 桌面 KDE Plasma 桌面 KDE's complete desktop experience. Simple by default, powerful when needed - Experiència completa d'escriptori del KDE. Senzilla per omissió, potent quan cal - Experiència completa d'escriptori del KDE. Senzilla per omissió, potent quan cal + Experiència completa de l'escriptori KDE. Senzilla per omissió, potent quan cal + Experiència completa de l'escriptori KDE. Senzilla per omissió, potent quan cal KDE's komplette desktop-oplevelse. Simpelt som standard, kraftfuldt når det behøves + Das umfassende Arbeitsflächen-Erlebnis von KDE. Standardmäßig einfach, bei Bedarf leistungsstark Η πλήρης εμπειρία επιφάνειας εργασίας του KDE. Προκαθορισμένα απλή, πανίσχυρη όταν χρειάζεται. KDE's complete desktop experience. Simple by default, powerful when needed Experiencia completa de escritorio de KDE. Sencillo por omisión, potente cuando es necesario. KDE:n täydellinen työpöytäkokemus. Oletuksena yksinkertainen, tarvittaessa tehokas - L'expérience de bureau complète de KDE. Simple par défaut, puissant si nécessaire. + L'expérience de bureau complète de KDE. Simple par défaut, puissant si nécessaire Experiencia de escritorio completa de KDE. Simple de primeiras, potente cando cómpre. L'esperienza completa del desktop da KDE. Semplice nelle scelte predefinite, potente quando c'è bisogno - KDE 데스크톱 사용자 환경입니다. 첫 시작은 간단하게, 필요할 때에는 강력하게. + KDE 데스크톱 사용자 환경입니다. 첫 시작은 간단하게, 필요할 때에는 강력하게 De complete bureaubladervaring van KDE. Standaard eenvoudig, krachtig indien nodig Komplett KDE-oppleving. Enkel som standard og kraftig når nødvendig. Całkowite wrażenie pulpitu KDE. Prosty z natury, zaawansowany gdy potrzeba A experiência completa de trabalho do KDE. Simples por omissão, poderosa quando necessário A experiência da área de trabalho do KDE completa. Simples por padrão, potente quando necessário Kompletná pracovná plocha KDE. Predvolene jednoduchá, pri potrebe silná KDE-jeva popolna namizna izkušnja. Privzeto preprosta, a tudi zmogljiva, če je to zahtevano - КДЕ‑ово потпуно искуство површи. Подразумевано једноставно, моћно кад затреба. - KDE‑ovo potpuno iskustvo površi. Podrazumevano jednostavno, moćno kad zatreba. - КДЕ‑ово потпуно искуство површи. Подразумевано једноставно, моћно кад затреба. - KDE‑ovo potpuno iskustvo površi. Podrazumevano jednostavno, moćno kad zatreba. + КДЕ‑ово потпуно искуство површи, подразумевано једноставно а моћно кад затреба + KDE‑ovo potpuno iskustvo površi, podrazumevano jednostavno a moćno kad zatreba + КДЕ‑ово потпуно искуство површи, подразумевано једноставно а моћно кад затреба + KDE‑ovo potpuno iskustvo površi, podrazumevano jednostavno a moćno kad zatreba KDE:s fullständiga skrivbordsupplevelse. Normalt enkel, kraftfull vid behov KDE'nin tam masaüstü deneyimi. Özünde sade, gerektiğinde güçlü - Повноцінне стільничне середовище KDE. Типово просте, але потужне, якщо це потрібно. + Повноцінне стільничне середовище KDE. Типово просте, але потужне, якщо це потрібно xxKDE's complete desktop experience. Simple by default, powerful when neededxx KDE 的完整桌面体验。默认简单,需要时强大。 KDE 的完整桌面體驗。預設簡潔,需要時足夠強大 @@ -81,10 +82,15 @@ Plasma being used as a desktop Fa servir el Plasma com a escriptori Fa servir el Plasma com a escriptori + Plasma bruges som skrivebord + Plasma als eine Arbeitsfläche benutzt Χρήση Plasma ως επιφάνεια εργασίας + Plasma being used as a desktop Plasma usado como escritorio + Plasma on käytössä työpöytänä Plasma utilisé comme bureau Plasma usato come desktop + Plasma를 데스크톱으로 사용함 Plasma gebruikt wordend als een bureaublad Plazma wykorzystywana jako pulpit O Plasma em uso como ambiente de trabalho diff --git a/runners/kwin/plasma-runner-kwin.desktop b/runners/kwin/plasma-runner-kwin.desktop --- a/runners/kwin/plasma-runner-kwin.desktop +++ b/runners/kwin/plasma-runner-kwin.desktop @@ -72,7 +72,7 @@ Comment[sr@ijekavianlatin]=Pozabavite se Plasminim slagačem Comment[sr@latin]=Pozabavite se Plasminim slagačem Comment[sv]=Interaktion med Plasma sammansättning -Comment[tr]=Plazma Oluşturucusu Etkileş +Comment[tr]=Plasma Dizgicisi ile Etileşim Comment[uk]=Взаємодія із засобом композиції Плазми Comment[x-test]=xxInteract with the Plasma Compositorxx Comment[zh_CN]=和 Plasma 混成器进行交互 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