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