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_ASSERT(gdk_pixbuf_get_bits_per_sample(buf) == 8);
293 Q_ASSERT(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_RGBA8888);
300
301 // convert to more optimal format and detach to survive lifetime of buf
302 return converted.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
303}
304
305/*!
306 \internal
307 \brief Instantiate a new GTK widget.
308
309 Returns a pointer to a new GTK widget of \param type, allocated on the heap.
310 Returns nullptr of gtk_Default has is passed.
311 */
312GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const
313{
314#define CASE(Type)\
315 case QGtkWidget::Type: return Type ##_new();
316#define CASEN(Type)\
317 case QGtkWidget::Type: return Type ##_new(nullptr);
318
319 switch (type) {
320 CASE(gtk_menu_bar)
321 CASE(gtk_menu)
322 CASE(gtk_button)
323 case QGtkWidget::gtk_button_box: return gtk_button_box_new(orientation: GtkOrientation::GTK_ORIENTATION_HORIZONTAL);
324 CASE(gtk_check_button)
325 CASEN(gtk_radio_button)
326 CASEN(gtk_frame)
327 CASE(gtk_statusbar)
328 CASE(gtk_entry)
329 case QGtkWidget::gtk_popup: return gtk_window_new(type: GTK_WINDOW_POPUP);
330 CASE(gtk_notebook)
331 CASE(gtk_toolbar)
332 CASE(gtk_tree_view)
333 CASE(gtk_combo_box)
334 CASE(gtk_combo_box_text)
335 CASE(gtk_progress_bar)
336 CASE(gtk_fixed)
337 CASE(gtk_separator_menu_item)
338 CASE(gtk_offscreen_window)
339 case QGtkWidget::gtk_Default: return nullptr;
340 }
341#undef CASE
342#undef CASEN
343 Q_UNREACHABLE();
344}
345
346/*!
347 \internal
348 \brief Read a GTK widget's color from a generic color getter.
349
350 This method returns a generic color of \param con, a given GTK style context.
351 The requested color is defined by \param def and the GTK color-state \param state.
352 The return type is GDK color in RGBA format.
353 */
354GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const
355{
356 GdkRGBA color;
357
358#define CASE(def, call)\
359 case QGtkColorDefault::def:\
360 gtk_style_context_get_ ##call(con, state, &color);\
361 break;
362
363 switch (def) {
364 CASE(Foreground, color)
365 CASE(Background, background_color)
366 CASE(Border, border_color)
367 }
368 return color;
369#undef CASE
370}
371
372/*!
373 \internal
374 \brief Read a GTK widget's color from a property.
375
376 Returns a color of GTK-widget \param widget, defined by \param source and \param state.
377 The return type is GDK color in RGBA format.
378
379 \note If no corresponding property can be found for \param source, the method falls back to a
380 suitable generic color.
381 */
382QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const
383{
384 GdkRGBA col;
385 GtkStyleContext *con = context(widget);
386
387#define CASE(src, def)\
388 case QGtkColorSource::src: {\
389 const ColorKey key = ColorKey({QGtkColorSource::src, state});\
390 if (gtkColorMap.contains(key)) {\
391 const ColorValue val = gtkColorMap.value(key);\
392 if (!gtk_style_context_lookup_color(con, val.propertyName.toUtf8().constData(), &col)) {\
393 col = genericColor(con, state, val.genericSource);\
394 qCDebug(lcQGtk3Interface) << "Property name" << val.propertyName << "not found.\n"\
395 << "Falling back to " << val.genericSource;\
396 }\
397 } else {\
398 col = genericColor(con, state, QGtkColorDefault::def);\
399 qCDebug(lcQGtk3Interface) << "No color source found for" << QGtkColorSource::src\
400 << fromGtkState(state) << "\n Falling back to"\
401 << QGtkColorDefault::def;\
402 }\
403 }\
404 break;
405
406 switch (source) {
407 CASE(Foreground, Foreground)
408 CASE(Background, Background)
409 CASE(Text, Foreground)
410 CASE(Base, Background)
411 CASE(Border, Border)
412 }
413
414 return fromGdkColor(c: col);
415#undef CASE
416}
417
418/*!
419 \internal
420 \brief Get pointer to a GTK widget by \param type.
421
422 Returns the pointer to a GTK widget, specified by \param type.
423 GTK widgets are cached, so that only one instance of each type is created.
424 \note
425 The method returns nullptr for the enum value gtk_Default.
426 */
427GtkWidget *QGtk3Interface::widget(QGtkWidget type) const
428{
429 if (type == QGtkWidget::gtk_Default)
430 return nullptr;
431
432 // Return from cache
433 if (GtkWidget *w = cache.value(key: type))
434 return w;
435
436 // Create new item and cache it
437 GtkWidget *w = qt_new_gtkWidget(type);
438 cache.insert(key: type, value: w);
439 return w;
440}
441
442/*!
443 \internal
444 \brief Access a GTK widget's style context.
445
446 Returns the pointer to the style context of GTK widget \param w.
447
448 \note If \param w is nullptr, the GTK default style context (entry style) is returned.
449 */
450GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const
451{
452 if (w)
453 return gtk_widget_get_style_context(widget: w);
454
455 return gtk_widget_get_style_context(widget: widget(type: QGtkWidget::gtk_entry));
456}
457
458/*!
459 \internal
460 \brief Create a QBrush from a GTK widget.
461
462 Returns a QBrush corresponding to GTK widget type \param wtype, \param source and \param state.
463
464 Brush height and width is ignored in GTK3, because brush assets (e.g. 9-patches)
465 can't be accessed by the GTK3 API. It's therefore unknown, if the brush relates only to colors,
466 or to a pixmap based style.
467
468 */
469QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const
470{
471 // FIXME: When a color's pixmap can be accessed via the GTK API,
472 // read it and set it in the brush.
473 return QBrush(color(widget: widget(type: wtype), source, state));
474}
475
476/*!
477 \internal
478 \brief Returns the name of the current GTK theme.
479 */
480QString QGtk3Interface::themeName() const
481{
482 QString name;
483
484 if (GtkSettings *settings = gtk_settings_get_default()) {
485 gchar *theme_name;
486 g_object_get(object: settings, first_property_name: "gtk-theme-name", &theme_name, nullptr);
487 name = QLatin1StringView(theme_name);
488 g_free(mem: theme_name);
489 }
490
491 return name;
492}
493
494/*!
495 \internal
496 \brief Determine color scheme by colors.
497
498 Returns the color scheme of the current GTK theme, heuristically determined by the
499 lightness difference between default background and foreground colors.
500
501 \note Returns Unknown in the unlikely case that both colors have the same lightness.
502 */
503Qt::ColorScheme QGtk3Interface::colorSchemeByColors() const
504{
505 const QColor background = color(widget: widget(type: QGtkWidget::gtk_Default),
506 source: QGtkColorSource::Background,
507 state: GTK_STATE_FLAG_ACTIVE);
508 const QColor foreground = color(widget: widget(type: QGtkWidget::gtk_Default),
509 source: QGtkColorSource::Foreground,
510 state: GTK_STATE_FLAG_ACTIVE);
511
512 if (foreground.lightness() > background.lightness())
513 return Qt::ColorScheme::Dark;
514 if (foreground.lightness() < background.lightness())
515 return Qt::ColorScheme::Light;
516 return Qt::ColorScheme::Unknown;
517}
518
519/*!
520 \internal
521 \brief Map font type to GTK widget type.
522
523 Returns the GTK widget type corresponding to the given QPlatformTheme::Font \param type.
524 */
525inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type)
526{
527 switch (type) {
528 case QPlatformTheme::SystemFont: return QGtkWidget::gtk_Default;
529 case QPlatformTheme::MenuFont: return QGtkWidget::gtk_menu;
530 case QPlatformTheme::MenuBarFont: return QGtkWidget::gtk_menu_bar;
531 case QPlatformTheme::MenuItemFont: return QGtkWidget::gtk_menu;
532 case QPlatformTheme::MessageBoxFont: return QGtkWidget::gtk_popup;
533 case QPlatformTheme::LabelFont: return QGtkWidget::gtk_popup;
534 case QPlatformTheme::TipLabelFont: return QGtkWidget::gtk_Default;
535 case QPlatformTheme::StatusBarFont: return QGtkWidget::gtk_statusbar;
536 case QPlatformTheme::TitleBarFont: return QGtkWidget::gtk_Default;
537 case QPlatformTheme::MdiSubWindowTitleFont: return QGtkWidget::gtk_Default;
538 case QPlatformTheme::DockWidgetTitleFont: return QGtkWidget::gtk_Default;
539 case QPlatformTheme::PushButtonFont: return QGtkWidget::gtk_button;
540 case QPlatformTheme::CheckBoxFont: return QGtkWidget::gtk_check_button;
541 case QPlatformTheme::RadioButtonFont: return QGtkWidget::gtk_radio_button;
542 case QPlatformTheme::ToolButtonFont: return QGtkWidget::gtk_button;
543 case QPlatformTheme::ItemViewFont: return QGtkWidget::gtk_entry;
544 case QPlatformTheme::ListViewFont: return QGtkWidget::gtk_tree_view;
545 case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_combo_box;
546 case QPlatformTheme::ListBoxFont: return QGtkWidget::gtk_Default;
547 case QPlatformTheme::ComboMenuItemFont: return QGtkWidget::gtk_combo_box;
548 case QPlatformTheme::ComboLineEditFont: return QGtkWidget::gtk_combo_box_text;
549 case QPlatformTheme::SmallFont: return QGtkWidget::gtk_Default;
550 case QPlatformTheme::MiniFont: return QGtkWidget::gtk_Default;
551 case QPlatformTheme::FixedFont: return QGtkWidget::gtk_Default;
552 case QPlatformTheme::GroupBoxTitleFont: return QGtkWidget::gtk_Default;
553 case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button;
554 case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry;
555 case QPlatformTheme::NFonts: return QGtkWidget::gtk_Default;
556 }
557 Q_UNREACHABLE();
558}
559
560/*!
561 \internal
562 \brief Convert pango \param style to QFont::Style.
563 */
564inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style)
565{
566 switch (style) {
567 case PANGO_STYLE_ITALIC: return QFont::StyleItalic;
568 case PANGO_STYLE_OBLIQUE: return QFont::StyleOblique;
569 case PANGO_STYLE_NORMAL: return QFont::StyleNormal;
570 }
571 // This is reached when GTK has introduced a new font style
572 Q_UNREACHABLE();
573}
574
575/*!
576 \internal
577 \brief Convert pango font \param weight to an int, representing font weight in Qt.
578
579 Compatibility of PangoWeight is statically asserted.
580 The minimum (1) and maximum (1000) weight in Qt is respeced.
581 */
582inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight)
583{
584 // GTK PangoWeight can be directly converted to QFont::Weight
585 // unless one of the enums changes.
586 static_assert(PANGO_WEIGHT_THIN == 100 && PANGO_WEIGHT_ULTRAHEAVY == 1000,
587 "Pango font weight enum changed. Fix conversion.");
588
589 static_assert(QFont::Thin == 100 && QFont::Black == 900,
590 "QFont::Weight enum changed. Fix conversion.");
591
592 return qBound(min: 1, val: static_cast<int>(weight), max: 1000);
593}
594
595/*!
596 \internal
597 \brief Return a GTK styled font.
598
599 Returns the QFont corresponding to \param type by reading the corresponding
600 GTK widget type's font.
601
602 \note GTK allows to specify a non fixed font as the system's fixed font.
603 If a fixed font is requested, the method fixes the pitch and falls back to monospace,
604 unless a suitable fixed pitch font is found.
605 */
606QFont QGtk3Interface::font(QPlatformTheme::Font type) const
607{
608 GtkStyleContext *con = context(w: widget(type: toWidgetType(type)));
609 if (!con)
610 return QFont();
611
612 // explicitly add provider for fixed font
613 GtkCssProvider *cssProvider = nullptr;
614 if (type == QPlatformTheme::FixedFont) {
615 cssProvider = gtk_css_provider_new();
616 gtk_style_context_add_class (context: con, GTK_STYLE_CLASS_MONOSPACE);
617 const char *fontSpec = "* {font-family: monospace;}";
618 gtk_css_provider_load_from_data(css_provider: cssProvider, data: fontSpec, length: -1, NULL);
619 gtk_style_context_add_provider(context: con, GTK_STYLE_PROVIDER(cssProvider),
620 GTK_STYLE_PROVIDER_PRIORITY_USER);
621 }
622
623 // remove monospace provider from style context and unref it
624 QScopeGuard guard([&](){
625 if (cssProvider) {
626 gtk_style_context_remove_provider(context: con, GTK_STYLE_PROVIDER(cssProvider));
627 g_object_unref(object: cssProvider);
628 }
629 });
630
631 const PangoFontDescription *gtkFont = gtk_style_context_get_font(context: con, state: GTK_STATE_FLAG_NORMAL);
632 if (!gtkFont)
633 return QFont();
634
635 const QString family = QString::fromLatin1(ba: pango_font_description_get_family(desc: gtkFont));
636 if (family.isEmpty())
637 return QFont();
638
639 const int weight = toFontWeight(weight: pango_font_description_get_weight(desc: gtkFont));
640
641 // Creating a QFont() creates a futex lockup on a theme change
642 // QFont doesn't have a constructor with float point size
643 // => create a dummy point size and set it later.
644 QFont font(family, 1, weight);
645 font.setPointSizeF(static_cast<float>(pango_font_description_get_size(desc: gtkFont)/PANGO_SCALE));
646 font.setStyle(toFontStyle(style: pango_font_description_get_style(desc: gtkFont)));
647
648 if (type == QPlatformTheme::FixedFont) {
649 font.setFixedPitch(true);
650 if (!QFontInfo(font).fixedPitch()) {
651 qCDebug(lcQGtk3Interface) << "No fixed pitch font found in font family"
652 << font.family() << ". falling back to a default"
653 << "fixed pitch font";
654 font.setFamily("monospace"_L1);
655 }
656 }
657
658 return font;
659}
660
661/*!
662 \internal
663 \brief Returns a GTK styled file icon for \param fileInfo.
664 */
665QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const
666{
667 GFile *file = g_file_new_for_path(path: fileInfo.absoluteFilePath().toLatin1().constData());
668 if (!file)
669 return QIcon();
670
671 GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_ICON,
672 flags: G_FILE_QUERY_INFO_NONE, cancellable: nullptr, error: nullptr);
673 if (!info) {
674 g_object_unref(object: file);
675 return QIcon();
676 }
677
678 GIcon *icon = g_file_info_get_icon(info);
679 if (!icon) {
680 g_object_unref(object: file);
681 g_object_unref(object: info);
682 return QIcon();
683 }
684
685 GtkIconTheme *theme = gtk_icon_theme_get_default();
686 GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(icon_theme: theme, icon, size: 16,
687 flags: GTK_ICON_LOOKUP_FORCE_SIZE);
688 if (!iconInfo) {
689 g_object_unref(object: file);
690 g_object_unref(object: info);
691 return QIcon();
692 }
693
694 GdkPixbuf *buf = gtk_icon_info_load_icon(icon_info: iconInfo, error: nullptr);
695 QImage image = qt_convert_gdk_pixbuf(buf);
696 g_object_unref(object: file);
697 g_object_unref(object: info);
698 g_object_unref(object: buf);
699 return QIcon(QPixmap::fromImage(image));
700}
701
702QT_END_NAMESPACE
703

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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