[kde-doc-english] [kde-workspace] kcontrol/input/xcursor: Make mouse coursor size configurable

Lukas Sommer sommerluk at gmail.com
Wed Nov 9 22:32:00 UTC 2011


Git commit 617b08f5f6652bb9d918abc963954723caca59d2 by Lukas Sommer.
Committed on 09/11/2011 at 23:27.
Pushed by sommer into branch 'master'.

Make mouse coursor size configurable

Enable systemsettings to change the size of the mouse cursor.
FEATURE: 90444
FIXED-IN: 4.8
REVIEW: 101701
GUI:
DIGEST:

M  +18   -6    kcontrol/input/xcursor/cursortheme.cpp
M  +22   -6    kcontrol/input/xcursor/cursortheme.h
M  +2    -2    kcontrol/input/xcursor/legacytheme.h
M  +6    -12   kcontrol/input/xcursor/previewwidget.cpp
M  +1    -1    kcontrol/input/xcursor/previewwidget.h
M  +166  -31   kcontrol/input/xcursor/themepage.cpp
M  +32   -3    kcontrol/input/xcursor/themepage.h
M  +33   -7    kcontrol/input/xcursor/themepage.ui
M  +68   -4    kcontrol/input/xcursor/xcursortheme.cpp
M  +6    -3    kcontrol/input/xcursor/xcursortheme.h

http://commits.kde.org/kde-workspace/617b08f5f6652bb9d918abc963954723caca59d2

diff --git a/kcontrol/input/xcursor/cursortheme.cpp b/kcontrol/input/xcursor/cursortheme.cpp
index 92abea5..2c8c260 100644
--- a/kcontrol/input/xcursor/cursortheme.cpp
+++ b/kcontrol/input/xcursor/cursortheme.cpp
@@ -110,24 +110,36 @@ QPixmap CursorTheme::createIcon() const
     int cursorSize = nominalCursorSize(iconSize);
     QSize size = QSize(iconSize, iconSize);
 
+    QPixmap pixmap = createIcon(cursorSize);
+
+    if (!pixmap.isNull())
+    {
+        // Scale the pixmap if it's larger than the preferred icon size
+        if (pixmap.width() > size.width() || pixmap.height() > size.height())
+            pixmap = pixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+    }
+
+    return pixmap;
+}
+
+
+QPixmap CursorTheme::createIcon(int size) const
+{
     QPixmap pixmap;
-    QImage image = loadImage(sample(), cursorSize);
+    QImage image = loadImage(sample(), size);
 
     if (image.isNull() && sample() != "left_ptr")
-        image = loadImage("left_ptr", cursorSize);
+        image = loadImage("left_ptr", size);
 
     if (!image.isNull())
     {
-        // Scale the image if it's larger than the preferred icon size
-        if (image.width() > size.width() || image.height() > size.height())
-            image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
-
         pixmap = QPixmap::fromImage(image);
     }
 
     return pixmap;
 }
 
