summaryrefslogtreecommitdiffstats
path: root/src/plugins/platformthemes/gtk3/qgtk3interface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platformthemes/gtk3/qgtk3interface.cpp')
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3interface.cpp702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp
new file mode 100644
index 0000000000..a35e211fbf
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp
@@ -0,0 +1,702 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#include "qgtk3interface_p.h"
+#include "qgtk3storage_p.h"
+#include <QtCore/QMetaEnum>
+#include <QtCore/QFileInfo>
+#include <QtGui/QFontDatabase>
+
+QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQGtk3Interface, "qt.qpa.gtk");
+
+
+// Callback for gnome event loop has to be static
+static QGtk3Storage *m_storage = nullptr;
+
+QGtk3Interface::QGtk3Interface(QGtk3Storage *s)
+{
+ initColorMap();
+
+ if (!s) {
+ qCDebug(lcQGtk3Interface) << "QGtk3Interface instantiated without QGtk3Storage."
+ << "No reaction to runtime theme changes.";
+ return;
+ }
+
+ // Connect to the GTK settings changed signal
+ auto handleThemeChange = [] {
+ if (m_storage)
+ m_storage->handleThemeChange();
+ };
+
+ GtkSettings *settings = gtk_settings_get_default();
+ const gboolean success = g_signal_connect(settings, "notify::gtk-theme-name",
+ G_CALLBACK(handleThemeChange), nullptr);
+ if (success == FALSE) {
+ qCDebug(lcQGtk3Interface) << "Connection to theme change signal failed."
+ << "No reaction to runtime theme changes.";
+ } else {
+ m_storage = s;
+ }
+}
+
+QGtk3Interface::~QGtk3Interface()
+{
+ // Ignore theme changes when destructor is reached
+ m_storage = nullptr;
+
+ // QGtkWidgets have to be destroyed manually
+ for (auto v : cache)
+ gtk_widget_destroy(v.second);
+}
+
+/*!
+ \internal
+ \brief Converts a string into the GtkStateFlags enum.
+
+ Converts a string formatted GTK color \param state into an enum value.
+ Returns an integer corresponding to GtkStateFlags.
+ Returns -1 if \param state does not correspond to a valid enum key.
+ */
+int QGtk3Interface::toGtkState(const QString &state)
+{
+#define CASE(x) \
+ if (QLatin1String(QByteArray(state.toLatin1())) == #x ##_L1) \
+ return GTK_STATE_FLAG_ ##x
+
+#define CONVERT\
+ CASE(NORMAL);\
+ CASE(ACTIVE);\
+ CASE(PRELIGHT);\
+ CASE(SELECTED);\
+ CASE(INSENSITIVE);\
+ CASE(INCONSISTENT);\
+ CASE(FOCUSED);\
+ CASE(BACKDROP);\
+ CASE(DIR_LTR);\
+ CASE(DIR_RTL);\
+ CASE(LINK);\
+ CASE(VISITED);\
+ CASE(CHECKED);\
+ CASE(DROP_ACTIVE)
+
+ CONVERT;
+ return -1;
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Returns \param state converted into a string.
+ */
+const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state)
+{
+#define CASE(x) case GTK_STATE_FLAG_ ##x: return QLatin1String(#x)
+ switch (state) {
+ CONVERT;
+ }
+ Q_UNREACHABLE();
+#undef CASE
+#undef CONVERT
+}
+
+/*!
+ \internal
+ \brief Populates the internal map used to find a GTK color's source and fallback generic color.
+ */
+void QGtk3Interface::initColorMap()
+{
+ #define SAVE(src, state, prop, def)\
+ {ColorKey({QGtkColorSource::src, GTK_STATE_FLAG_ ##state}), ColorValue({#prop ##_L1, QGtkColorDefault::def})}
+
+ gtkColorMap = ColorMap {
+ SAVE(Foreground, NORMAL, theme_fg_color, Foreground),
+ SAVE(Foreground, BACKDROP, theme_unfocused_selected_fg_color, Foreground),
+ SAVE(Foreground, INSENSITIVE, insensitive_fg_color, Foreground),
+ SAVE(Foreground, SELECTED, theme_selected_fg_color, Foreground),
+ SAVE(Foreground, ACTIVE, theme_unfocused_fg_color, Foreground),
+ SAVE(Text, NORMAL, theme_text_color, Foreground),
+ SAVE(Text, ACTIVE, theme_unfocused_text_color, Foreground),
+ SAVE(Base, NORMAL, theme_base_color, Background),
+ SAVE(Base, INSENSITIVE, insensitive_base_color, Background),
+ SAVE(Background, NORMAL, theme_bg_color, Background),
+ SAVE(Background, SELECTED, theme_selected_bg_color, Background),
+ SAVE(Background, INSENSITIVE, insensitive_bg_color, Background),
+ SAVE(Background, ACTIVE, theme_unfocused_bg_color, Background),
+ SAVE(Background, BACKDROP, theme_unfocused_selected_bg_color, Background),
+ SAVE(Border, NORMAL, borders, Border),
+ SAVE(Border, ACTIVE, unfocused_borders, Border)
+ };
+#undef SAVE
+
+ qCDebug(lcQGtk3Interface) << "Color map populated from defaults.";
+}
+
+/*!
+ \internal
+ \brief Returns a QImage corresponding to \param standardPixmap.
+
+ A QImage (not a QPixmap) is returned so it can be cached and re-scaled in case the pixmap is
+ requested multiple times with different resolutions.
+
+ \note Rather than defaulting to a QImage(), all QPlatformTheme::StandardPixmap enum values have
+ been mentioned explicitly.
+ That way they can be covered more easily in case additional icons are provided by GTK.
+ */
+QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const
+{
+ switch (standardPixmap) {
+ case QPlatformTheme::DialogDiscardButton:
+ return qt_gtk_get_icon(GTK_STOCK_DELETE);
+ case QPlatformTheme::DialogOkButton:
+ return qt_gtk_get_icon(GTK_STOCK_OK);
+ case QPlatformTheme::DialogCancelButton:
+ return qt_gtk_get_icon(GTK_STOCK_CANCEL);
+ case QPlatformTheme::DialogYesButton:
+ return qt_gtk_get_icon(GTK_STOCK_YES);
+ case QPlatformTheme::DialogNoButton:
+ return qt_gtk_get_icon(GTK_STOCK_NO);
+ case QPlatformTheme::DialogOpenButton:
+ return qt_gtk_get_icon(GTK_STOCK_OPEN);
+ case QPlatformTheme::DialogCloseButton:
+ return qt_gtk_get_icon(GTK_STOCK_CLOSE);
+ case QPlatformTheme::DialogApplyButton:
+ return qt_gtk_get_icon(GTK_STOCK_APPLY);
+ case QPlatformTheme::DialogSaveButton:
+ return qt_gtk_get_icon(GTK_STOCK_SAVE);
+ case QPlatformTheme::MessageBoxWarning:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_WARNING);
+ case QPlatformTheme::MessageBoxQuestion:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_QUESTION);
+ case QPlatformTheme::MessageBoxInformation:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_INFO);
+ case QPlatformTheme::MessageBoxCritical:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_ERROR);
+ case QPlatformTheme::CustomBase:
+ case QPlatformTheme::TitleBarMenuButton:
+ case QPlatformTheme::TitleBarMinButton:
+ case QPlatformTheme::TitleBarMaxButton:
+ case QPlatformTheme::TitleBarCloseButton:
+ case QPlatformTheme::TitleBarNormalButton:
+ case QPlatformTheme::TitleBarShadeButton:
+ case QPlatformTheme::TitleBarUnshadeButton:
+ case QPlatformTheme::TitleBarContextHelpButton:
+ case QPlatformTheme::DockWidgetCloseButton:
+ case QPlatformTheme::DesktopIcon:
+ case QPlatformTheme::TrashIcon:
+ case QPlatformTheme::ComputerIcon:
+ case QPlatformTheme::DriveFDIcon:
+ case QPlatformTheme::DriveHDIcon:
+ case QPlatformTheme::DriveCDIcon:
+ case QPlatformTheme::DriveDVDIcon:
+ case QPlatformTheme::DriveNetIcon:
+ case QPlatformTheme::DirOpenIcon:
+ case QPlatformTheme::DirClosedIcon:
+ case QPlatformTheme::DirLinkIcon:
+ case QPlatformTheme::DirLinkOpenIcon:
+ case QPlatformTheme::FileIcon:
+ case QPlatformTheme::FileLinkIcon:
+ case QPlatformTheme::ToolBarHorizontalExtensionButton:
+ case QPlatformTheme::ToolBarVerticalExtensionButton:
+ case QPlatformTheme::FileDialogStart:
+ case QPlatformTheme::FileDialogEnd:
+ case QPlatformTheme::FileDialogToParent:
+ case QPlatformTheme::FileDialogNewFolder:
+ case QPlatformTheme::FileDialogDetailedView:
+ case QPlatformTheme::FileDialogInfoView:
+ case QPlatformTheme::FileDialogContentsView:
+ case QPlatformTheme::FileDialogListView:
+ case QPlatformTheme::FileDialogBack:
+ case QPlatformTheme::DirIcon:
+ case QPlatformTheme::DialogHelpButton:
+ case QPlatformTheme::DialogResetButton:
+ case QPlatformTheme::ArrowUp:
+ case QPlatformTheme::ArrowDown:
+ case QPlatformTheme::ArrowLeft:
+ case QPlatformTheme::ArrowRight:
+ case QPlatformTheme::ArrowBack:
+ case QPlatformTheme::ArrowForward:
+ case QPlatformTheme::DirHomeIcon:
+ case QPlatformTheme::CommandLink:
+ case QPlatformTheme::VistaShield:
+ case QPlatformTheme::BrowserReload:
+ case QPlatformTheme::BrowserStop:
+ case QPlatformTheme::MediaPlay:
+ case QPlatformTheme::MediaStop:
+ case QPlatformTheme::MediaPause:
+ case QPlatformTheme::MediaSkipForward:
+ case QPlatformTheme::MediaSkipBackward:
+ case QPlatformTheme::MediaSeekForward:
+ case QPlatformTheme::MediaSeekBackward:
+ case QPlatformTheme::MediaVolume:
+ case QPlatformTheme::MediaVolumeMuted:
+ case QPlatformTheme::LineEditClearButton:
+ case QPlatformTheme::DialogYesToAllButton:
+ case QPlatformTheme::DialogNoToAllButton:
+ case QPlatformTheme::DialogSaveAllButton:
+ case QPlatformTheme::DialogAbortButton:
+ case QPlatformTheme::DialogRetryButton:
+ case QPlatformTheme::DialogIgnoreButton:
+ case QPlatformTheme::RestoreDefaultsButton:
+ case QPlatformTheme::TabCloseButton:
+ case QPlatformTheme::NStandardPixmap:
+ return QImage();
+ }
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Returns a QImage for a given GTK \param iconName.
+ */
+QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const
+{
+ GtkIconSet* iconSet = gtk_icon_factory_lookup_default (iconName);
+ GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(iconSet, context(), GTK_ICON_SIZE_DIALOG);
+ return qt_convert_gdk_pixbuf(icon);
+}
+
+/*!
+ \internal
+ \brief Returns a QImage converted from the GDK pixel buffer \param buf.
+
+ The ability to convert GdkPixbuf to QImage relies on the following assumptions:
+ \list
+ \li QImage uses uchar as a data container (unasserted)
+ \li the types guint8 and uchar are identical (statically asserted)
+ \li GDK pixel buffer uses 8 bits per sample (assumed at runtime)
+ \li GDK pixel buffer has 4 channels (assumed at runtime)
+ \endlist
+ */
+QImage QGtk3Interface::qt_convert_gdk_pixbuf(GdkPixbuf *buf) const
+{
+ if (!buf)
+ return QImage();
+
+ const guint8 *gdata = gdk_pixbuf_read_pixels(buf);
+ static_assert(std::is_same<decltype(gdata), const uchar *>::value,
+ "guint8 has diverted from uchar. Code needs fixing.");
+ Q_ASSERT(gdk_pixbuf_get_bits_per_sample(buf) == 8);
+ Q_ASSERT(gdk_pixbuf_get_n_channels(buf) == 4);
+ const uchar *data = static_cast<const uchar *>(gdata);
+
+ const int width = gdk_pixbuf_get_width(buf);
+ const int height = gdk_pixbuf_get_height(buf);
+ const int bpl = gdk_pixbuf_get_rowstride(buf);
+ QImage converted(data, width, height, bpl, QImage::Format_RGBA8888);
+
+ // convert to more optimal format and detach to survive lifetime of buf
+ return converted.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+}
+
+/*!
+ \internal
+ \brief Instantiate a new GTK widget.
+
+ Returns a pointer to a new GTK widget of \param type, allocated on the heap.
+ Returns nullptr of gtk_Default has is passed.
+ */
+GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const
+{
+#define CASE(Type)\
+ case QGtkWidget::Type: return Type ##_new();
+#define CASEN(Type)\
+ case QGtkWidget::Type: return Type ##_new(nullptr);
+
+ switch (type) {
+ CASE(gtk_menu_bar)
+ CASE(gtk_menu)
+ CASE(gtk_button)
+ case QGtkWidget::gtk_button_box: return gtk_button_box_new(GtkOrientation::GTK_ORIENTATION_HORIZONTAL);
+ CASE(gtk_check_button)
+ CASEN(gtk_radio_button)
+ CASEN(gtk_frame)
+ CASE(gtk_statusbar)
+ CASE(gtk_entry)
+ case QGtkWidget::gtk_popup: return gtk_window_new(GTK_WINDOW_POPUP);
+ CASE(gtk_notebook)
+ CASE(gtk_toolbar)
+ CASE(gtk_tree_view)
+ CASE(gtk_combo_box)
+ CASE(gtk_combo_box_text)
+ CASE(gtk_progress_bar)
+ CASE(gtk_fixed)
+ CASE(gtk_separator_menu_item)
+ CASE(gtk_offscreen_window)
+ case QGtkWidget::gtk_Default: return nullptr;
+ }
+#undef CASE
+#undef CASEN
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Read a GTK widget's color from a generic color getter.
+
+ This method returns a generic color of \param con, a given GTK style context.
+ The requested color is defined by \param def and the GTK color-state \param state.
+ The return type is GDK color in RGBA format.
+ */
+GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const
+{
+ GdkRGBA color;
+
+#define CASE(def, call)\
+ case QGtkColorDefault::def:\
+ gtk_style_context_get_ ##call(con, state, &color);\
+ break;
+
+ switch (def) {
+ CASE(Foreground, color)
+ CASE(Background, background_color)
+ CASE(Border, border_color)
+ }
+ return color;
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Read a GTK widget's color from a property.
+
+ Returns a color of GTK-widget \param widget, defined by \param source and \param state.
+ The return type is GDK color in RGBA format.
+
+ \note If no corresponding property can be found for \param source, the method falls back to a
+ suitable generic color.
+ */
+QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const
+{
+ GdkRGBA col;
+ GtkStyleContext *con = context(widget);
+
+#define CASE(src, def)\
+ case QGtkColorSource::src: {\
+ const ColorKey key = ColorKey({QGtkColorSource::src, state});\
+ if (gtkColorMap.contains(key)) {\
+ const ColorValue val = gtkColorMap.value(key);\
+ if (!gtk_style_context_lookup_color(con, val.propertyName.toUtf8().constData(), &col)) {\
+ col = genericColor(con, state, val.genericSource);\
+ qCDebug(lcQGtk3Interface) << "Property name" << val.propertyName << "not found.\n"\
+ << "Falling back to " << val.genericSource;\
+ }\
+ } else {\
+ col = genericColor(con, state, QGtkColorDefault::def);\
+ qCDebug(lcQGtk3Interface) << "No color source found for" << QGtkColorSource::src\
+ << fromGtkState(state) << "\n Falling back to"\
+ << QGtkColorDefault::def;\
+ }\
+ }\
+ break;
+
+ switch (source) {
+ CASE(Foreground, Foreground)
+ CASE(Background, Background)
+ CASE(Text, Foreground)
+ CASE(Base, Background)
+ CASE(Border, Border)
+ }
+
+ return fromGdkColor(col);
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Get pointer to a GTK widget by \param type.
+
+ Returns the pointer to a GTK widget, specified by \param type.
+ GTK widgets are cached, so that only one instance of each type is created.
+ \note
+ The method returns nullptr for the enum value gtk_Default.
+ */
+GtkWidget *QGtk3Interface::widget(QGtkWidget type) const
+{
+ if (type == QGtkWidget::gtk_Default)
+ return nullptr;
+
+ // Return from cache
+ if (GtkWidget *w = cache.value(type))
+ return w;
+
+ // Create new item and cache it
+ GtkWidget *w = qt_new_gtkWidget(type);
+ cache.insert(type, w);
+ return w;
+}
+
+/*!
+ \internal
+ \brief Access a GTK widget's style context.
+
+ Returns the pointer to the style context of GTK widget \param w.
+
+ \note If \param w is nullptr, the GTK default style context (entry style) is returned.
+ */
+GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const
+{
+ if (w)
+ return gtk_widget_get_style_context(w);
+
+ return gtk_widget_get_style_context(widget(QGtkWidget::gtk_entry));
+}
+
+/*!
+ \internal
+ \brief Create a QBrush from a GTK widget.
+
+ Returns a QBrush corresponding to GTK widget type \param wtype, \param source and \param state.
+
+ Brush height and width is ignored in GTK3, because brush assets (e.g. 9-patches)
+ can't be accessed by the GTK3 API. It's therefore unknown, if the brush relates only to colors,
+ or to a pixmap based style.
+
+ */
+QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const
+{
+ // FIXME: When a color's pixmap can be accessed via the GTK API,
+ // read it and set it in the brush.
+ return QBrush(color(widget(wtype), source, state));
+}
+
+/*!
+ \internal
+ \brief Returns the name of the current GTK theme.
+ */
+QString QGtk3Interface::themeName() const
+{
+ QString name;
+
+ if (GtkSettings *settings = gtk_settings_get_default()) {
+ gchar *theme_name;
+ g_object_get(settings, "gtk-theme-name", &theme_name, nullptr);
+ name = QLatin1StringView(theme_name);
+ g_free(theme_name);
+ }
+
+ return name;
+}
+
+/*!
+ \internal
+ \brief Determine color scheme by colors.
+
+ Returns the color scheme of the current GTK theme, heuristically determined by the
+ lightness difference between default background and foreground colors.
+
+ \note Returns Unknown in the unlikely case that both colors have the same lightness.
+ */
+Qt::ColorScheme QGtk3Interface::colorSchemeByColors() const
+{
+ const QColor background = color(widget(QGtkWidget::gtk_Default),
+ QGtkColorSource::Background,
+ GTK_STATE_FLAG_ACTIVE);
+ const QColor foreground = color(widget(QGtkWidget::gtk_Default),
+ QGtkColorSource::Foreground,
+ GTK_STATE_FLAG_ACTIVE);
+
+ if (foreground.lightness() > background.lightness())
+ return Qt::ColorScheme::Dark;
+ if (foreground.lightness() < background.lightness())
+ return Qt::ColorScheme::Light;
+ return Qt::ColorScheme::Unknown;
+}
+
+/*!
+ \internal
+ \brief Map font type to GTK widget type.
+
+ Returns the GTK widget type corresponding to the given QPlatformTheme::Font \param type.
+ */
+inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type)
+{
+ switch (type) {
+ case QPlatformTheme::SystemFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MenuFont: return QGtkWidget::gtk_menu;
+ case QPlatformTheme::MenuBarFont: return QGtkWidget::gtk_menu_bar;
+ case QPlatformTheme::MenuItemFont: return QGtkWidget::gtk_menu;
+ case QPlatformTheme::MessageBoxFont: return QGtkWidget::gtk_popup;
+ case QPlatformTheme::LabelFont: return QGtkWidget::gtk_popup;
+ case QPlatformTheme::TipLabelFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::StatusBarFont: return QGtkWidget::gtk_statusbar;
+ case QPlatformTheme::TitleBarFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MdiSubWindowTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::DockWidgetTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::PushButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::CheckBoxFont: return QGtkWidget::gtk_check_button;
+ case QPlatformTheme::RadioButtonFont: return QGtkWidget::gtk_radio_button;
+ case QPlatformTheme::ToolButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::ItemViewFont: return QGtkWidget::gtk_entry;
+ case QPlatformTheme::ListViewFont: return QGtkWidget::gtk_tree_view;
+ case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_combo_box;
+ case QPlatformTheme::ListBoxFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::ComboMenuItemFont: return QGtkWidget::gtk_combo_box;
+ case QPlatformTheme::ComboLineEditFont: return QGtkWidget::gtk_combo_box_text;
+ case QPlatformTheme::SmallFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MiniFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::FixedFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::GroupBoxTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry;
+ case QPlatformTheme::NFonts: return QGtkWidget::gtk_Default;
+ }
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Convert pango \param style to QFont::Style.
+ */
+inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style)
+{
+ switch (style) {
+ case PANGO_STYLE_ITALIC: return QFont::StyleItalic;
+ case PANGO_STYLE_OBLIQUE: return QFont::StyleOblique;
+ case PANGO_STYLE_NORMAL: return QFont::StyleNormal;
+ }
+ // This is reached when GTK has introduced a new font style
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Convert pango font \param weight to an int, representing font weight in Qt.
+
+ Compatibility of PangoWeight is statically asserted.
+ The minimum (1) and maximum (1000) weight in Qt is respeced.
+ */
+inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight)
+{
+ // GTK PangoWeight can be directly converted to QFont::Weight
+ // unless one of the enums changes.
+ static_assert(PANGO_WEIGHT_THIN == 100 && PANGO_WEIGHT_ULTRAHEAVY == 1000,
+ "Pango font weight enum changed. Fix conversion.");
+
+ static_assert(QFont::Thin == 100 && QFont::Black == 900,
+ "QFont::Weight enum changed. Fix conversion.");
+
+ return qBound(1, static_cast<int>(weight), 1000);
+}
+
+/*!
+ \internal
+ \brief Return a GTK styled font.
+
+ Returns the QFont corresponding to \param type by reading the corresponding
+ GTK widget type's font.
+
+ \note GTK allows to specify a non fixed font as the system's fixed font.
+ If a fixed font is requested, the method fixes the pitch and falls back to monospace,
+ unless a suitable fixed pitch font is found.
+ */
+QFont QGtk3Interface::font(QPlatformTheme::Font type) const
+{
+ GtkStyleContext *con = context(widget(toWidgetType(type)));
+ if (!con)
+ return QFont();
+
+ // explicitly add provider for fixed font
+ GtkCssProvider *cssProvider = nullptr;
+ if (type == QPlatformTheme::FixedFont) {
+ cssProvider = gtk_css_provider_new();
+ gtk_style_context_add_class (con, GTK_STYLE_CLASS_MONOSPACE);
+ const char *fontSpec = "* {font-family: monospace;}";
+ gtk_css_provider_load_from_data(cssProvider, fontSpec, -1, NULL);
+ gtk_style_context_add_provider(con, GTK_STYLE_PROVIDER(cssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+ }
+
+ // remove monospace provider from style context and unref it
+ QScopeGuard guard([&](){
+ if (cssProvider) {
+ gtk_style_context_remove_provider(con, GTK_STYLE_PROVIDER(cssProvider));
+ g_object_unref(cssProvider);
+ }
+ });
+
+ const PangoFontDescription *gtkFont = gtk_style_context_get_font(con, GTK_STATE_FLAG_NORMAL);
+ if (!gtkFont)
+ return QFont();
+
+ const QString family = QString::fromLatin1(pango_font_description_get_family(gtkFont));
+ if (family.isEmpty())
+ return QFont();
+
+ const int weight = toFontWeight(pango_font_description_get_weight(gtkFont));
+
+ // Creating a QFont() creates a futex lockup on a theme change
+ // QFont doesn't have a constructor with float point size
+ // => create a dummy point size and set it later.
+ QFont font(family, 1, weight);
+ font.setPointSizeF(static_cast<float>(pango_font_description_get_size(gtkFont)/PANGO_SCALE));
+ font.setStyle(toFontStyle(pango_font_description_get_style(gtkFont)));
+
+ if (type == QPlatformTheme::FixedFont) {
+ font.setFixedPitch(true);
+ if (!QFontInfo(font).fixedPitch()) {
+ qCDebug(lcQGtk3Interface) << "No fixed pitch font found in font family"
+ << font.family() << ". falling back to a default"
+ << "fixed pitch font";
+ font.setFamily("monospace"_L1);
+ }
+ }
+
+ return font;
+}
+
+/*!
+ \internal
+ \brief Returns a GTK styled file icon for \param fileInfo.
+ */
+QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const
+{
+ GFile *file = g_file_new_for_path(fileInfo.absoluteFilePath().toLatin1().constData());
+ if (!file)
+ return QIcon();
+
+ GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
+ if (!info) {
+ g_object_unref(file);
+ return QIcon();
+ }
+
+ GIcon *icon = g_file_info_get_icon(info);
+ if (!icon) {
+ g_object_unref(file);
+ g_object_unref(info);
+ return QIcon();
+ }
+
+ GtkIconTheme *theme = gtk_icon_theme_get_default();
+ GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(theme, icon, 16,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+ if (!iconInfo) {
+ g_object_unref(file);
+ g_object_unref(info);
+ return QIcon();
+ }
+
+ GdkPixbuf *buf = gtk_icon_info_load_icon(iconInfo, nullptr);
+ QImage image = qt_convert_gdk_pixbuf(buf);
+ g_object_unref(file);
+ g_object_unref(info);
+ g_object_unref(buf);
+ return QIcon(QPixmap::fromImage(image));
+}
+
+QT_END_NAMESPACE