1 /**
2     KStyle for KDE4
3         SPDX-FileCopyrightText: 2004-2005 Maksim Orlovich <maksim@kde.org>
4         SPDX-FileCopyrightText: 2005, 2006 Sandro Giessl <giessl@kde.org>
5 
6     Based in part on the following software:
7 
8     KStyle for KDE3
9         SPDX-FileCopyrightText: 2001-2002 Karol Szwed <gallium@kde.org>
10     Portions:
11         SPDX-FileCopyrightText: 1998-2000 TrollTech AS
12 
13     Keramik for KDE3,
14         SPDX-FileCopyrightText: 2002 Malte Starostik <malte@kde.org>
15         SPDX-FileCopyrightText: 2002-2003 Maksim Orlovich<maksim@kde.org>
16     Portions:
17         SPDX-FileCopyrightText: 2001-2002 Karol Szwed <gallium@kde.org>
18         SPDX-FileCopyrightText: 2001-2002 Fredrik Höglund <fredrik@kde.org>
19         SPDX-FileCopyrightText: 2000 Daniel M. Duley <mosfet@kde.org>
20         SPDX-FileCopyrightText: 2000 Dirk Mueller <mueller@kde.org>
21         SPDX-FileCopyrightText: 2001 Martijn Klingens <klingens@kde.org>
22         SPDX-FileCopyrightText: 2003 Sandro Giessl <sandro@giessl.com>
23 
24     Many thanks to Bradley T. Hughes for the 3 button scrollbar code.
25 
26     SPDX-License-Identifier: LGPL-2.0-or-later
27 */
28 
29 #include "kstyle.h"
30 
31 #include <QAbstractItemView>
32 #include <QApplication>
33 #include <QDialogButtonBox>
34 #include <QEvent>
35 #include <QIcon>
36 #include <QPushButton>
37 #include <QShortcut>
38 #include <QStyleOption>
39 #include <QToolBar>
40 
41 #include <KColorScheme>
42 #include <KConfigGroup>
43 #include <KIconLoader>
44 #include <KMessageWidget>
45 
46 // ----------------------------------------------------------------------------
47 
48 static const QStyle::StyleHint SH_KCustomStyleElement = (QStyle::StyleHint)0xff000001;
49 static const int X_KdeBase = 0xff000000;
50 
51 class KStylePrivate
52 {
53 public:
54     KStylePrivate();
55 
56     QHash<QString, int> styleElements;
57     int hintCounter, controlCounter, subElementCounter;
58 };
59 
KStylePrivate()60 KStylePrivate::KStylePrivate()
61 {
62     controlCounter = subElementCounter = X_KdeBase;
63     hintCounter = X_KdeBase + 1; // sic! X_KdeBase is covered by SH_KCustomStyleElement
64 }
65 
66 /*
67     The functions called by widgets that request custom element support, passed to the effective style.
68     Collected in a static inline function due to similarity.
69 */
70 
customStyleElement(QStyle::StyleHint type,const QString & element,QWidget * widget)71 static inline int customStyleElement(QStyle::StyleHint type, const QString &element, QWidget *widget)
72 {
73     if (!widget || widget->style()->metaObject()->indexOfClassInfo("X-KDE-CustomElements") < 0) {
74         return 0;
75     }
76 
77     const QString originalName = widget->objectName();
78     widget->setObjectName(element);
79     const int id = widget->style()->styleHint(type, nullptr, widget);
80     widget->setObjectName(originalName);
81     return id;
82 }
83 
customStyleHint(const QString & element,const QWidget * widget)84 QStyle::StyleHint KStyle::customStyleHint(const QString &element, const QWidget *widget)
85 {
86     return (StyleHint)customStyleElement(SH_KCustomStyleElement, element, const_cast<QWidget *>(widget));
87 }
88 
customControlElement(const QString & element,const QWidget * widget)89 QStyle::ControlElement KStyle::customControlElement(const QString &element, const QWidget *widget)
90 {
91     return (ControlElement)customStyleElement(SH_KCustomStyleElement, element, const_cast<QWidget *>(widget));
92 }
93 
customSubElement(const QString & element,const QWidget * widget)94 QStyle::SubElement KStyle::customSubElement(const QString &element, const QWidget *widget)
95 {
96     return (SubElement)customStyleElement(SH_KCustomStyleElement, element, const_cast<QWidget *>(widget));
97 }
98 
KStyle()99 KStyle::KStyle()
100     : d(new KStylePrivate)
101 {
102 }
103 
~KStyle()104 KStyle::~KStyle()
105 {
106     delete d;
107 }
108 
109 /*
110     Custom Style Element runtime extension:
111     We reserve one StyleHint to let the effective style inform widgets whether it supports certain
112     string based style elements.
113     As this could lead to number conflicts (i.e. an app utilizing one of the hints itself for other
114     purposes) there're various safety mechanisms to rule out such interference.
115 
116     1) It's most unlikely that a widget in some 3rd party app will accidentally call a general
117     QStyle/KStyle styleHint() or draw*() and (unconditionally) expect a valid return, however:
118     a. The StyleHint is not directly above Qt's custom base, assuming most 3rd party apps would
119     - in case - make use of such
120     b. In order to be accepted, the StyleHint query must pass a widget with a perfectly matching
121     name, containing the typical element prefix ("CE_", etc.) and being supported by the current style
122     c. Instead using Qt's fragile qstyleoption_cast on the QStyleOption provided to the StyleHint
123     query, try to dump out a string and hope for the best, we now manipulate the widgets objectName().
124     Plain Qt dependent widgets can do that themselves and if a widget uses KStyle's convenience access
125     functions, it won't notice this at all
126 
127     2) The key problem is that a common KDE widget will run into an apps custom style which will then
128     falsely respond to the styleHint() call with an invalid value.
129     To prevent this, supporting styles *must* set a Q_CLASSINFO "X-KDE-CustomElements".
130 
131     3) If any of the above traps snaps, the returned id is 0 - the QStyle default, indicating
132     that this element is not supported by the current style.
133 
134     Obviously, this contains the "diminished clean" action to (temporarily) manipulate the
135     objectName() of a const QWidget* - but this happens completely inside KStyle or the widget, if
136     it does not make use of KStyles static convenience functions.
137     My biggest worry here would be, that in a multithreaded environment a thread (usually not being
138     owner of the widget) does something crucially relying on the widgets name property...
139     This however would also have to happen during the widget construction or stylechanges, when
140     the functions in doubt will typically be called.
141     So this is imho unlikely causing any trouble, ever.
142 */
143 
144 /*
145     The functions called by the real style implementation to add support for a certain element.
146     Checks for well-formed string (containing the element prefix) and returns 0 otherwise.
147     Checks whether the element is already supported or inserts it otherwise; Returns the proper id
148     NOTICE: We could check for "X-KDE-CustomElements", but this would bloat style start up times
149     (if they e.g. register 100 elements or so)
150 */
151 
newStyleElement(const QString & element,const char * check,int & counter,QHash<QString,int> * elements)152 static inline int newStyleElement(const QString &element, const char *check, int &counter, QHash<QString, int> *elements)
153 {
154     if (!element.contains(QLatin1String(check))) {
155         return 0;
156     }
157     int id = elements->value(element, 0);
158     if (!id) {
159         ++counter;
160         id = counter;
161         elements->insert(element, id);
162     }
163     return id;
164 }
165 
newStyleHint(const QString & element)166 QStyle::StyleHint KStyle::newStyleHint(const QString &element)
167 {
168     return (StyleHint)newStyleElement(element, "SH_", d->hintCounter, &d->styleElements);
169 }
170 
newControlElement(const QString & element)171 QStyle::ControlElement KStyle::newControlElement(const QString &element)
172 {
173     return (ControlElement)newStyleElement(element, "CE_", d->controlCounter, &d->styleElements);
174 }
175 
newSubElement(const QString & element)176 KStyle::SubElement KStyle::newSubElement(const QString &element)
177 {
178     return (SubElement)newStyleElement(element, "SE_", d->subElementCounter, &d->styleElements);
179 }
180 
polish(QWidget * w)181 void KStyle::polish(QWidget *w)
182 {
183     // Enable hover effects in all itemviews
184     if (QAbstractItemView *itemView = qobject_cast<QAbstractItemView *>(w)) {
185         itemView->viewport()->setAttribute(Qt::WA_Hover);
186     }
187 
188     if (QDialogButtonBox *box = qobject_cast<QDialogButtonBox *>(w)) {
189         QPushButton *button = box->button(QDialogButtonBox::Ok);
190 
191         if (button) {
192             auto shortcut = new QShortcut(Qt::CTRL | Qt::Key_Return, button);
193             QObject::connect(shortcut, &QShortcut::activated, button, &QPushButton::click);
194         }
195     }
196     if (auto messageWidget = qobject_cast<KMessageWidget *>(w)) {
197         KColorScheme scheme;
198         QColor color;
199         QPalette palette = messageWidget->palette();
200         switch (messageWidget->messageType()) {
201         case KMessageWidget::Positive:
202             color = scheme.foreground(KColorScheme::PositiveText).color();
203             break;
204         case KMessageWidget::Information:
205             color = scheme.foreground(KColorScheme::ActiveText).color();
206             break;
207         case KMessageWidget::Warning:
208             color = scheme.foreground(KColorScheme::NeutralText).color();
209             break;
210         case KMessageWidget::Error:
211             color = scheme.foreground(KColorScheme::NegativeText).color();
212             break;
213         }
214         palette.setColor(QPalette::Window, color);
215         messageWidget->setPalette(palette);
216     }
217     QCommonStyle::polish(w);
218 }
219 
standardPalette() const220 QPalette KStyle::standardPalette() const
221 {
222     return KColorScheme::createApplicationPalette(KSharedConfig::openConfig());
223 }
224 
standardIcon(StandardPixmap standardIcon,const QStyleOption * option,const QWidget * widget) const225 QIcon KStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const
226 {
227     switch (standardIcon) {
228     case QStyle::SP_DesktopIcon:
229         return QIcon::fromTheme(QStringLiteral("user-desktop"));
230     case QStyle::SP_TrashIcon:
231         return QIcon::fromTheme(QStringLiteral("user-trash"));
232     case QStyle::SP_ComputerIcon:
233         return QIcon::fromTheme(QStringLiteral("computer"));
234     case QStyle::SP_DriveFDIcon:
235         return QIcon::fromTheme(QStringLiteral("media-floppy"));
236     case QStyle::SP_DriveHDIcon:
237         return QIcon::fromTheme(QStringLiteral("drive-harddisk"));
238     case QStyle::SP_DriveCDIcon:
239     case QStyle::SP_DriveDVDIcon:
240         return QIcon::fromTheme(QStringLiteral("drive-optical"));
241     case QStyle::SP_DriveNetIcon:
242         return QIcon::fromTheme(QStringLiteral("folder-remote"));
243     case QStyle::SP_DirHomeIcon:
244         return QIcon::fromTheme(QStringLiteral("user-home"));
245     case QStyle::SP_DirOpenIcon:
246         return QIcon::fromTheme(QStringLiteral("document-open-folder"));
247     case QStyle::SP_DirClosedIcon:
248         return QIcon::fromTheme(QStringLiteral("folder"));
249     case QStyle::SP_DirIcon:
250         return QIcon::fromTheme(QStringLiteral("folder"));
251     case QStyle::SP_DirLinkIcon:
252         return QIcon::fromTheme(QStringLiteral("folder")); // TODO: generate (!?) folder with link emblem
253     case QStyle::SP_FileIcon:
254         return QIcon::fromTheme(QStringLiteral("text-plain")); // TODO: look for a better icon
255     case QStyle::SP_FileLinkIcon:
256         return QIcon::fromTheme(QStringLiteral("text-plain")); // TODO: generate (!?) file with link emblem
257     case QStyle::SP_FileDialogStart:
258         return QIcon::fromTheme(QStringLiteral("media-playback-start")); // TODO: find correct icon
259     case QStyle::SP_FileDialogEnd:
260         return QIcon::fromTheme(QStringLiteral("media-playback-stop")); // TODO: find correct icon
261     case QStyle::SP_FileDialogToParent:
262         return QIcon::fromTheme(QStringLiteral("go-up"));
263     case QStyle::SP_FileDialogNewFolder:
264         return QIcon::fromTheme(QStringLiteral("folder-new"));
265     case QStyle::SP_FileDialogDetailedView:
266         return QIcon::fromTheme(QStringLiteral("view-list-details"));
267     case QStyle::SP_FileDialogInfoView:
268         return QIcon::fromTheme(QStringLiteral("document-properties"));
269     case QStyle::SP_FileDialogContentsView:
270         return QIcon::fromTheme(QStringLiteral("view-list-icons"));
271     case QStyle::SP_FileDialogListView:
272         return QIcon::fromTheme(QStringLiteral("view-list-text"));
273     case QStyle::SP_FileDialogBack:
274         return QIcon::fromTheme(QStringLiteral("go-previous"));
275     case QStyle::SP_MessageBoxInformation:
276         return QIcon::fromTheme(QStringLiteral("dialog-information"));
277     case QStyle::SP_MessageBoxWarning:
278         return QIcon::fromTheme(QStringLiteral("dialog-warning"));
279     case QStyle::SP_MessageBoxCritical:
280         return QIcon::fromTheme(QStringLiteral("dialog-error"));
281     case QStyle::SP_MessageBoxQuestion:
282         // This used to be dialog-information for a long time, so keep it as a fallback
283         return QIcon::fromTheme(QStringLiteral("dialog-question"), QIcon::fromTheme(QStringLiteral("dialog-information")));
284     case QStyle::SP_DialogOkButton:
285         return QIcon::fromTheme(QStringLiteral("dialog-ok"));
286     case QStyle::SP_DialogCancelButton:
287         return QIcon::fromTheme(QStringLiteral("dialog-cancel"));
288     case QStyle::SP_DialogHelpButton:
289         return QIcon::fromTheme(QStringLiteral("help-contents"));
290     case QStyle::SP_DialogOpenButton:
291         return QIcon::fromTheme(QStringLiteral("document-open"));
292     case QStyle::SP_DialogSaveButton:
293         return QIcon::fromTheme(QStringLiteral("document-save"));
294     case QStyle::SP_DialogCloseButton:
295         return QIcon::fromTheme(QStringLiteral("dialog-close"));
296     case QStyle::SP_DialogApplyButton:
297         return QIcon::fromTheme(QStringLiteral("dialog-ok-apply"));
298     case QStyle::SP_DialogResetButton:
299         return QIcon::fromTheme(QStringLiteral("edit-undo"));
300     case QStyle::SP_DialogDiscardButton:
301         return QIcon::fromTheme(QStringLiteral("edit-delete"));
302     case QStyle::SP_DialogYesButton:
303         return QIcon::fromTheme(QStringLiteral("dialog-ok-apply"));
304     case QStyle::SP_DialogNoButton:
305         return QIcon::fromTheme(QStringLiteral("dialog-cancel"));
306     case QStyle::SP_ArrowUp:
307         return QIcon::fromTheme(QStringLiteral("go-up"));
308     case QStyle::SP_ArrowDown:
309         return QIcon::fromTheme(QStringLiteral("go-down"));
310     case QStyle::SP_ArrowLeft:
311         return QIcon::fromTheme(QStringLiteral("go-previous-view"));
312     case QStyle::SP_ArrowRight:
313         return QIcon::fromTheme(QStringLiteral("go-next-view"));
314     case QStyle::SP_ArrowBack:
315         return QIcon::fromTheme(QStringLiteral("go-previous"));
316     case QStyle::SP_ArrowForward:
317         return QIcon::fromTheme(QStringLiteral("go-next"));
318     case QStyle::SP_BrowserReload:
319         return QIcon::fromTheme(QStringLiteral("view-refresh"));
320     case QStyle::SP_BrowserStop:
321         return QIcon::fromTheme(QStringLiteral("process-stop"));
322     case QStyle::SP_MediaPlay:
323         return QIcon::fromTheme(QStringLiteral("media-playback-start"));
324     case QStyle::SP_MediaStop:
325         return QIcon::fromTheme(QStringLiteral("media-playback-stop"));
326     case QStyle::SP_MediaPause:
327         return QIcon::fromTheme(QStringLiteral("media-playback-pause"));
328     case QStyle::SP_MediaSkipForward:
329         return QIcon::fromTheme(QStringLiteral("media-skip-forward"));
330     case QStyle::SP_MediaSkipBackward:
331         return QIcon::fromTheme(QStringLiteral("media-skip-backward"));
332     case QStyle::SP_MediaSeekForward:
333         return QIcon::fromTheme(QStringLiteral("media-seek-forward"));
334     case QStyle::SP_MediaSeekBackward:
335         return QIcon::fromTheme(QStringLiteral("media-seek-backward"));
336     case QStyle::SP_MediaVolume:
337         return QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
338     case QStyle::SP_MediaVolumeMuted:
339         return QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
340     case SP_LineEditClearButton: {
341         const bool rtl = (option && option->direction == Qt::RightToLeft) || (!option && QApplication::isRightToLeft());
342 
343         const QString directionalThemeName = rtl ? QStringLiteral("edit-clear-locationbar-ltr") : QStringLiteral("edit-clear-locationbar-rtl");
344 
345         return QIcon::fromTheme(directionalThemeName, QIcon::fromTheme(QStringLiteral("edit-clear")));
346     }
347     case QStyle::SP_DialogYesToAllButton:
348         return QIcon::fromTheme(QStringLiteral("dialog-ok"));
349     case QStyle::SP_DialogNoToAllButton:
350         return QIcon::fromTheme(QStringLiteral("dialog-cancel"));
351     case QStyle::SP_DialogSaveAllButton:
352         return QIcon::fromTheme(QStringLiteral("document-save-all"));
353     case QStyle::SP_DialogAbortButton:
354         return QIcon::fromTheme(QStringLiteral("dialog-cancel"));
355     case QStyle::SP_DialogRetryButton:
356         return QIcon::fromTheme(QStringLiteral("view-refresh"));
357     case QStyle::SP_DialogIgnoreButton:
358         return QIcon::fromTheme(QStringLiteral("dialog-cancel"));
359     case QStyle::SP_RestoreDefaultsButton:
360         return QIcon::fromTheme(QStringLiteral("document-revert"));
361 
362     default:
363         break;
364     }
365 
366     return QCommonStyle::standardIcon(standardIcon, option, widget);
367 }
368 
styleHint(StyleHint hint,const QStyleOption * option,const QWidget * widget,QStyleHintReturn * returnData) const369 int KStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const
370 {
371     switch (hint) {
372     case SH_DialogButtonBox_ButtonsHaveIcons: {
373         // was KGlobalSettings::showIconsOnPushButtons() :
374         KConfigGroup g(KSharedConfig::openConfig(), "KDE");
375         return g.readEntry("ShowIconsOnPushButtons", true);
376     }
377 
378     case SH_ItemView_ArrowKeysNavigateIntoChildren:
379         return true;
380 
381     case SH_Widget_Animate: {
382         KConfigGroup g(KSharedConfig::openConfig(), "KDE-Global GUI Settings");
383         return g.readEntry("GraphicEffectsLevel", true);
384     }
385 
386     case QStyle::SH_Menu_SubMenuSloppyCloseTimeout:
387         return 300;
388 
389     case SH_ToolButtonStyle: {
390         KConfigGroup g(KSharedConfig::openConfig(), "Toolbar style");
391 
392         bool useOthertoolbars = false;
393         const QWidget *parent = widget ? widget->parentWidget() : nullptr;
394 
395         // If the widget parent is a QToolBar and the magic property is set
396         if (parent && qobject_cast<const QToolBar *>(parent)) {
397             if (parent->property("otherToolbar").isValid()) {
398                 useOthertoolbars = true;
399             }
400         }
401 
402         QString buttonStyle;
403         if (useOthertoolbars) {
404             buttonStyle = g.readEntry("ToolButtonStyleOtherToolbars", "NoText").toLower();
405         } else {
406             buttonStyle = g.readEntry("ToolButtonStyle", "TextBesideIcon").toLower();
407         }
408 
409         return buttonStyle == QLatin1String("textbesideicon") ? Qt::ToolButtonTextBesideIcon
410             : buttonStyle == QLatin1String("icontextright")   ? Qt::ToolButtonTextBesideIcon
411             : buttonStyle == QLatin1String("textundericon")   ? Qt::ToolButtonTextUnderIcon
412             : buttonStyle == QLatin1String("icontextbottom")  ? Qt::ToolButtonTextUnderIcon
413             : buttonStyle == QLatin1String("textonly")        ? Qt::ToolButtonTextOnly
414                                                               : Qt::ToolButtonIconOnly;
415     }
416 
417     case SH_KCustomStyleElement:
418         if (!widget) {
419             return 0;
420         }
421 
422         return d->styleElements.value(widget->objectName(), 0);
423 
424     case SH_ScrollBar_LeftClickAbsolutePosition: {
425         KConfigGroup g(KSharedConfig::openConfig(), "KDE");
426         return !g.readEntry("ScrollbarLeftClickNavigatesByPage", true);
427     }
428 
429     default:
430         break;
431     };
432 
433     return QCommonStyle::styleHint(hint, option, widget, returnData);
434 }
435 
pixelMetric(PixelMetric metric,const QStyleOption * option,const QWidget * widget) const436 int KStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
437 {
438     switch (metric) {
439     case PM_SmallIconSize:
440     case PM_ButtonIconSize:
441         return KIconLoader::global()->currentSize(KIconLoader::Small);
442 
443     case PM_ToolBarIconSize:
444         return KIconLoader::global()->currentSize(KIconLoader::Toolbar);
445 
446     case PM_LargeIconSize:
447         return KIconLoader::global()->currentSize(KIconLoader::Dialog);
448 
449     case PM_MessageBoxIconSize:
450         // TODO return KIconLoader::global()->currentSize(KIconLoader::MessageBox);
451         return KIconLoader::SizeHuge;
452     default:
453         break;
454     }
455 
456     return QCommonStyle::pixelMetric(metric, option, widget);
457 }
458