| 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> |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 114 | 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 | |
| 142 | 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 | |
| 150 | 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 | |
| 164 | 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 | |
| 180 | QPlatformMenu* QGtk3Theme::() const |
| 181 | { |
| 182 | return new QGtk3Menu; |
| 183 | } |
| 184 | |
| 185 | QPlatformMenuItem* QGtk3Theme::() const |
| 186 | { |
| 187 | return new QGtk3MenuItem; |
| 188 | } |
| 189 | |
| 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 | |