Commit bd95edf3 authored by Marco Martin's avatar Marco Martin
Browse files

QML mouse cursor KCM and components

Summary:
introduce a cursor theme kcm ported to QML, following the new
design guidelines, alongside generic components that will be reused for
modules that are mostly big grid views, such as look and feel.

BUG: 375106

Test Plan: tested on different devices with different dpi

Reviewers: #plasma, hein

Subscribers: hein, davidedmundson, aspotashev, januz, ngraham, romangg, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8692
parent 401aae2e
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcmmousetheme\")
add_definitions(-DTRANSLATION_DOMAIN=\"kcmcursortheme\")
include_directories( ${LIBUSB_INCLUDE_DIR} )
if(X11_Xcursor_FOUND)
set( libnoinst_SRCS
xcursor/themepage.cpp
xcursor/thememodel.cpp
xcursor/cursortheme.cpp
xcursor/xcursortheme.cpp
xcursor/previewwidget.cpp
xcursor/itemdelegate.cpp
xcursor/sortproxymodel.cpp
../krdb/krdb.cpp )
ki18n_wrap_ui( libnoinst_SRCS xcursor/themepage.ui )
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xcursor/ )
include_directories( ${KWINDOWSYSTEM_INCLUDE_DIR} )
else()
set( libnoinst_SRCS xcursor/themepage.cpp )
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xcursor/ )
set( libnoinst_SRCS core/themepage.cpp )
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/core/ )
endif()
set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml)
......@@ -32,6 +30,7 @@ add_library(kcm_cursortheme MODULE ${kcm_cursortheme_PART_SRCS})
target_link_libraries(kcm_cursortheme
Qt5::DBus
Qt5::X11Extras
Qt5::Quick
KF5::Archive
KF5::KCMUtils
KF5::I18n
......@@ -39,6 +38,7 @@ target_link_libraries(kcm_cursortheme
KF5::KIOWidgets
KF5::KDELibs4Support
KF5::NewStuff
KF5::QuickAddons
${X11_LIBRARIES}
XCB::XCB
)
......@@ -50,11 +50,14 @@ if (X11_Xfixes_FOUND)
target_link_libraries(kcm_cursortheme ${X11_Xfixes_LIB})
endif ()
install(TARGETS kcm_cursortheme DESTINATION ${PLUGIN_INSTALL_DIR} )
install(TARGETS kcm_cursortheme DESTINATION ${PLUGIN_INSTALL_DIR}/kcms )
kcoreaddons_desktop_to_json(kcm_cursortheme "kcm_cursortheme.desktop")
########### install files ###############
install( FILES cursortheme.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
install( FILES kcm_cursortheme.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
install( FILES xcursor/xcursor.knsrc DESTINATION ${CONFIG_INSTALL_DIR} )
kpackage_install_package(package kcm_cursortheme kcms)
#! /usr/bin/env bash
$EXTRACTRC `find -name \*.ui` >> rc.cpp || exit 11
$XGETTEXT *.cpp */*.cpp -o $podir/kcmmousetheme.pot
$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_cursortheme.pot
rm -f rc.cpp
[Desktop Entry]
Exec=kcmshell5 cursortheme
Exec=kcmshell5 kcm_cursortheme
Icon=edit-select
Type=Service
......@@ -36,19 +36,18 @@ Name[hu]=Kurzortéma
Name[ia]=Thema de cursor
Name[id]=Tema Kursor
Name[is]=Bendilþema
Name[it]=Tema dei puntatori
Name[it]=Tema dei cursori
Name[ja]=カーソルテーマ
Name[kk]=Меңзер нақышы
Name[km]=ស្បែក​ទស្សន៍ទ្រនិច
Name[kn]=ಸ್ಥಳಸೂಚಕ (ಕರ್ಸರ್) ಪರಿಸರವಿನ್ಯಾಸ
Name[ko]=커서 테마
Name[lt]=Žymeklių apipavidalinimas
Name[lt]=Žymeklių tema
Name[lv]=Kursora tēma
Name[mr]=कर्सर शैली
Name[nb]=Pekertema
Name[nds]=Wieserutsehn
Name[nl]=Cursorthema
Name[nn]=Peikartema
Name[pa]=ਕਰਸਰ ਥੀਮ
Name[pl]=Zestawy wskaźników
Name[pt]=Tema de Cursores
......@@ -75,33 +74,27 @@ Name[zh_TW]=游標主題
Comment=Mouse Cursor Theme
Comment[ar]=سمة مؤشّر الفأرة
Comment[bs]=Tema kursora miša
Comment[ca]=Tema de cursor del ratolí
Comment[ca@valencia]=Tema de cursor del ratolí
Comment[ca]=Tema de cursor de ratolí
Comment[cs]=Motiv kurzorů myši
Comment[da]=Markørtema
Comment[de]=Mauszeigerdesign
Comment[el]=Θέμα δρομέα ποντικιού
Comment[en_GB]=Mouse Cursor Theme
Comment[es]=Tema de cursores del ratón
Comment[et]=Hiirekursori teema
Comment[eu]=Sagu-kurtsoreen gaia
Comment[fi]=Osoitinteema
Comment[fr]=Thème du pointeur de la souris
Comment[gl]=Tema do cursor do rato
Comment[he]=ערכת־נושא למצביעי העכבר
Comment[gl]=Tema do cursor
Comment[hu]=Egérkurzor témája
Comment[id]=Tema Kursor Mouse
Comment[is]=Bendilþema fyrir mús
Comment[it]=Tema dei puntatori del mouse
Comment[ja]=マウスカーソルテーマ
Comment[ko]=마우스 커서 테마
Comment[lt]=Žymeklių apipavidalinimas
Comment[lt]=Žymeklių tema
Comment[mr]=माउस कर्सर शैली
Comment[nb]=Musepekertema
Comment[nds]=Utsehn vun den Muuswieser
Comment[nl]=Muiscursorthema
Comment[nn]=Peikartema
Comment[pa]=ਮਾਊਸ ਕਰਸਰ ਥੀਮ
Comment[pl]=Zestawy wskaźników myszy
Comment[pt]=Tema de Cursores do Rato
Comment[pt_BR]=Tema de cursores do mouse
......@@ -145,7 +138,6 @@ X-KDE-Keywords[mr]=माऊस, कर्सर, थीम, कर्सर,
X-KDE-Keywords[nb]=Mus,peker,tema,pekerutseende,pekerfarge,pekertema,musetema,musutseende,museskins,pekerfarger,pekeerutseende
X-KDE-Keywords[nds]=Muus,Wieser,Muster, Wieserutsehn,Klöör,Utsehn
X-KDE-Keywords[nl]=Muis,Cursor,Thema,Uiterlijk van cursor,kleur van cursor,Thema van cursor,Thema van muis,uiterlijk van muis,Muisoppervlak,Kleuren van aanwijzer,Uiterlijk van aanwijzer
X-KDE-Keywords[nn]=mus,peikar,tema,peikarutsjånad,peikarfarge,peikartema,musetema,musutsjånad,musedrakt,peikarfargar,peikarutsjånad
X-KDE-Keywords[pl]=Mysz,Kursor,Motyw,Wygląd kursora,Kolor kursora,Motyw kursora,Motyw myszy,Wygląd myszy,Skórki myszy,Kolory wskaźnika,Wygląd wskaźnika
X-KDE-Keywords[pt]=Rato,Cursor,Tema,Aparência do Cursor,Cor do Cursor,Tema do Cursor,Tema do Rato,Aparência do Rato,Visuais do Rato,Cores do Cursor
X-KDE-Keywords[pt_BR]=Mouse,Cursor,Tema,Aparência do cursor,Cor do cursor,Tema do cursor,Tema do mouse,Aparência do mouse,Visuais do mouse,Cores do ponteiro,Aparência do ponteiro
......
......@@ -16,34 +16,78 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <config-X11.h>
#include "kcmcursortheme.h"
#include "xcursor/thememodel.h"
#include "xcursor/sortproxymodel.h"
#include "xcursor/cursortheme.h"
#include "xcursor/previewwidget.h"
#include "../krdb/krdb.h"
#include <KAboutData>
#include <KPluginFactory>
#include <KLocalizedString>
#include <KConfig>
#include <KConfigGroup>
#include <KMessageBox>
#include <KUrlRequesterDialog>
#include <KIO/Job>
#include <KIO/DeleteJob>
#include <kio/netaccess.h>
#include <KTar>
#include <KGlobalSettings>
#include <QVBoxLayout>
#include <KNewStuff3/KNS3/DownloadDialog>
K_PLUGIN_FACTORY(CursorThemeConfigFactory,
registerPlugin<CursorThemeConfig>();
)
K_EXPORT_PLUGIN(CursorThemeConfigFactory("kcm_cursortheme", "kcminput"))
#include <QX11Info>
#include <QStandardItemModel>
#include <X11/Xlib.h>
#include <X11/Xcursor/Xcursor.h>
CursorThemeConfig::CursorThemeConfig(QWidget *parent, const QVariantList &args)
: KCModule(parent, args)
{
QLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
#include <klauncher_iface.h>
#ifdef HAVE_XFIXES
# include <X11/extensions/Xfixes.h>
#endif
K_PLUGIN_FACTORY_WITH_JSON(CursorThemeConfigFactory, "kcm_cursortheme.json", registerPlugin<CursorThemeConfig>();)
themepage = new ThemePage(this);
connect(themepage, SIGNAL(changed(bool)), SLOT(changed()));
layout->addWidget(themepage);
CursorThemeConfig::CursorThemeConfig(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, args),
m_appliedSize(0),
m_preferredSize(0),
m_selectedThemeRow(-1),
m_originalSelectedThemeRow(-1),
m_canInstall(true),
m_canResize(true),
m_canConfigure(true)
{
qmlRegisterType<PreviewWidget>("org.kde.private.kcm_cursortheme", 1, 0, "PreviewWidget");
qmlRegisterType<SortProxyModel>();
KAboutData* aboutData = new KAboutData(QStringLiteral("kcm_cursortheme"), i18n("Cursor Theme"),
QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(c) 2003-2007 Fredrik Höglund"));
QStringLiteral("1.0"), QString(i18n("Mouse Cursor Theme settings")), KAboutLicense::GPL, i18n("(c) 2003-2007 Fredrik Höglund"));
aboutData->addAuthor(i18n("Fredrik Höglund"));
aboutData->addAuthor(i18n("Marco Martin"));
setAboutData(aboutData);
m_model = new CursorThemeModel(this);
m_proxyModel = new SortProxyModel(this);
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setFilterCaseSensitivity(Qt::CaseSensitive);
m_proxyModel->sort(NameColumn, Qt::AscendingOrder);
m_sizesModel = new QStandardItemModel(this);
// Disable the install button if we can't install new themes to ~/.icons,
// or Xcursor isn't set up to look for cursor themes there.
if (!m_model->searchPaths().contains(QDir::homePath() + "/.icons") || !iconsIsWritable()) {
setCanInstall(false);
}
}
CursorThemeConfig::~CursorThemeConfig()
......@@ -51,22 +95,509 @@ CursorThemeConfig::~CursorThemeConfig()
/* */
}
void CursorThemeConfig::load()
void CursorThemeConfig::setCanInstall(bool can)
{
if (m_canInstall == can) {
return;
}
m_canInstall = can;
emit canInstallChanged();
}
bool CursorThemeConfig::canInstall() const
{
return m_canInstall;
}
void CursorThemeConfig::setCanResize(bool can)
{
if (m_canResize == can) {
return;
}
m_canResize = can;
emit canResizeChanged();
}
bool CursorThemeConfig::canResize() const
{
return m_canResize;
}
void CursorThemeConfig::setCanConfigure(bool can)
{
if (m_canConfigure == can) {
return;
}
m_canConfigure = can;
emit canConfigureChanged();
}
bool CursorThemeConfig::canConfigure() const
{
return m_canConfigure;
}
void CursorThemeConfig::setSelectedThemeRow(int row)
{
if (m_selectedThemeRow == row) {
return;
}
m_selectedThemeRow = row;
emit selectedThemeRowChanged();
updateSizeComboBox();
QModelIndex selected = selectedIndex();
if (selected.isValid()) {
const CursorTheme *theme = m_proxyModel->theme(selected);
}
setNeedsSave(m_originalSelectedThemeRow != m_selectedThemeRow || m_originalPreferredSize != m_preferredSize);
}
int CursorThemeConfig::selectedThemeRow() const
{
return m_selectedThemeRow;
}
void CursorThemeConfig::setPreferredSize(int size)
{
if (m_preferredSize == size) {
return;
}
m_preferredSize = size;
emit preferredSizeChanged();
setNeedsSave(m_originalSelectedThemeRow != m_selectedThemeRow || m_originalPreferredSize != m_preferredSize);
}
int CursorThemeConfig::preferredSize() const
{
themepage->load();
emit changed(false);
return m_preferredSize;
}
QAbstractItemModel *CursorThemeConfig::cursorsModel()
{
return m_proxyModel;
}
QAbstractItemModel *CursorThemeConfig::sizesModel()
{
return m_sizesModel;
}
bool CursorThemeConfig::iconsIsWritable() const
{
const QFileInfo icons = QFileInfo(QDir::homePath() + "/.icons");
const QFileInfo home = QFileInfo(QDir::homePath());
return ((icons.exists() && icons.isDir() && icons.isWritable()) ||
(!icons.exists() && home.isWritable()));
}
void CursorThemeConfig::updateSizeComboBox()
{
// clear the combo box
m_sizesModel->clear();
// refill the combo box and adopt its icon size
QModelIndex selected = selectedIndex();
int maxIconWidth = 0;
int maxIconHeight = 0;
if (selected.isValid()) {
const CursorTheme *theme = m_proxyModel->theme(selected);
const QList<int> sizes = theme->availableSizes();
QIcon m_icon;
// only refill the combobox if there is more that 1 size
if (sizes.size() > 1) {
int i;
QList<int> comboBoxList;
QPixmap m_pixmap;
// insert the items
m_pixmap = theme->createIcon(0);
if (m_pixmap.width() > maxIconWidth) {
maxIconWidth = m_pixmap.width();
}
if (m_pixmap.height() > maxIconHeight) {
maxIconHeight = m_pixmap.height();
}
QStandardItem *item = new QStandardItem(QIcon(m_pixmap),
i18nc("@item:inlistbox size", "Resolution dependent"));
item->setData(0);
m_sizesModel->appendRow(item);
comboBoxList << 0;
foreach (i, sizes) {
m_pixmap = theme->createIcon(i);
if (m_pixmap.width() > maxIconWidth) {
maxIconWidth = m_pixmap.width();
}
if (m_pixmap.height() > maxIconHeight) {
maxIconHeight = m_pixmap.height();
}
QStandardItem *item = new QStandardItem(QIcon(m_pixmap), QString::number(i));
item->setData(i);
m_sizesModel->appendRow(item);
comboBoxList << i;
};
// select an item
int selectItem = comboBoxList.indexOf(m_preferredSize);
// m_preferredSize not available for this theme
if (selectItem < 0) {
/* Search the value next to m_preferredSize. The first entry (0)
is ignored. (If m_preferredSize would have been 0, then we
would had found it yet. As m_preferredSize is not 0, we won't
default to "automatic size".)*/
int j;
int distance;
int smallestDistance;
selectItem = 1;
j = comboBoxList.value(selectItem);
smallestDistance = j < m_preferredSize ? m_preferredSize - j : j - m_preferredSize;
for (int i = 2; i < comboBoxList.size(); ++i) {
j = comboBoxList.value(i);
distance = j < m_preferredSize ? m_preferredSize - j : j - m_preferredSize;
if (distance < smallestDistance || (distance == smallestDistance && j > m_preferredSize)) {
smallestDistance = distance;
selectItem = i;
};
}
};
setPreferredSize(selectItem);
};
};
// enable or disable the combobox
KConfig c("kcminputrc");
KConfigGroup cg(&c, "Mouse");
if (cg.isEntryImmutable("cursorSize")) {
setCanResize(false);
} else {
setCanResize(m_sizesModel->rowCount() > 0);
};
}
bool CursorThemeConfig::applyTheme(const CursorTheme *theme, const int size)
{
// Require the Xcursor version that shipped with X11R6.9 or greater, since
// in previous versions the Xfixes code wasn't enabled due to a bug in the
// build system (freedesktop bug #975).
#if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
if (!theme) {
return false;
}
if (!CursorTheme::haveXfixes()) {
return false;
}
QByteArray themeName = QFile::encodeName(theme->name());
// Set up the proper launch environment for newly started apps
OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"),
QStringLiteral("/KLauncher"),
QDBusConnection::sessionBus());
klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), themeName);
// Update the Xcursor X resources
runRdb(0);
// Notify all applications that the cursor theme has changed
KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged);
// Reload the standard cursors
QStringList names;
// Qt cursors
names << "left_ptr" << "up_arrow" << "cross" << "wait"
<< "left_ptr_watch" << "ibeam" << "size_ver" << "size_hor"
<< "size_bdiag" << "size_fdiag" << "size_all" << "split_v"
<< "split_h" << "pointing_hand" << "openhand"
<< "closedhand" << "forbidden" << "whats_this" << "copy" << "move" << "link";
// X core cursors
names << "X_cursor" << "right_ptr" << "hand1"
<< "hand2" << "watch" << "xterm"
<< "crosshair" << "left_ptr_watch" << "center_ptr"
<< "sb_h_double_arrow" << "sb_v_double_arrow" << "fleur"
<< "top_left_corner" << "top_side" << "top_right_corner"
<< "right_side" << "bottom_right_corner" << "bottom_side"
<< "bottom_left_corner" << "left_side" << "question_arrow"
<< "pirate";
foreach (const QString &name, names) {
XFixesChangeCursorByName(QX11Info::display(), theme->loadCursor(name, size), QFile::encodeName(name));
}
return true;
#else
Q_UNUSED(theme)
return false;
#endif
}
void CursorThemeConfig::save()
{
themepage->save();
emit changed(false);
const CursorTheme *theme = selectedIndex().isValid() ? m_proxyModel->theme(selectedIndex()) : NULL;
KConfig config("kcminputrc");
KConfigGroup c(&config, "Mouse");
if (theme) {
c.writeEntry("cursorTheme", theme->name());
};
c.writeEntry("cursorSize", m_preferredSize);
c.sync();
if (!applyTheme(theme, m_preferredSize)) {
KMessageBox::information(0,
i18n("You have to restart KDE for these changes to take effect."),
i18n("Cursor Settings Changed"), "CursorSettingsChanged");
}
m_appliedIndex = selectedIndex();
m_appliedSize = m_preferredSize;
m_originalSelectedThemeRow = m_selectedThemeRow;
m_originalPreferredSize = m_preferredSize;
setNeedsSave(false);
}
void CursorThemeConfig::load()
{
// Get the name of the theme libXcursor currently uses
QString currentTheme;
if (QX11Info::isPlatformX11()) {
currentTheme = XcursorGetTheme(QX11Info::display());
}
// Get the name of the theme KDE is configured to use
KConfig c("kcminputrc");
KConfigGroup cg(&c, "Mouse");
currentTheme = cg.readEntry("cursorTheme", currentTheme);
// Find the theme in the listview
if (!currentTheme.isEmpty()) {
m_appliedIndex = m_proxyModel->findIndex(currentTheme);
} else {
m_appliedIndex = m_proxyModel->defaultIndex();
}
// Disable the listview and the buttons if we're in kiosk mode
if (cg.isEntryImmutable("cursorTheme")) {
setCanConfigure(false);
setCanInstall(false);
}
// Load cursor size
int size = cg.readEntry("cursorSize", 0);
if (size <= 0) {
m_preferredSize = 0;
} else {
m_preferredSize = size;
}
updateSizeComboBox(); // This handles also the kiosk mode
m_appliedSize = size;
const CursorTheme *theme = m_proxyModel->theme(m_appliedIndex);
setSelectedThemeRow(m_appliedIndex.row());
m_originalSelectedThemeRow = m_selectedThemeRow;
m_originalPreferredSize = m_preferredSize;
setNeedsSave(false);
}
void CursorThemeConfig::defaults()
{
themepage->defaults();
changed();
QModelIndex defaultIndex = m_proxyModel->findIndex("breeze_cursors");
setSelectedThemeRow(defaultIndex.row());
m_preferredSize = 0;
updateSizeComboBox();
setNeedsSave(m_originalSelectedThemeRow != m_selectedThemeRow || m_originalPreferredSize != m_preferredSize);
}
void CursorThemeConfig::selectionChanged()
{
updateSizeComboBox();
setNeedsSave(m_originalSelectedThemeRow != m_selectedThemeRow || m_originalPreferredSize != m_preferredSize);
//setNeedsSave(m_appliedIndex != selectedIndex());
}
QModelIndex CursorThemeConfig::selectedIndex() const
{
return m_proxyModel->index(m_selectedThemeRow, 0);
}
void CursorThemeConfig::getNewClicked()
{
KNS3::DownloadDialog dialog("xcursor.knsrc", 0);
if (dialog.exec()) {
KNS3::Entry::List list = dialog.changedEntries();
if (list.count() > 0) {
m_model->refreshList();
}
}
}
void CursorThemeConfig::installClicked()
{
// Get the URL for the theme we're going to install
QUrl url = KUrlRequesterDialog::getUrl(QUrl(), 0, i18n("Drag or Type Theme URL"));
if (url.isEmpty())
return;
QString tempFile;
if (!KIO::NetAccess::download(url, tempFile, 0)) {
QString text;