1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4//
5// W A R N I N G
6// -------------
7//
8// This file is not part of the Qt API. It exists purely as an
9// implementation detail. This header file may change from version to
10// version without notice, or even be removed.
11//
12// We mean it.
13//
14
15
16#include "qgtk3interface_p.h"
17#include "qgtk3storage_p.h"
18#include <QtCore/QMetaEnum>
19#include <QtCore/QFileInfo>
20#include <QtGui/QFontDatabase>
21
22QT_BEGIN_NAMESPACE
23Q_LOGGING_CATEGORY(lcQGtk3Interface, "qt.qpa.gtk");
24
25
26// Callback for gnome event loop has to be static
27static QGtk3Storage *m_storage = nullptr;
28
29QGtk3Interface::QGtk3Interface(QGtk3Storage *s)
30{
31 initColorMap();
32
33 if (!s) {
34 qCDebug(lcQGtk3Interface) << "QGtk3Interface instantiated without QGtk3Storage."
35 << "No reaction to runtime theme changes.";
36 return;
37 }
38
39 // Connect to the GTK settings changed signal
40 auto handleThemeChange = [] {
41 if (m_storage)
42 m_storage->handleThemeChange();
43 };
44
45 GtkSettings *settings = gtk_settings_get_default();
46 const gboolean success = g_signal_connect(settings, "notify::gtk-theme-name",
47 G_CALLBACK(handleThemeChange), nullptr);
48 if (success == FALSE) {
49 qCDebug(lcQGtk3Interface) << "Connection to theme change signal failed."
50 << "No reaction to runtime theme changes.";
51 } else {
52 m_storage = s;
53 }
54}
55
56QGtk3Interface::~QGtk3Interface()
57{
58 // Ignore theme changes when destructor is reached
59 m_storage = nullptr;
60
61 // QGtkWidgets have to be destroyed manually
62 for (auto v : cache)
63 gtk_widget_destroy(widget: v.second);
64}
65
66/*!
67 \internal
68 \brief Converts a string into the GtkStateFlags enum.
69
70 Converts a string formatted GTK color \param state into an enum value.
71 Returns an integer corresponding to GtkStateFlags.
72 Returns -1 if \param state does not correspond to a valid enum key.
73 */
74int QGtk3Interface::toGtkState(const QString &state)
75{
76#define CASE(x) \
77 if (QLatin1String(QByteArray(state.toLatin1())) == #x ##_L1) \
78 return GTK_STATE_FLAG_ ##x
79
80#define CONVERT\
81 CASE(NORMAL);\
82 CASE(ACTIVE);\
83 CASE(PRELIGHT);\
84 CASE(SELECTED);\
85 CASE(INSENSITIVE);\
86 CASE(INCONSISTENT);\
87 CASE(FOCUSED);\
88 CASE(BACKDROP);\
89 CASE(DIR_LTR);\
90 CASE(DIR_RTL);\
91 CASE(LINK);\
92 CASE(VISITED);\
93 CASE(CHECKED);\
94 CASE(DROP_ACTIVE)
95
96 CONVERT;
97 return -1;
98#undef CASE
99}
100
101/*!
102 \internal
103 \brief Returns \param state converted into a string.
104 */
105const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state)
106{
107#define CASE(x) case GTK_STATE_FLAG_ ##x: return QLatin1String(#x)
108 switch (state) {
109 CONVERT;
110 }
111 Q_UNREACHABLE();
112#undef CASE
113#undef CONVERT
114}
115
116/*!
117 \internal
118 \brief Populates the internal map used to find a GTK color's source and fallback generic color.
119 */
120void QGtk3Interface::initColorMap()
121{
122 #define SAVE(src, state, prop, def)\
123 {ColorKey({QGtkColorSource::src, GTK_STATE_FLAG_ ##state}), ColorValue({#prop ##_L1, QGtkColorDefault::def})}
124
125 gtkColorMap = ColorMap {
126 SAVE(Foreground, NORMAL, theme_fg_color, Foreground),
127 SAVE(Foreground, BACKDROP, theme_unfocused_selected_fg_color, Foreground),
128 SAVE(Foreground, INSENSITIVE, insensitive_fg_color, Foreground),
129 SAVE(Foreground, SELECTED, theme_selected_fg_color, Foreground),
130 SAVE(Foreground, ACTIVE, theme_unfocused_fg_color, Foreground),
131 SAVE(Text, NORMAL, theme_text_color, Foreground),
132 SAVE(Text, ACTIVE, theme_unfocused_text_color, Foreground),
133 SAVE(Base, NORMAL, theme_base_color, Background),
134 SAVE(Base, INSENSITIVE, insensitive_base_color, Background),
135 SAVE(Background, NORMAL, theme_bg_color, Background),
136 SAVE(Background, SELECTED, theme_selected_bg_color, Background),
137 SAVE(Background, INSENSITIVE, insensitive_bg_color, Background),
138 SAVE(Background, ACTIVE, theme_unfocused_bg_color, Background),
139 SAVE(Background, BACKDROP, theme_unfocused_selected_bg_color, Background),
140 SAVE(Border, NORMAL, borders, Border),
141 SAVE(Border, ACTIVE, unfocused_borders, Border)
142 };
143#undef SAVE
144
145 qCDebug(lcQGtk3Interface) << "Color map populated from defaults.";
146}
147
148/*!
149 \internal
150 \brief Returns a QImage corresponding to \param standardPixmap.
151
152 A QImage (not a QPixmap) is returned so it can be cached and re-scaled in case the pixmap is
153 requested multiple times with different resolutions.
154
155 \note Rather than defaulting to a QImage(), all QPlatformTheme::StandardPixmap enum values have
156 been mentioned explicitly.
157 That way they can be covered more easily in case additional icons are provided by GTK.
158 */
159QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const
160{
161 switch (standardPixmap) {
162 case QPlatformTheme::DialogDiscardButton:
163 return qt_gtk_get_icon(GTK_STOCK_DELETE);
164 case QPlatformTheme::DialogOkButton:
165 return qt_gtk_get_icon(GTK_STOCK_OK);
166 case QPlatformTheme::DialogCancelButton:
167 return qt_gtk_get_icon(GTK_STOCK_CANCEL);
168 case QPlatformTheme::DialogYesButton:
169 return qt_gtk_get_icon(GTK_STOCK_YES);
170 case QPlatformTheme::DialogNoButton:
171 return qt_gtk_get_icon(GTK_STOCK_NO);
172 case QPlatformTheme::DialogOpenButton:
173 return qt_gtk_get_icon(GTK_STOCK_OPEN);
174 case QPlatformTheme::DialogCloseButton:
175 return qt_gtk_get_icon(GTK_STOCK_CLOSE);
176 case QPlatformTheme::DialogApplyButton:
177 return qt_gtk_get_icon(GTK_STOCK_APPLY);
178 case QPlatformTheme::DialogSaveButton:
179 return qt_gtk_get_icon(GTK_STOCK_SAVE);
180 case QPlatformTheme::MessageBoxWarning:
181 return qt_gtk_get_icon(GTK_STOCK_DIALOG_WARNING);
182 case QPlatformTheme::MessageBoxQuestion:
183 return qt_gtk_get_icon(GTK_STOCK_DIALOG_QUESTION);
184 case QPlatformTheme::MessageBoxInformation:
185 return qt_gtk_get_icon(GTK_STOCK_DIALOG_INFO);
186 case QPlatformTheme::MessageBoxCritical:
187 return qt_gtk_get_icon(GTK_STOCK_DIALOG_ERROR);
188 case QPlatformTheme::CustomBase:
189 case QPlatformTheme::TitleBarMenuButton:
190 case QPlatformTheme::TitleBarMinButton:
191 case QPlatformTheme::TitleBarMaxButton:
192 case QPlatformTheme::TitleBarCloseButton:
193 case QPlatformTheme::TitleBarNormalButton:
194 case QPlatformTheme::TitleBarShadeButton:
195 case QPlatformTheme::TitleBarUnshadeButton:
196 case QPlatformTheme::TitleBarContextHelpButton:
197 case QPlatformTheme::DockWidgetCloseButton:
198 case QPlatformTheme::DesktopIcon:
199 case QPlatformTheme::TrashIcon:
200 case QPlatformTheme::ComputerIcon:
201 case QPlatformTheme::DriveFDIcon:
202 case QPlatformTheme::DriveHDIcon:
203 case QPlatformTheme::DriveCDIcon:
204 case QPlatformTheme::DriveDVDIcon:
205 case QPlatformTheme::DriveNetIcon:
206 case QPlatformTheme::DirOpenIcon:
207 case QPlatformTheme::DirClosedIcon:
208 case QPlatformTheme::DirLinkIcon:
209 case QPlatformTheme::DirLinkOpenIcon:
210 case QPlatformTheme::FileIcon:
211 case QPlatformTheme::FileLinkIcon:
212 case QPlatformTheme::ToolBarHorizontalExtensionButton:
213 case QPlatformTheme::ToolBarVerticalExtensionButton:
214 case QPlatformTheme::FileDialogStart:
215 case QPlatformTheme::FileDialogEnd:
216 case QPlatformTheme::FileDialogToParent:
217 case QPlatformTheme::FileDialogNewFolder:
218 case QPlatformTheme::FileDialogDetailedView:
219 case QPlatformTheme::FileDialogInfoView:
220 case QPlatformTheme::FileDialogContentsView:
221 case QPlatformTheme::FileDialogListView:
222 case QPlatformTheme::FileDialogBack:
223 case QPlatformTheme::DirIcon:
224 case QPlatformTheme::DialogHelpButton:
225 case QPlatformTheme::DialogResetButton:
226 case QPlatformTheme::ArrowUp:
227 case QPlatformTheme::ArrowDown:
228 case QPlatformTheme::ArrowLeft:
229 case QPlatformTheme::ArrowRight:
230 case QPlatformTheme::ArrowBack:
231 case QPlatformTheme::ArrowForward:
232 case QPlatformTheme::DirHomeIcon:
233 case QPlatformTheme::CommandLink:
234 case QPlatformTheme::VistaShield:
235 case QPlatformTheme::BrowserReload:
236 case QPlatformTheme::BrowserStop:
237 case QPlatformTheme::MediaPlay:
238 case QPlatformTheme::MediaStop:
239 case QPlatformTheme::MediaPause:
240 case QPlatformTheme::MediaSkipForward:
241 case QPlatformTheme::MediaSkipBackward:
242 case QPlatformTheme::MediaSeekForward:
243 case QPlatformTheme::MediaSeekBackward:
244 case QPlatformTheme::MediaVolume:
245 case QPlatformTheme::MediaVolumeMuted:
246 case QPlatformTheme::LineEditClearButton:
247 case QPlatformTheme::DialogYesToAllButton:
248 case QPlatformTheme::DialogNoToAllButton:
249 case QPlatformTheme::DialogSaveAllButton:
250 case QPlatformTheme::DialogAbortButton:
251 case QPlatformTheme::DialogRetryButton:
252 case QPlatformTheme::DialogIgnoreButton:
253 case QPlatformTheme::RestoreDefaultsButton:
254 case QPlatformTheme::TabCloseButton:
255 case QPlatformTheme::NStandardPixmap:
256 return QImage();
257 }
258 Q_UNREACHABLE();
259}
260
261/*!
262 \internal
263 \brief Returns a QImage for a given GTK \param iconName.
264 */
265QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const
266{
267 GtkIconSet* iconSet = gtk_icon_factory_lookup_default (stock_id: iconName);
268 GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(icon_set: iconSet, context: context(), size: GTK_ICON_SIZE_DIALOG);
269 return qt_convert_gdk_pixbuf(buf: icon);
270}
271
272/*!
273 \internal
274 \brief Returns a QImage converted from the GDK pixel buffer \param buf.
275
276 The ability to convert GdkPixbuf to QImage relies on the following assumptions:
277 \list
278 \li QImage uses uchar as a data container (unasserted)
279 \li the types guint8 and uchar are identical (statically asserted)
280 \li GDK pixel buffer uses 8 bits per sample (assumed at runtime)
281 \li GDK pixel buffer has 4 channels (assumed at runtime)
282 \endlist
283 */
284QImage QGtk3Interface::qt_convert_gdk_pixbuf(GdkPixbuf *buf) const
285{
286 if (!buf)
287 return QImage();
288
289 const guint8 *gdata = gdk_pixbuf_read_pixels(pixbuf: buf);
290 static_assert(std::is_same<decltype(gdata), const uchar *>::value,
291 "guint8 has diverted from uchar. Code needs fixing.");
292 Q_ASSUME(gdk_pixbuf_get_bits_per_sample(buf) == 8);
293 Q_ASSUME(gdk_pixbuf_get_n_channels(buf) == 4);
294 const uchar *data = static_cast<const uchar *>(gdata);
295
296 const int width = gdk_pixbuf_get_width(pixbuf: buf);
297 const int height = gdk_pixbuf_get_height(pixbuf: buf);
298 const int bpl = gdk_pixbuf_get_rowstride(pixbuf: buf);
299 QImage converted(data, width, height, bpl, QImage::Format_ARGB32);
300 return converted.copy(); // detatch to survive lifetime of buf
301}
302
303/*!
304 \internal
305 \brief Instantiate a new GTK widget.
306
307 Returns a pointer to a new GTK widget of \param type, allocated on the heap.
308 Returns nullptr of gtk_Default has is passed.
309 */
310GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const
311{
312#define CASE(Type)\
313 case QGtkWidget::Type: return Type ##_new();
314#define CASEN(Type)\
315 case QGtkWidget::Type: return Type ##_new(nullptr);
316
317 switch (type) {
318 CASE(gtk_menu_bar)
319 CASE(gtk_menu)
320 CASE(gtk_button)
321 case QGtkWidget::gtk_button_box: return gtk_button_box_new(orientation: GtkOrientation::GTK_ORIENTATION_HORIZONTAL);
322 CASE(gtk_check_button)
323 CASEN(gtk_radio_button)
324 CASEN(gtk_frame)
325 CASE(gtk_statusbar)
326 CASE(gtk_entry)
327 case QGtkWidget::gtk_popup: return gtk_window_new(type: GTK_WINDOW_POPUP);
328 CASE(gtk_notebook)
329 CASE(gtk_toolbar)
330 CASE(gtk_tree_view)
331 CASE(gtk_combo_box)
332 CASE(gtk_combo_box_text)
333 CASE(gtk_progress_bar)
334 CASE(gtk_fixed)
335 CASE(gtk_separator_menu_item)
336 CASE(gtk_offscreen_window)
337 case QGtkWidget::gtk_Default: return nullptr;
338 }
339#undef CASE
340#undef CASEN
341 Q_UNREACHABLE();
342}
343
344/*!
345 \internal
346 \brief Read a GTK widget's color from a generic color getter.
347
348 This method returns a generic color of \param con, a given GTK style context.
349 The requested color is defined by \param def and the GTK color-state \param state.
350 The return type is GDK color in RGBA format.
351 */
352GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const
353{
354 GdkRGBA color;
355
356#define CASE(def, call)\
357 case QGtkColorDefault::def:\
358 gtk_style_context_get_ ##call(con, state, &color);\
359 break;
360
361 switch (def) {
362 CASE(Foreground, color)
363 CASE(Background, background_color)
364 CASE(Border, border_color)
365 }
366 return color;
367#undef CASE
368}
369
370/*!
371 \internal
372 \brief Read a GTK widget's color from a property.
373
374 Returns a color of GTK-widget \param widget, defined by \param source and \param state.
375 The return type is GDK color in RGBA format.
376
377 \note If no corresponding property can be found for \param source, the method falls back to a
378 suitable generic color.
379 */
380QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const
381{
382 GdkRGBA col;
383 GtkStyleContext *con = context(widget);
384
385#define CASE(src, def)\
386 case QGtkColorSource::src: {\
387 const ColorKey key = ColorKey({QGtkColorSource::src, state});\
388 if (gtkColorMap.contains(key)) {\
389 const ColorValue val = gtkColorMap.value(key);\
390 if (!gtk_style_context_lookup_color(con, val.propertyName.toUtf8().constData(), &col)) {\
391 col = genericColor(con, state, val.genericSource);\
392 qCDebug(lcQGtk3Interface) << "Property name" << val.propertyName << "not found.\n"\
393 << "Falling back to " << val.genericSource;\
394 }\
395 } else {\
396 col = genericColor(con, state, QGtkColorDefault::def);\
397 qCDebug(lcQGtk3Interface) << "No color source found for" << QGtkColorSource::src\
398 << fromGtkState(state) << "\n Falling back to"\
399 << QGtkColorDefault::def;\
400 }\
401 }\
402 break;
403
404 switch (source) {
405 CASE(Foreground, Foreground)
406 CASE(Background, Background)
407 CASE(Text, Foreground)
408 CASE(Base, Background)
409 CASE(Border, Border)
410 }
411
412 return fromGdkColor(c: col);
413#undef CASE
414}
415
416/*!
417 \internal
418 \brief Get pointer to a GTK widget by \param type.
419
420 Returns the pointer to a GTK widget, specified by \param type.
421 GTK widgets are cached, so that only one instance of each type is created.
422 \note
423 The method returns nullptr for the enum value gtk_Default.
424 */
425GtkWidget *QGtk3Interface::widget(QGtkWidget type) const
426{
427 if (type == QGtkWidget::gtk_Default)
428 return nullptr;
429
430 // Return from cache
431 if (GtkWidget *w = cache.value(key: type))
432 return w;
433
434 // Create new item and cache it
435 GtkWidget *w = qt_new_gtkWidget(type);
436 cache.insert(key: type, value: w);
437 return w;
438}
439
440/*!
441 \internal
442 \brief Access a GTK widget's style context.
443
444 Returns the pointer to the style context of GTK widget \param w.
445
446 \note If \param w is nullptr, the GTK default style context (entry style) is returned.
447 */
448GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const
449{
450 if (w)
451 return gtk_widget_get_style_context(widget: w);
452
453 return gtk_widget_get_style_context(widget: widget(type: QGtkWidget::gtk_entry));
454}
455
456/*!
457 \internal
458 \brief Create a QBrush from a GTK widget.
459
460 Returns a QBrush corresponding to GTK widget type \param wtype, \param source and \param state.
461
462 Brush height and width is ignored in GTK3, because brush assets (e.g. 9-patches)
463 can't be accessed by the GTK3 API. It's therefore unknown, if the brush relates only to colors,
464 or to a pixmap based style.
465
466 */
467QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const
468{
469 // FIXME: When a color's pixmap can be accessed via the GTK API,
470 // read it and set it in the brush.
471 return QBrush(color(widget: widget(type: wtype), source, state));
472}
473
474/*!
475 \internal
476 \brief Returns the name of the current GTK theme.
477 */
478QString QGtk3Interface::themeName() const
479{
480 QString name;
481
482 if (GtkSettings *settings = gtk_settings_get_default()) {
483 gchar *theme_name;
484 g_object_get(object: settings, first_property_name: "gtk-theme-name", &theme_name, nullptr);
485 name = QLatin1StringView(theme_name);
486 g_free(mem: theme_name);
487 }
488
489 return name;
490}
491
492/*!
493 \internal
494 \brief Determine color scheme by colors.
495
496 Returns the color scheme of the current GTK theme, heuristically determined by the
497 lightness difference between default background and foreground colors.
498
499 \note Returns Unknown in the unlikely case that both colors have the same lightness.
500 */
501Qt::ColorScheme QGtk3Interface::colorSchemeByColors() const
502{
503 const QColor background = color(widget: widget(type: QGtkWidget::gtk_Default),
504 source: QGtkColorSource::Background,
505 state: GTK_STATE_FLAG_ACTIVE);
506 const QColor foreground = color(widget: widget(type: QGtkWidget::gtk_Default),
507 source: QGtkColorSource::Foreground,
508 state: GTK_STATE_FLAG_ACTIVE);
509
510 if (foreground.lightness() > background.lightness())
511 return Qt::ColorScheme::Dark;
512 if (foreground.lightness() < background.lightness())
513 return Qt::ColorScheme::Light;
514 return Qt::ColorScheme::Unknown;
515}
516
517/*!
518 \internal
519 \brief Map font type to GTK widget type.
520
521 Returns the GTK widget type corresponding to the given QPlatformTheme::Font \param type.
522 */
523inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type)
524{
525 switch (type) {
526 case QPlatformTheme::SystemFont: return QGtkWidget::gtk_Default;
527 case QPlatformTheme::MenuFont: return QGtkWidget::gtk_menu;
528 case QPlatformTheme::MenuBarFont: return QGtkWidget::gtk_menu_bar;
529 case QPlatformTheme::MenuItemFont: return QGtkWidget::gtk_menu;
530 case QPlatformTheme::MessageBoxFont: return QGtkWidget::gtk_popup;
531 case QPlatformTheme::LabelFont: return QGtkWidget::gtk_popup;
532 case QPlatformTheme::TipLabelFont: return QGtkWidget::gtk_Default;
533 case QPlatformTheme::StatusBarFont: return QGtkWidget::gtk_statusbar;
534 case QPlatformTheme::TitleBarFont: return QGtkWidget::gtk_Default;
535 case QPlatformTheme::MdiSubWindowTitleFont: return QGtkWidget::gtk_Default;
536 case QPlatformTheme::DockWidgetTitleFont: return QGtkWidget::gtk_Default;
537 case QPlatformTheme::PushButtonFont: return QGtkWidget::gtk_button;
538 case QPlatformTheme::CheckBoxFont: return QGtkWidget::gtk_check_button;
539 case QPlatformTheme::RadioButtonFont: return QGtkWidget::gtk_radio_button;
540 case QPlatformTheme::ToolButtonFont: return QGtkWidget::gtk_button;
541 case QPlatformTheme::ItemViewFont: return QGtkWidget::gtk_entry;
542 case QPlatformTheme::ListViewFont: return QGtkWidget::gtk_tree_view;
543 case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_combo_box;
544 case QPlatformTheme::ListBoxFont: return QGtkWidget::gtk_Default;
545 case QPlatformTheme::ComboMenuItemFont: return QGtkWidget::gtk_combo_box;
546 case QPlatformTheme::ComboLineEditFont: return QGtkWidget::gtk_combo_box_text;
547 case QPlatformTheme::SmallFont: return QGtkWidget::gtk_Default;
548 case QPlatformTheme::MiniFont: return QGtkWidget::gtk_Default;
549 case QPlatformTheme::FixedFont: return QGtkWidget::gtk_Default;
550 case QPlatformTheme::GroupBoxTitleFont: return QGtkWidget::gtk_Default;
551 case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button;
552 case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry;
553 case QPlatformTheme::NFonts: return QGtkWidget::gtk_Default;
554 }
555 Q_UNREACHABLE();
556}
557
558/*!
559 \internal
560 \brief Convert pango \param style to QFont::Style.
561 */
562inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style)
563{
564 switch (style) {
565 case PANGO_STYLE_ITALIC: return QFont::StyleItalic;
566 case PANGO_STYLE_OBLIQUE: return QFont::StyleOblique;
567 case PANGO_STYLE_NORMAL: return QFont::StyleNormal;
568 }
569 // This is reached when GTK has introduced a new font style
570 Q_UNREACHABLE();
571}
572
573/*!
574 \internal
575 \brief Convert pango font \param weight to an int, representing font weight in Qt.
576
577 Compatibility of PangoWeight is statically asserted.
578 The minimum (1) and maximum (1000) weight in Qt is respeced.
579 */
580inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight)
581{
582 // GTK PangoWeight can be directly converted to QFont::Weight
583 // unless one of the enums changes.
584 static_assert(PANGO_WEIGHT_THIN == 100 && PANGO_WEIGHT_ULTRAHEAVY == 1000,
585 "Pango font weight enum changed. Fix conversion.");
586
587 static_assert(QFont::Thin == 100 && QFont::Black == 900,
588 "QFont::Weight enum changed. Fix conversion.");
589
590 return qBound(min: 1, val: static_cast<int>(weight), max: 1000);
591}
592
593/*!
594 \internal
595 \brief Return a GTK styled font.
596
597 Returns the QFont corresponding to \param type by reading the corresponding
598 GTK widget type's font.
599
600 \note GTK allows to specify a non fixed font as the system's fixed font.
601 If a fixed font is requested, the method fixes the pitch and falls back to monospace,
602 unless a suitable fixed pitch font is found.
603 */
604QFont QGtk3Interface::font(QPlatformTheme::Font type) const
605{
606 GtkStyleContext *con = context(w: widget(type: toWidgetType(type)));
607 if (!con)
608 return QFont();
609
610 // explicitly add provider for fixed font
611 GtkCssProvider *cssProvider = nullptr;
612 if (type == QPlatformTheme::FixedFont) {
613 cssProvider = gtk_css_provider_new();
614 gtk_style_context_add_class (context: con, GTK_STYLE_CLASS_MONOSPACE);
615 const char *fontSpec = "* {font-family: monospace;}";
616 gtk_css_provider_load_from_data(css_provider: cssProvider, data: fontSpec, length: -1, NULL);
617 gtk_style_context_add_provider(context: con, GTK_STYLE_PROVIDER(cssProvider),
618 GTK_STYLE_PROVIDER_PRIORITY_USER);
619 }
620
621 // remove monospace provider from style context and unref it
622 QScopeGuard guard([&](){
623 if (cssProvider) {
624 gtk_style_context_remove_provider(context: con, GTK_STYLE_PROVIDER(cssProvider));
625 g_object_unref(object: cssProvider);
626 }
627 });
628
629 const PangoFontDescription *gtkFont = gtk_style_context_get_font(context: con, state: GTK_STATE_FLAG_NORMAL);
630 if (!gtkFont)
631 return QFont();
632
633 const QString family = QString::fromLatin1(ba: pango_font_description_get_family(desc: gtkFont));
634 if (family.isEmpty())
635 return QFont();
636
637 const int weight = toFontWeight(weight: pango_font_description_get_weight(desc: gtkFont));
638
639 // Creating a QFont() creates a futex lockup on a theme change
640 // QFont doesn't have a constructor with float point size
641 // => create a dummy point size and set it later.
642 QFont font(family, 1, weight);
643 font.setPointSizeF(static_cast<float>(pango_font_description_get_size(desc: gtkFont)/PANGO_SCALE));
644 font.setStyle(toFontStyle(style: pango_font_description_get_style(desc: gtkFont)));
645
646 if (type == QPlatformTheme::FixedFont) {
647 font.setFixedPitch(true);
648 if (!QFontInfo(font).fixedPitch()) {
649 qCDebug(lcQGtk3Interface) << "No fixed pitch font found in font family"
650 << font.family() << ". falling back to a default"
651 << "fixed pitch font";
652 font.setFamily("monospace"_L1);
653 }
654 }
655
656 return font;
657}
658
659/*!
660 \internal
661 \brief Returns a GTK styled file icon for \param fileInfo.
662 */
663QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const
664{
665 GFile *file = g_file_new_for_path(path: fileInfo.absoluteFilePath().toLatin1().constData());
666 if (!file)
667 return QIcon();
668
669 GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
670 flags: G_FILE_QUERY_INFO_NONE, cancellable: nullptr, error: nullptr);
671 if (!info) {
672 g_object_unref(object: file);
673 return QIcon();
674 }
675
676 GIcon *icon = g_file_info_get_icon(info);
677 if (!icon) {
678 g_object_unref(object: file);
679 g_object_unref(object: info);
680 return QIcon();
681 }
682
683 GtkIconTheme *theme = gtk_icon_theme_get_default();
684 GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(icon_theme: theme, icon, size: GTK_ICON_SIZE_BUTTON,
685 flags: GTK_ICON_LOOKUP_FORCE_SIZE);
686 if (!iconInfo) {
687 g_object_unref(object: file);
688 g_object_unref(object: info);
689 g_object_unref(object: icon);
690 return QIcon();
691 }
692
693 GdkPixbuf *buf = gtk_icon_info_load_icon(icon_info: iconInfo, error: nullptr);
694 QImage image = qt_convert_gdk_pixbuf(buf);
695 g_object_unref(object: file);
696 g_object_unref(object: info);
697 g_object_unref(object: icon);
698 g_object_unref(object: buf);
699 return QIcon(QPixmap::fromImage(image));
700}
701
702QT_END_NAMESPACE
703

source code of qtbase/src/plugins/platformthemes/gtk3/qgtk3interface.cpp