1// Copyright (C) 2016 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#include "qgtk3theme.h"
5#include "qgtk3dialoghelpers.h"
6#include <QVariant>
7#include <QGuiApplication>
8#include <qpa/qwindowsysteminterface.h>
9
10#undef signals
11#include <gtk/gtk.h>
12
13#if QT_CONFIG(xcb_xlib)
14#include <X11/Xlib.h>
15#endif
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21const char *QGtk3Theme::name = "gtk3";
22
23template <typename T>
24static T gtkSetting(const gchar *propertyName)
25{
26 GtkSettings *settings = gtk_settings_get_default();
27 T value;
28 g_object_get(settings, propertyName, &value, NULL);
29 return value;
30}
31
32static QString gtkSetting(const gchar *propertyName)
33{
34 gchararray value = gtkSetting<gchararray>(propertyName);
35 QString str = QString::fromUtf8(utf8: value);
36 g_free(mem: value);
37 return str;
38}
39
40void gtkMessageHandler(const gchar *log_domain,
41 GLogLevelFlags log_level,
42 const gchar *message,
43 gpointer unused_data) {
44 /* Silence false-positive Gtk warnings (we are using Xlib to set
45 * the WM_TRANSIENT_FOR hint).
46 */
47 if (g_strcmp0(str1: message, str2: "GtkDialog mapped without a transient parent. "
48 "This is discouraged.") != 0) {
49 /* For other messages, call the default handler. */
50 g_log_default_handler(log_domain, log_level, message, unused_data);
51 }
52}
53
54QGtk3Theme::QGtk3Theme()
55{
56 // Ensure gtk uses the same windowing system, but let it
57 // fallback in case GDK_BACKEND environment variable
58 // filters the preferred one out
59 if (QGuiApplication::platformName().startsWith(s: "wayland"_L1))
60 gdk_set_allowed_backends(backends: "wayland,x11");
61 else if (QGuiApplication::platformName() == "xcb"_L1)
62 gdk_set_allowed_backends(backends: "x11,wayland");
63
64#if QT_CONFIG(xcb_xlib)
65 // gtk_init will reset the Xlib error handler, and that causes
66 // Qt applications to quit on X errors. Therefore, we need to manually restore it.
67 int (*oldErrorHandler)(Display *, XErrorEvent *) = XSetErrorHandler(nullptr);
68#endif
69
70 gtk_init(argc: nullptr, argv: nullptr);
71
72#if QT_CONFIG(xcb_xlib)
73 XSetErrorHandler(oldErrorHandler);
74#endif
75
76 /* Initialize some types here so that Gtk+ does not crash when reading
77 * the treemodel for GtkFontChooser.
78 */
79 g_type_ensure(PANGO_TYPE_FONT_FAMILY);
80 g_type_ensure(PANGO_TYPE_FONT_FACE);
81
82 /* Use our custom log handler. */
83 g_log_set_handler(log_domain: "Gtk", log_levels: G_LOG_LEVEL_MESSAGE, log_func: gtkMessageHandler, user_data: nullptr);
84
85#define SETTING_CONNECT(setting) g_signal_connect(settings, "notify::" setting, G_CALLBACK(notifyThemeChanged), nullptr)
86 auto notifyThemeChanged = [] {
87 QWindowSystemInterface::handleThemeChange();
88 };
89
90 GtkSettings *settings = gtk_settings_get_default();
91 SETTING_CONNECT("gtk-cursor-blink-time");
92 SETTING_CONNECT("gtk-double-click-distance");
93 SETTING_CONNECT("gtk-double-click-time");
94 SETTING_CONNECT("gtk-long-press-time");
95 SETTING_CONNECT("gtk-entry-password-hint-timeout");
96 SETTING_CONNECT("gtk-dnd-drag-threshold");
97 SETTING_CONNECT("gtk-icon-theme-name");
98 SETTING_CONNECT("gtk-fallback-icon-theme");
99 SETTING_CONNECT("gtk-font-name");
100 SETTING_CONNECT("gtk-application-prefer-dark-theme");
101 SETTING_CONNECT("gtk-theme-name");
102 SETTING_CONNECT("gtk-cursor-theme-name");
103 SETTING_CONNECT("gtk-cursor-theme-size");
104#undef SETTING_CONNECT
105
106 m_storage.reset(p: new QGtk3Storage);
107}
108
109static inline QVariant gtkGetLongPressTime()
110{
111 const char *gtk_long_press_time = "gtk-long-press-time";
112 static bool found = g_object_class_find_property(G_OBJECT_GET_CLASS(gtk_settings_get_default()), property_name: gtk_long_press_time);
113 if (!found)
114 return QVariant();
115 return QVariant(gtkSetting<guint>(propertyName: gtk_long_press_time)); // Since 3.14, apparently we support >= 3.6
116}
117
118QVariant QGtk3Theme::themeHint(QPlatformTheme::ThemeHint hint) const
119{
120 switch (hint) {
121 case QPlatformTheme::CursorFlashTime:
122 return QVariant(gtkSetting<gint>(propertyName: "gtk-cursor-blink-time"));
123 case QPlatformTheme::MouseDoubleClickDistance:
124 return QVariant(gtkSetting<gint>(propertyName: "gtk-double-click-distance"));
125 case QPlatformTheme::MouseDoubleClickInterval:
126 return QVariant(gtkSetting<gint>(propertyName: "gtk-double-click-time"));
127 case QPlatformTheme::MousePressAndHoldInterval: {
128 QVariant v = gtkGetLongPressTime();
129 if (!v.isValid())
130 v = QGnomeTheme::themeHint(hint);
131 return v;
132 }
133 case QPlatformTheme::PasswordMaskDelay:
134 return QVariant(gtkSetting<guint>(propertyName: "gtk-entry-password-hint-timeout"));
135 case QPlatformTheme::StartDragDistance:
136 return QVariant(gtkSetting<gint>(propertyName: "gtk-dnd-drag-threshold"));
137 case QPlatformTheme::SystemIconThemeName:
138 return QVariant(gtkSetting(propertyName: "gtk-icon-theme-name"));
139 case QPlatformTheme::SystemIconFallbackThemeName:
140 return QVariant(gtkSetting(propertyName: "gtk-fallback-icon-theme"));
141 case QPlatformTheme::MouseCursorTheme:
142 return QVariant(gtkSetting(propertyName: "gtk-cursor-theme-name"));
143 case QPlatformTheme::MouseCursorSize: {
144 int s = gtkSetting<gint>(propertyName: "gtk-cursor-theme-size");
145 if (s > 0)
146 return QVariant(QSize(s, s));
147 return QGnomeTheme::themeHint(hint);
148 }
149 default:
150 return QGnomeTheme::themeHint(hint);
151 }
152}
153
154QString QGtk3Theme::gtkFontName() const
155{
156 QString cfgFontName = gtkSetting(propertyName: "gtk-font-name");
157 if (!cfgFontName.isEmpty())
158 return cfgFontName;
159 return QGnomeTheme::gtkFontName();
160}
161
162Qt::ColorScheme QGtk3Theme::colorScheme() const
163{
164 Q_ASSERT(m_storage);
165 return m_storage->colorScheme();
166}
167
168bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const
169{
170 switch (type) {
171 case ColorDialog:
172 return true;
173 case FileDialog:
174 return useNativeFileDialog();
175 case FontDialog:
176 return true;
177 default:
178 return false;
179 }
180}
181
182QPlatformDialogHelper *QGtk3Theme::createPlatformDialogHelper(DialogType type) const
183{
184 switch (type) {
185 case ColorDialog:
186 return new QGtk3ColorDialogHelper;
187 case FileDialog:
188 if (!useNativeFileDialog())
189 return nullptr;
190 return new QGtk3FileDialogHelper;
191 case FontDialog:
192 return new QGtk3FontDialogHelper;
193 default:
194 return nullptr;
195 }
196}
197
198bool QGtk3Theme::useNativeFileDialog()
199{
200 /* Require GTK3 >= 3.15.5 to avoid running into this bug:
201 * https://bugzilla.gnome.org/show_bug.cgi?id=725164
202 *
203 * While this bug only occurs when using widget-based file dialogs
204 * (native GTK3 dialogs are fine) we have to disable platform file
205 * dialogs entirely since we can't avoid creation of a platform
206 * dialog helper.
207 */
208 return gtk_check_version(required_major: 3, required_minor: 15, required_micro: 5) == nullptr;
209}
210
211const QPalette *QGtk3Theme::palette(Palette type) const
212{
213 Q_ASSERT(m_storage);
214 return m_storage->palette(type);
215}
216
217QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
218{
219 Q_ASSERT(m_storage);
220 return m_storage->standardPixmap(standardPixmap: sp, size);
221}
222
223const QFont *QGtk3Theme::font(Font type) const
224{
225 Q_ASSERT(m_storage);
226 return m_storage->font(type);
227}
228
229QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo,
230 QPlatformTheme::IconOptions iconOptions) const
231{
232 Q_UNUSED(iconOptions);
233 Q_ASSERT(m_storage);
234 return m_storage->fileIcon(fileInfo);
235}
236
237QT_END_NAMESPACE
238

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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