+
 void CursorTheme::setCursorName(QCursor &cursor, const QString &name) const
 {
 #ifdef HAVE_XFIXES
diff --git a/kcontrol/input/xcursor/cursortheme.h b/kcontrol/input/xcursor/cursortheme.h
index 586ccba..672239b 100644
--- a/kcontrol/input/xcursor/cursortheme.h
+++ b/kcontrol/input/xcursor/cursortheme.h
@@ -62,6 +62,11 @@ class CursorTheme
         const QString sample() const       { return m_sample; }
         const QString name() const         { return m_name; }
         const QString path() const         { return m_path; }
+        /** @returns A list of the available sizes in this cursor theme,
+            @warning This list may be empty if the engine doesn't support
+            the recognition of the size. */
+        const QList<int> availableSizes() const
+                                           { return m_availableSizes; }
         bool isWritable() const            { return m_writable; }
         bool isHidden() const              { return m_hidden; }
         QPixmap icon() const;
@@ -72,16 +77,27 @@ class CursorTheme
         /// Loads the cursor image @p name, with the nominal size @p size.
         /// The image should be autocropped to the smallest possible size.
         /// If the theme doesn't have the cursor @p name, it should return a null image.
-        virtual QImage loadImage(const QString &name, int size = -1) const = 0;
+        virtual QImage loadImage(const QString &name, int size = 0) const = 0;
 
         /// Convenience function. Default implementation calls
         /// QPixmap::fromImage(loadImage());
-        virtual QPixmap loadPixmap(const QString &name, int size = -1) const;
+        virtual QPixmap loadPixmap(const QString &name, int size = 0) const;
 
         /// Loads the cursor @p name, with the nominal size @p size.
         /// If the theme doesn't have the cursor @p name, it should return
         /// the default cursor from the active theme instead.
-        virtual QCursor loadCursor(const QString &name, int size = -1) const = 0;	
+        virtual QCursor loadCursor(const QString &name, int size = 0) const = 0;
+
+        /** Creates the icon returned by @ref icon(). Don't use this function
+            directly but use @ref icon() instead, because @ref icon() caches
+            the icon.
+            @returns A pixmap with a cursor (usually left_ptr) that can
+            be used as icon for this theme. The size is adopted to
+            standard icon sizes.*/
+        virtual QPixmap createIcon() const;
+        /** @returns A pixmap with a cursor (usually left_ptr) that can
+            be used as icon for this theme. */
+        virtual QPixmap createIcon(int size) const;
 
     protected:
         void setTitle( const QString &title )      { m_title       = title; }
@@ -89,13 +105,12 @@ class CursorTheme
         void setSample( const QString &sample )    { m_sample      = sample; }
         inline void setName( const QString &name );
         void setPath( const QString &path )        { m_path        = path; }
+        void setAvailableSizes( const QList<int> &availableSizes )
+                                                   { m_availableSizes = availableSizes; }
         void setIcon( const QPixmap &icon )        { m_icon        = icon; }
         void setIsWritable( bool val )             { m_writable    = val; }
         void setIsHidden( bool val )               { m_hidden      = val; }
 
-        /// Creates the icon returned by @ref icon().
-        virtual QPixmap createIcon() const;
-
         /// Convenience function for cropping an image.
         QImage autoCropImage( const QImage &image ) const;
 
@@ -105,6 +120,7 @@ class CursorTheme
         QString m_title;
         QString m_description;
         QString m_path;
+        QList<int> m_availableSizes;
         QString m_sample;
         mutable QPixmap m_icon;
         bool m_writable:1;
diff --git a/kcontrol/input/xcursor/legacytheme.h b/kcontrol/input/xcursor/legacytheme.h
index 846bf9b..bd3c33d 100644
--- a/kcontrol/input/xcursor/legacytheme.h
+++ b/kcontrol/input/xcursor/legacytheme.h
@@ -41,8 +41,8 @@ class LegacyTheme : public CursorTheme
         LegacyTheme();
         virtual ~LegacyTheme();
 
-        QImage loadImage(const QString &name, int size = -1) const;
-        QCursor loadCursor(const QString &name, int size = - 1) const;
+        QImage loadImage(const QString &name, int size = 0) const;
+        QCursor loadCursor(const QString &name, int size = 0) const;
 
     protected:
         LegacyTheme(const QString &title, const QString &description = QString())
diff --git a/kcontrol/input/xcursor/previewwidget.cpp b/kcontrol/input/xcursor/previewwidget.cpp
index 3c264fc..aff149b 100644
--- a/kcontrol/input/xcursor/previewwidget.cpp
+++ b/kcontrol/input/xcursor/previewwidget.cpp
@@ -50,7 +50,6 @@ namespace {
     };
 
     const int numCursors      = 9;     // The number of cursors from the above list to be previewed
-    const int previewSize     = 24;    // The nominal cursor size to be used in the preview widget
     const int cursorSpacing   = 20;    // Spacing between preview cursors
     const int widgetMinWidth  = 10;    // The minimum width of the preview widget
     const int widgetMinHeight = 48;    // The minimum height of the preview widget
@@ -60,7 +59,7 @@ namespace {
 class PreviewCursor
 {
     public:
-        PreviewCursor( const CursorTheme *theme, const QString &name );
+        PreviewCursor( const CursorTheme *theme, const QString &name, int size );
         ~PreviewCursor() {}
 
         const QPixmap &pixmap() const { return m_pixmap; }
@@ -81,23 +80,18 @@ class PreviewCursor
 };
 
 
-PreviewCursor::PreviewCursor(const CursorTheme *theme, const QString &name)
+PreviewCursor::PreviewCursor(const CursorTheme *theme, const QString &name, int size)
 {
     // Create the preview pixmap
-    QImage image = theme->loadImage(name, previewSize);
+    QImage image = theme->loadImage(name, size);
 
     if (image.isNull())
         return;
 
-    int maxSize = previewSize * 2;
-    if (image.height() > maxSize || image.width() > maxSize)
-        image = image.scaled(maxSize, maxSize, Qt::KeepAspectRatio,
-                             Qt::SmoothTransformation);
-
     m_pixmap = QPixmap::fromImage(image);
 
     // Load the cursor
-    m_cursor = theme->loadCursor(name, previewSize);
+    m_cursor = theme->loadCursor(name, size);
     // ### perhaps we should tag the cursor so it doesn't get
     //     replaced when a new theme is applied
 }
@@ -168,7 +162,7 @@ void PreviewWidget::layoutItems()
 }
 
 
-void PreviewWidget::setTheme(const CursorTheme *theme)
+void PreviewWidget::setTheme(const CursorTheme *theme, const int size)
 {
     qDeleteAll(list);
     list.clear();
@@ -176,7 +170,7 @@ void PreviewWidget::setTheme(const CursorTheme *theme)
     if (theme)
     {
         for (int i = 0; i < numCursors; i++)
-            list << new PreviewCursor(theme, cursor_names[i]);
+            list << new PreviewCursor(theme, cursor_names[i], size);
 
         needLayout = true;
         updateGeometry();
diff --git a/kcontrol/input/xcursor/previewwidget.h b/kcontrol/input/xcursor/previewwidget.h
index f4d2c4e..4a11e2d 100644
--- a/kcontrol/input/xcursor/previewwidget.h
+++ b/kcontrol/input/xcursor/previewwidget.h
@@ -30,7 +30,7 @@ class PreviewWidget : public QWidget
         PreviewWidget(QWidget *parent);
         ~PreviewWidget();
 
-        void setTheme(const CursorTheme *theme);
+        void setTheme(const CursorTheme *theme, const int size);
         void setUseLables(bool);
         QSize sizeHint() const;
 
diff --git a/kcontrol/input/xcursor/themepage.cpp b/kcontrol/input/xcursor/themepage.cpp
index 6c9f29a..0f678ed 100644
--- a/kcontrol/input/xcursor/themepage.cpp
+++ b/kcontrol/input/xcursor/themepage.cpp
@@ -73,6 +73,7 @@ ThemePage::ThemePage(QWidget *parent)
     proxy->setFilterCaseSensitivity(Qt::CaseSensitive);
     proxy->sort(NameColumn, Qt::AscendingOrder);
 
+    // Get the icon size for QListView
     int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
 
     view->setModel(proxy);
@@ -85,6 +86,16 @@ ThemePage::ThemePage(QWidget *parent)
             SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
             SLOT(selectionChanged()));
 
+    // Make sure we find out about size changes
+    connect(sizeComboBox,
+            SIGNAL(currentIndexChanged(int)),
+            SLOT(sizeChanged()));
+
+    // Make sure we find out about user activity
+    connect(sizeComboBox,
+            SIGNAL(activated(int)),
+            SLOT(preferredSizeChanged()));
+
     // Disable the install button if we can't install new themes to ~/.icons,
     // or Xcursor isn't set up to look for cursor themes there.
     if (!model->searchPaths().contains(QDir::homePath() + "/.icons") || !iconsIsWritable()) {
@@ -113,6 +124,114 @@ bool ThemePage::iconsIsWritable() const
 }
 
 
+void ThemePage::updateSizeComboBox()
+{
+    // clear the combo box
+    sizeComboBox->clear();
+
+    // refill the combo box and adopt its icon size
+    QModelIndex selected = selectedIndex();
+    int maxIconWidth = 0;
+    int maxIconHeight = 0;
+    if (selected.isValid())
+    {
+        const CursorTheme *theme = proxy->theme(selected);
+        const QList<int> sizes = theme->availableSizes();
+        QIcon m_icon;
+        if (sizes.size() > 1)  // only refill the combobox if there is more that 1 size
+        {
+            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();
+            sizeComboBox->addItem(
+                QIcon(m_pixmap),
+                i18nc("@item:inlistbox size", "resolution dependent"),
+                0);
+            comboBoxList << 0;
+            foreach (i, sizes)
+            {
+                m_pixmap = theme->createIcon(i);
+                if (m_pixmap.width() > maxIconWidth)
+                    maxIconWidth = m_pixmap.width();
+                if (m_pixmap.height() > maxIconHeight)
+                    maxIconHeight = m_pixmap.height();
+                sizeComboBox->addItem(QIcon(m_pixmap), QString::number(i), i);
+                comboBoxList << i;
+            };
+
+            // select an item
+            int selectItem = comboBoxList.indexOf(preferredSize);
+            if (selectItem < 0)  // preferredSize not available for this theme
+            {
+                /* Search the value next to preferredSize. The first entry (0)
+                   is ignored. (If preferredSize would have been 0, then we
+                   would had found it yet. As preferredSize is not 0, we won't
+                   default to "automatic size".)*/
+                int j;
+                int distance;
+                int smallestDistance;
+                selectItem = 1;
+                j = comboBoxList.value(selectItem);
+                smallestDistance = j < preferredSize ? preferredSize - j : j - preferredSize;
+                for (int i = 2; i < comboBoxList.size(); ++i)
+                {
+                    j = comboBoxList.value(i);
+                    distance = j < preferredSize ? preferredSize - j : j - preferredSize;
+                    if (distance < smallestDistance || (distance == smallestDistance && j > preferredSize))
+                    {
+                        smallestDistance = distance;
+                        selectItem = i;
+                    };
+                }
+            };
+            sizeComboBox->setCurrentIndex(selectItem);
+        };
+    };
+    sizeComboBox->setIconSize(QSize(maxIconWidth, maxIconHeight));
+
+    // enable or disable the combobox
+    KConfig c("kcminputrc");
+    KConfigGroup cg(&c, "Mouse");
+    if (cg.isEntryImmutable("cursorSize")) {
+        sizeComboBox->setEnabled(false);
+        sizeLabel->setEnabled(false);
+    } else {
+        sizeComboBox->setEnabled(sizeComboBox->count() > 0);
+        sizeLabel->setEnabled(sizeComboBox->count() > 0);
+    };
+}
+
+
+int ThemePage::selectedSize() const
+{
+  if (sizeComboBox->isEnabled() && sizeComboBox->currentIndex() >= 0)
+      return sizeComboBox->itemData(sizeComboBox->currentIndex(), Qt::UserRole).toInt();
+  return 0;
+}
+
+
+void ThemePage::updatePreview()
+{
+    QModelIndex selected = selectedIndex();
+
+    if (selected.isValid()) {
+        const CursorTheme *theme = proxy->theme(selected);
+        preview->setTheme(theme, selectedSize());
+        removeButton->setEnabled(theme->isWritable());
+    } else {
+        preview->setTheme(NULL, 0);
+        removeButton->setEnabled(false);
+    };
+}
+
+
 bool ThemePage::haveXfixes()
 {
     bool result = false;
@@ -131,12 +250,15 @@ bool ThemePage::haveXfixes()
 }
 
 
-bool ThemePage::applyTheme(const CursorTheme *theme)
+bool ThemePage::applyTheme(const CursorTheme *theme, const int size)
 {
     // Require the Xcursor version that shipped with X11R6.9 or greater, since
     // in previous versions the Xfixes code wasn't enabled due to a bug in the
     // build system (freedesktop bug #975).
 #if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
+    if (!theme)
+        return false;
+
     if (!haveXfixes())
         return false;
 
@@ -173,7 +295,7 @@ bool ThemePage::applyTheme(const CursorTheme *theme)
 
     foreach (const QString &name, names)
     {
-        QCursor cursor = theme->loadCursor(name);
+        QCursor cursor = theme->loadCursor(name, size);
         XFixesChangeCursorByName(x11Info().display(), cursor.handle(), QFile::encodeName(name));
     }
 
@@ -187,17 +309,20 @@ bool ThemePage::applyTheme(const CursorTheme *theme)
 
 void ThemePage::save()
 {
-    if (appliedIndex == selectedIndex() || !selectedIndex().isValid())
-        return;
-
-    const CursorTheme *theme = proxy->theme(selectedIndex());
+    const CursorTheme *theme = selectedIndex().isValid() ? proxy->theme(selectedIndex()) : NULL;
+    const int size = selectedSize();
 
     KConfig config("kcminputrc");
     KConfigGroup c(&config, "Mouse");
-    c.writeEntry("cursorTheme", theme->name());
+    if (theme)
+    {
+        c.writeEntry("cursorTheme", theme->name());
+    };
+    c.writeEntry("cursorSize", size);
+    preferredSize = size;
     c.sync();
 
-    if (!applyTheme(theme))
+    if (!applyTheme(theme, size))
     {
         KMessageBox::information(this,
                                  i18n("You have to restart KDE for these changes to take effect."),
@@ -205,6 +330,7 @@ void ThemePage::save()
     }
 
     appliedIndex = selectedIndex();
+    appliedSize = size;
 }
 
 
@@ -233,16 +359,26 @@ void ThemePage::load()
         removeButton->setEnabled(false);
     }
 
+    // Load cursor size
+    int size = cg.readEntry("cursorSize", 0);
+    if (size <= 0)
+        preferredSize = 0;
+    else
+        preferredSize = size;
+    updateSizeComboBox(); // This handles also the kiosk mode
+
+    appliedSize = size;
+
     const CursorTheme *theme = proxy->theme(appliedIndex);
 
     if (appliedIndex.isValid())
     {
         // Select the current theme
-        selectRow(appliedIndex);
+        view->setCurrentIndex(appliedIndex);
         view->scrollTo(appliedIndex, QListView::PositionAtCenter);
 
         // Update the preview widget as well
-        preview->setTheme(theme);
+        preview->setTheme(theme, size);
     }
 
     if (!theme || !theme->isWritable())
@@ -255,33 +391,17 @@ void ThemePage::defaults()
     view->selectionModel()->clear();
     QModelIndex defaultIndex = proxy->findIndex("Oxygen_Black");
     view->setCurrentIndex(defaultIndex);
-}
-
-
-void ThemePage::selectRow(int row) const
-{
-    // Create a selection that stretches across all columns
-    QModelIndex from = proxy->index(row, 0);
-    QModelIndex to   = proxy->index(row, model->columnCount() - 1);
-    QItemSelection selection(from, to);
-
-    view->selectionModel()->select(selection, QItemSelectionModel::Select);
+    preferredSize = 0;
+    updateSizeComboBox();
 }
 
 
 void ThemePage::selectionChanged()
 {
-    QModelIndex selected = selectedIndex();
+    updateSizeComboBox();
+    updatePreview();
 
-    if (selected.isValid())
-    {
-        const CursorTheme *theme = proxy->theme(selected);
-        preview->setTheme(theme);
-        removeButton->setEnabled(theme->isWritable());
-    } else
-        preview->setTheme(NULL);
-
-    emit changed(appliedIndex != selected);
+    emit changed(appliedIndex != selectedIndex());
 }
 
 QModelIndex ThemePage::selectedIndex() const
@@ -293,6 +413,21 @@ QModelIndex ThemePage::selectedIndex() const
     return QModelIndex();
 }
 
+void ThemePage::sizeChanged()
+{
+    updatePreview();
+    emit changed(selectedSize() != appliedSize);
+}
+
+void ThemePage::preferredSizeChanged()
+{
+    int index = sizeComboBox->currentIndex();
+    if (index >= 0)
+        preferredSize = sizeComboBox->itemData(index, Qt::UserRole).toInt();
+    else
+        preferredSize = 0;
+}
+
 void ThemePage::getNewClicked()
 {
     KNS3::DownloadDialog dialog("xcursor.knsrc", this);
diff --git a/kcontrol/input/xcursor/themepage.h b/kcontrol/input/xcursor/themepage.h
index 38ca893..3b21dd9 100644
--- a/kcontrol/input/xcursor/themepage.h
+++ b/kcontrol/input/xcursor/themepage.h
@@ -48,21 +48,50 @@ class ThemePage : public QWidget, private Ui::ThemePage
 
     private slots:
         void selectionChanged();
+        /** Updates the preview. If the size has changed, it also emits changed() */
+        void sizeChanged();
+        /** Sets #preferredSize to the item that is currently selected in sizeComboBox.
+            If none is selected, it is set to 0. */
+        void preferredSizeChanged();
+        /** Updates the size combo box. It loads the size list of the selected cursor
+            theme with the corresponding icons and chooses an appropriate entry. It
+            enables the combo box and the label if the theme provides more than one
+            size, otherwise it disables it. If the size setting is looked in kiosk
+            mode, it stays always disabled. */
+        void updateSizeComboBox();
         void getNewClicked();
         void installClicked();
         void removeClicked();
 
     private:
-        void selectRow(int) const;
-        void selectRow(const QModelIndex &index) const { selectRow(index.row()); }
+        /** @returns 0 if in the UI "automatic size" is selected, otherwise
+                     returns the custom size. */
+        int selectedSize() const;
+        /** Holds the last size that was choosen by the user. Example: The user chooses
+            theme1 which provides the sizes 24 and 36. He chooses 36. preferredSize gets
+            set to 36. Now, he switchs to theme2 which provides the sizes 30 and 40.
+            preferredSize still is 36, so the UI will default to 40, which is next to 36.
+            Now, he chooses theme3 which provides the sizes 34 and 44. preferredSize is
+            still 36, so the UI defaults to 34. Now the user changes manually to 44. This
+            will also change preferredSize. */
+        int preferredSize;
+        void updatePreview();
         QModelIndex selectedIndex() const;
         bool installThemes(const QString &file);
-        bool applyTheme(const CursorTheme *theme);
+        /** Applies a given theme, using XFixes, XCursor and KGlobalSettings.
+            @param theme The cursor theme to be applied. It is save to pass 0 here
+              (will result in \e false as return value).
+            @param size The size hint that is used to select the cursor size.
+            @returns If the changes could be applied. Will return \e false if \e theme is
+               0 or if the XFixes and XCursor libraries aren't available in the required
+               version, otherwise returns \e true. */
+        bool applyTheme(const CursorTheme *theme, const int size);
         bool iconsIsWritable() const;
 
         CursorThemeModel *model;
         SortProxyModel *proxy;
 
+        int appliedSize;
         // This index refers to the CursorThemeModel, not the proxy or the view
         QPersistentModelIndex appliedIndex;
 };
diff --git a/kcontrol/input/xcursor/themepage.ui b/kcontrol/input/xcursor/themepage.ui
index 2e38054..be73663 100644
--- a/kcontrol/input/xcursor/themepage.ui
+++ b/kcontrol/input/xcursor/themepage.ui
@@ -6,19 +6,19 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>542</width>
+    <width>541</width>
     <height>360</height>
    </rect>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0" colspan="4">
+   <item row="0" column="0" colspan="2">
     <widget class="QLabel" name="label">
      <property name="text">
       <string>Select the cursor theme you want to use (hover preview to test cursor):</string>
      </property>
     </widget>
    </item>
-   <item row="1" column="0" colspan="4">
+   <item row="1" column="0" rowspan="3" colspan="2">
     <widget class="PreviewWidget" name="preview" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -34,7 +34,7 @@
      </property>
     </widget>
    </item>
-   <item row="2" column="0" rowspan="5" colspan="3">
+   <item row="4" column="0" rowspan="5" colspan="2">
     <widget class="QListView" name="view">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@@ -50,7 +50,7 @@
      </property>
     </widget>
    </item>
-   <item row="2" column="3">
+   <item row="4" column="2">
     <widget class="KPushButton" name="installKnsButton">
      <property name="toolTip">
       <string>Get new color schemes from the Internet</string>
@@ -60,20 +60,46 @@
      </property>
     </widget>
    </item>
-   <item row="3" column="3">
+   <item row="5" column="2">
     <widget class="KPushButton" name="installButton">
      <property name="text">
       <string>Install From File...</string>
      </property>
     </widget>
    </item>
-   <item row="4" column="3">
+   <item row="6" column="2">
     <widget class="KPushButton" name="removeButton">
      <property name="text">
       <string>Remove Theme</string>
      </property>
     </widget>
    </item>
+   <item row="2" column="2">
+    <widget class="QComboBox" name="sizeComboBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="sizeAdjustPolicy">
+      <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <widget class="QLabel" name="sizeLabel">
+     <property name="text">
+      <string comment="@label:listbox cursor size">Size:</string>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <customwidgets>
diff --git a/kcontrol/input/xcursor/xcursortheme.cpp b/kcontrol/input/xcursor/xcursortheme.cpp
index 2ecb9ba..2fc7504 100644
--- a/kcontrol/input/xcursor/xcursortheme.cpp
+++ b/kcontrol/input/xcursor/xcursortheme.cpp
@@ -45,6 +45,39 @@ XCursorTheme::XCursorTheme(const QDir &themeDir)
 
     if (themeDir.exists("index.theme"))
         parseIndexFile();
+
+    QString cursorFile = path() + "/cursors/left_ptr";
+    QList<int> sizeList;
+    XcursorImages *images = XcursorFilenameLoadAllImages(qPrintable(cursorFile));
+    if (images)
+    {
+        for (int i = 0; i < images->nimage; ++i)
+        {
+            if (!sizeList.contains(images->images[i]->size))
+                sizeList.append(images->images[i]->size);
+        };
+        XcursorImagesDestroy(images);
+        qSort(sizeList.begin(), sizeList.end());
+        m_availableSizes = sizeList;
+    };
+    if (!sizeList.isEmpty())
+    {
+        QString sizeListString = QString::number(sizeList.takeFirst());
+        while (!sizeList.isEmpty())
+        {
+            sizeListString.append(", ");
+            sizeListString.append(QString::number(sizeList.takeFirst()));
+        };
+        QString tempString = i18nc(
+            "@info/plain The argument is the list of available sizes (in pixel). Example: "
+                "'Available sizes: 24' or 'Available sizes: 24, 36, 48'",
+            "(Available sizes: %1)",
+            sizeListString);
+        if (m_description.isEmpty())
+          m_description = tempString;
+        else
+          m_description = m_description + ' ' + tempString;
+    };
 }
 
 
