1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qgtk3theme.h"
41 #include "qgtk3dialoghelpers.h"
42 #include "qgtk3menu.h"
43 #include <QVariant>
44 
45 #undef signals
46 #include <gtk/gtk.h>
47 
48 #include <X11/Xlib.h>
49 
50 QT_BEGIN_NAMESPACE
51 
52 const char *QGtk3Theme::name = "gtk3";
53 
54 template <typename T>
gtkSetting(const gchar * propertyName)55 static T gtkSetting(const gchar *propertyName)
56 {
57     GtkSettings *settings = gtk_settings_get_default();
58     T value;
59     g_object_get(settings, propertyName, &value, NULL);
60     return value;
61 }
62 
gtkSetting(const gchar * propertyName)63 static QString gtkSetting(const gchar *propertyName)
64 {
65     gchararray value = gtkSetting<gchararray>(propertyName);
66     QString str = QString::fromUtf8(value);
67     g_free(value);
68     return str;
69 }
70 
gtkMessageHandler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer unused_data)71 void gtkMessageHandler(const gchar *log_domain,
72                        GLogLevelFlags log_level,
73                        const gchar *message,
74                        gpointer unused_data) {
75     /* Silence false-positive Gtk warnings (we are using Xlib to set
76      * the WM_TRANSIENT_FOR hint).
77      */
78     if (g_strcmp0(message, "GtkDialog mapped without a transient parent. "
79                            "This is discouraged.") != 0) {
80         /* For other messages, call the default handler. */
81         g_log_default_handler(log_domain, log_level, message, unused_data);
82     }
83 }
84 
QGtk3Theme()85 QGtk3Theme::QGtk3Theme()
86 {
87     // gtk_init will reset the Xlib error handler, and that causes
88     // Qt applications to quit on X errors. Therefore, we need to manually restore it.
89     int (*oldErrorHandler)(Display *, XErrorEvent *) = XSetErrorHandler(nullptr);
90 
91     gtk_init(nullptr, nullptr);
92 
93     XSetErrorHandler(oldErrorHandler);
94 
95     /* Initialize some types here so that Gtk+ does not crash when reading
96      * the treemodel for GtkFontChooser.
97      */
98     g_type_ensure(PANGO_TYPE_FONT_FAMILY);
99     g_type_ensure(PANGO_TYPE_FONT_FACE);
100 
101     /* Use our custom log handler. */
102     g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, gtkMessageHandler, nullptr);
103 }
104 
gtkGetLongPressTime()105 static inline QVariant gtkGetLongPressTime()
106 {
107     const char *gtk_long_press_time = "gtk-long-press-time";
108     static bool found = g_object_class_find_property(G_OBJECT_GET_CLASS(gtk_settings_get_default()), gtk_long_press_time);
109     if (!found)
110         return QVariant();
111     return QVariant(gtkSetting<guint>(gtk_long_press_time));  // Since 3.14, apparently we support >= 3.6
112 }
113 
themeHint(QPlatformTheme::ThemeHint hint) const114 QVariant QGtk3Theme::themeHint(QPlatformTheme::ThemeHint hint) const
115 {
116     switch (hint) {
117     case QPlatformTheme::CursorFlashTime:
118         return QVariant(gtkSetting<gint>("gtk-cursor-blink-time"));
119     case QPlatformTheme::MouseDoubleClickDistance:
120         return QVariant(gtkSetting<gint>("gtk-double-click-distance"));
121     case QPlatformTheme::MouseDoubleClickInterval:
122         return QVariant(gtkSetting<gint>("gtk-double-click-time"));
123     case QPlatformTheme::MousePressAndHoldInterval: {
124         QVariant v = gtkGetLongPressTime();
125         if (!v.isValid())
126             v = QGnomeTheme::themeHint(hint);
127         return v;
128     }
129     case QPlatformTheme::PasswordMaskDelay:
130         return QVariant(gtkSetting<guint>("gtk-entry-password-hint-timeout"));
131     case QPlatformTheme::StartDragDistance:
132         return QVariant(gtkSetting<gint>("gtk-dnd-drag-threshold"));
133     case QPlatformTheme::SystemIconThemeName:
134         return QVariant(gtkSetting("gtk-icon-theme-name"));
135     case QPlatformTheme::SystemIconFallbackThemeName:
136         return QVariant(gtkSetting("gtk-fallback-icon-theme"));
137     default:
138         return QGnomeTheme::themeHint(hint);
139     }
140 }
141 
gtkFontName() const142 QString QGtk3Theme::gtkFontName() const
143 {
144     QString cfgFontName = gtkSetting("gtk-font-name");
145     if (!cfgFontName.isEmpty())
146         return cfgFontName;
147     return QGnomeTheme::gtkFontName();
148 }
149 
usePlatformNativeDialog(DialogType type) const150 bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const
151 {
152     switch (type) {
153     case ColorDialog:
154         return true;
155     case FileDialog:
156         return useNativeFileDialog();
157     case FontDialog:
158         return true;
159     default:
160         return false;
161     }
162 }
163 
createPlatformDialogHelper(DialogType type) const164 QPlatformDialogHelper *QGtk3Theme::createPlatformDialogHelper(DialogType type) const
165 {
166     switch (type) {
167     case ColorDialog:
168         return new QGtk3ColorDialogHelper;
169     case FileDialog:
170         if (!useNativeFileDialog())
171             return nullptr;
172         return new QGtk3FileDialogHelper;
173     case FontDialog:
174         return new QGtk3FontDialogHelper;
175     default:
176         return nullptr;
177     }
178 }
179 
createPlatformMenu() const180 QPlatformMenu* QGtk3Theme::createPlatformMenu() const
181 {
182     return new QGtk3Menu;
183 }
184 
createPlatformMenuItem() const185 QPlatformMenuItem* QGtk3Theme::createPlatformMenuItem() const
186 {
187     return new QGtk3MenuItem;
188 }
189 
useNativeFileDialog()190 bool QGtk3Theme::useNativeFileDialog()
191 {
192     /* Require GTK3 >= 3.15.5 to avoid running into this bug:
193      * https://bugzilla.gnome.org/show_bug.cgi?id=725164
194      *
195      * While this bug only occurs when using widget-based file dialogs
196      * (native GTK3 dialogs are fine) we have to disable platform file
197      * dialogs entirely since we can't avoid creation of a platform
198      * dialog helper.
199      */
200     return gtk_check_version(3, 15, 5) == nullptr;
201 }
202 
203 QT_END_NAMESPACE
204