@@ -116,10 +149,41 @@ XcursorImages *XCursorTheme::xcLoadImages(const QString &image, int size) const
 }
 
 
+int XCursorTheme::autodetectCursorSize() const
+{
+    /* This code is basically borrowed from display.c of the XCursor library
+       We can't use "int XcursorGetDefaultSize(Display *dpy)" because if
+       previously the cursor size was set to a custom value, it would return
+       this custom value. */
+    int size = 0;
+    int dpi = 0;
+    Display *dpy = QX11Info::display();
+    // The string "v" is owned and will be destroyed by Xlib
+    char *v = XGetDefault(dpy, "Xft", "dpi");
+    if (v)
+        dpi = atoi(v);
+    if (dpi)
+        size = dpi * 16 / 72;
+    if (size == 0)
+    {
+        int dim;
+        if (DisplayHeight(dpy, DefaultScreen(dpy)) <
+            DisplayWidth(dpy, DefaultScreen(dpy)))
+        {
+            dim = DisplayHeight(dpy, DefaultScreen(dpy));
+        } else {
+            dim = DisplayWidth(dpy, DefaultScreen(dpy));
+        };
+        size = dim / 48;
+    }
+    return size;
+}
+
+
 QCursor XCursorTheme::loadCursor(const QString &name, int size) const
 {
-    if (size == -1)
-        size = XcursorGetDefaultSize(QX11Info::display());
+    if (size <= 0)
+        size = autodetectCursorSize();
 
     // Load the cursor images
     XcursorImages *images = xcLoadImages(name, size);
@@ -143,8 +207,8 @@ QCursor XCursorTheme::loadCursor(const QString &name, int size) const
 
 QImage XCursorTheme::loadImage(const QString &name, int size) const
 {
-    if (size == -1)
-        size = XcursorGetDefaultSize(QX11Info::display());
+    if (size <= 0)
+        size = autodetectCursorSize();
 
     // Load the image
     XcursorImage *xcimage = xcLoadImage(name, size);
diff --git a/kcontrol/input/xcursor/xcursortheme.h b/kcontrol/input/xcursor/xcursortheme.h
index b474086..b4b6c53 100644
--- a/kcontrol/input/xcursor/xcursortheme.h
+++ b/kcontrol/input/xcursor/xcursortheme.h
@@ -45,9 +45,9 @@ class XCursorTheme : public LegacyTheme
         XCursorTheme(const QDir &dir);
         virtual ~XCursorTheme() {}
 
-        const QStringList inherits() const { return m_inherits; }	
-        QImage loadImage(const QString &name, int size = -1) const;
-        QCursor loadCursor(const QString &name, int size = -1) const;
+        const QStringList inherits() const { return m_inherits; }
+        QImage loadImage(const QString &name, int size = 0) const;
+        QCursor loadCursor(const QString &name, int size = 0) const;
 
     protected:
         XCursorTheme(const QString &title, const QString &desc)
@@ -59,6 +59,9 @@ class XCursorTheme : public LegacyTheme
         XcursorImages *xcLoadImages(const QString &name, int size) const;
         void parseIndexFile();
         QString findAlternative(const QString &name) const;
+        /** Returns the size that the XCursor library would use if no
+            cursor size is given. This depends mainly on Xft.dpi. */
+        int autodetectCursorSize() const;
 
         QStringList m_inherits;
         static QHash<QString, QString> alternatives;


More information about the kde-doc-english mailing list