1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/settings.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Modified by: Mart Raudsepp (GetMetric)
6 // Copyright:   (c) 1998 Robert Roebling
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12 
13 #include "wx/settings.h"
14 
15 #ifndef WX_PRECOMP
16     #include "wx/toplevel.h"
17     #include "wx/module.h"
18 #endif
19 
20 #include "wx/fontutil.h"
21 #include "wx/fontenum.h"
22 
23 #include "wx/gtk/private/wrapgtk.h"
24 #include "wx/gtk/private/gtk3-compat.h"
25 #include "wx/gtk/private/win_gtk.h"
26 #include "wx/gtk/private/stylecontext.h"
27 #include "wx/gtk/private/value.h"
28 
29 bool wxGetFrameExtents(GdkWindow* window, int* left, int* right, int* top, int* bottom);
30 
31 // ----------------------------------------------------------------------------
32 // wxSystemSettings implementation
33 // ----------------------------------------------------------------------------
34 
35 static wxFont gs_fontSystem;
36 static int gs_scrollWidth;
37 static GtkWidget* gs_tlw_parent;
38 
ContainerWidget()39 static GtkContainer* ContainerWidget()
40 {
41     static GtkContainer* s_widget;
42     if (s_widget == NULL)
43     {
44         s_widget = GTK_CONTAINER(gtk_fixed_new());
45         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
46         gs_tlw_parent = gtk_window_new(GTK_WINDOW_TOPLEVEL);
47         gtk_container_add(GTK_CONTAINER(gs_tlw_parent), GTK_WIDGET(s_widget));
48     }
49     return s_widget;
50 }
51 
ScrollBarWidget()52 static GtkWidget* ScrollBarWidget()
53 {
54     static GtkWidget* s_widget;
55     if (s_widget == NULL)
56     {
57         s_widget = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, NULL);
58         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
59         gtk_container_add(ContainerWidget(), s_widget);
60 #ifndef __WXGTK3__
61         gtk_widget_ensure_style(s_widget);
62 #endif
63     }
64     return s_widget;
65 }
66 
67 #ifndef __WXGTK3__
68 
69 extern "C" {
style_set(GtkWidget *,GtkStyle *,void *)70 static void style_set(GtkWidget*, GtkStyle*, void*)
71 {
72     gs_fontSystem = wxNullFont;
73     gs_scrollWidth = 0;
74 }
75 }
76 
ButtonWidget()77 static GtkWidget* ButtonWidget()
78 {
79     static GtkWidget* s_widget;
80     if (s_widget == NULL)
81     {
82         s_widget = gtk_button_new();
83         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
84         gtk_container_add(ContainerWidget(), s_widget);
85         gtk_widget_ensure_style(s_widget);
86         g_signal_connect(s_widget, "style_set", G_CALLBACK(style_set), NULL);
87     }
88     return s_widget;
89 }
90 
ListWidget()91 static GtkWidget* ListWidget()
92 {
93     static GtkWidget* s_widget;
94     if (s_widget == NULL)
95     {
96         s_widget = gtk_tree_view_new_with_model(
97             GTK_TREE_MODEL(gtk_list_store_new(1, G_TYPE_INT)));
98         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
99         gtk_container_add(ContainerWidget(), s_widget);
100         gtk_widget_ensure_style(s_widget);
101     }
102     return s_widget;
103 }
104 
TextCtrlWidget()105 static GtkWidget* TextCtrlWidget()
106 {
107     static GtkWidget* s_widget;
108     if (s_widget == NULL)
109     {
110         s_widget = gtk_text_view_new();
111         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
112         gtk_container_add(ContainerWidget(), s_widget);
113         gtk_widget_ensure_style(s_widget);
114     }
115     return s_widget;
116 }
117 
MenuItemWidget()118 static GtkWidget* MenuItemWidget()
119 {
120     static GtkWidget* s_widget;
121     if (s_widget == NULL)
122     {
123         s_widget = gtk_menu_item_new();
124         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
125         gtk_container_add(ContainerWidget(), s_widget);
126         gtk_widget_ensure_style(s_widget);
127     }
128     return s_widget;
129 }
130 
MenuBarWidget()131 static GtkWidget* MenuBarWidget()
132 {
133     static GtkWidget* s_widget;
134     if (s_widget == NULL)
135     {
136         s_widget = gtk_menu_bar_new();
137         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
138         gtk_container_add(ContainerWidget(), s_widget);
139         gtk_widget_ensure_style(s_widget);
140     }
141     return s_widget;
142 }
143 
ToolTipWidget()144 static GtkWidget* ToolTipWidget()
145 {
146     static GtkWidget* s_widget;
147     if (s_widget == NULL)
148     {
149         s_widget = gtk_window_new(GTK_WINDOW_POPUP);
150         g_object_add_weak_pointer(G_OBJECT(s_widget), (void**)&s_widget);
151         g_signal_connect_swapped(ContainerWidget(), "destroy",
152             G_CALLBACK(gtk_widget_destroy), s_widget);
153         const char* name = "gtk-tooltip";
154         if (!wx_is_at_least_gtk2(11))
155             name = "gtk-tooltips";
156         gtk_widget_set_name(s_widget, name);
157         gtk_widget_ensure_style(s_widget);
158     }
159     return s_widget;
160 }
161 #endif // !__WXGTK3__
162 
163 #ifdef __WXGTK3__
164 
165 #if !GTK_CHECK_VERSION(3,12,0)
166     #define GTK_STATE_FLAG_LINK (1 << 9)
167 #endif
168 
169 static wxColour gs_systemColorCache[wxSYS_COLOUR_MAX + 1];
170 
171 extern "C" {
notify_gtk_theme_name(GObject *,GParamSpec *,void *)172 static void notify_gtk_theme_name(GObject*, GParamSpec*, void*)
173 {
174     gs_fontSystem.UnRef();
175     gs_scrollWidth = 0;
176     for (int i = wxSYS_COLOUR_MAX; i--;)
177         gs_systemColorCache[i].UnRef();
178 }
179 
notify_gtk_font_name(GObject *,GParamSpec *,void *)180 static void notify_gtk_font_name(GObject*, GParamSpec*, void*)
181 {
182     gs_fontSystem.UnRef();
183 }
184 }
185 
186 // Some notes on using GtkStyleContext. Style information from a context
187 // attached to a non-visible GtkWidget is not accurate. The context has an
188 // internal visibility state, controlled by the widget, which it presumably
189 // uses to avoid doing unnecessary work. Creating a new style context from the
190 // GtkWidgetPath in a context attached to a widget also does not work. The path
191 // does not accurately reproduce the context state with older versions of GTK+,
192 // and there is no context hierarchy (parent contexts). The hierarchy of parent
193 // contexts is necessary, even though it would seem that the widget path has
194 // the same hierarchy in it. So the best way to get style information seems
195 // to be creating the widget paths and context hierarchy directly.
196 
197 //-----------------------------------------------------------------------------
198 
199 class wxGtkWidgetPath
200 {
201 public:
wxGtkWidgetPath()202     wxGtkWidgetPath() : m_path(gtk_widget_path_new()) { }
~wxGtkWidgetPath()203     ~wxGtkWidgetPath() { gtk_widget_path_free(m_path); }
operator GtkWidgetPath*()204     operator GtkWidgetPath*() { return m_path; }
205 private:
206     GtkWidgetPath* const m_path;
207 };
208 
209 //-----------------------------------------------------------------------------
210 // wxGtkStyleContext
211 //-----------------------------------------------------------------------------
212 
wxGtkStyleContext(double scale)213 wxGtkStyleContext::wxGtkStyleContext(double scale)
214     : m_path(gtk_widget_path_new())
215     , m_scale(int(scale))
216 {
217     m_context = NULL;
218 }
219 
Add(GType type,const char * objectName,...)220 wxGtkStyleContext& wxGtkStyleContext::Add(GType type, const char* objectName, ...)
221 {
222     if (m_context == NULL && type != GTK_TYPE_WINDOW)
223         AddWindow();
224 
225     gtk_widget_path_append_type(m_path, type);
226 #if GTK_CHECK_VERSION(3,20,0)
227     if (gtk_check_version(3,20,0) == NULL)
228         gtk_widget_path_iter_set_object_name(m_path, -1, objectName);
229 #endif
230     va_list args;
231     va_start(args, objectName);
232     const char* className;
233     while ((className = va_arg(args, char*)))
234         gtk_widget_path_iter_add_class(m_path, -1, className);
235     va_end(args);
236 
237     GtkStyleContext* sc = gtk_style_context_new();
238 #if GTK_CHECK_VERSION(3,10,0)
239     if (gtk_check_version(3,10,0) == NULL)
240         gtk_style_context_set_scale(sc, m_scale);
241 #endif
242     gtk_style_context_set_path(sc, m_path);
243     if (m_context)
244     {
245 #if GTK_CHECK_VERSION(3,4,0)
246         if (gtk_check_version(3,4,0) == NULL)
247             gtk_style_context_set_parent(sc, m_context);
248 #endif
249         g_object_unref(m_context);
250     }
251     m_context = sc;
252     return *this;
253 }
254 
Add(const char * objectName)255 wxGtkStyleContext& wxGtkStyleContext::Add(const char* objectName)
256 {
257     return Add(G_TYPE_NONE, objectName, NULL);
258 }
259 
~wxGtkStyleContext()260 wxGtkStyleContext::~wxGtkStyleContext()
261 {
262     gtk_widget_path_free(m_path);
263     if (m_context == NULL)
264         return;
265     if (gtk_check_version(3,16,0) == NULL || gtk_check_version(3,4,0))
266     {
267         g_object_unref(m_context);
268         return;
269     }
270 #if GTK_CHECK_VERSION(3,4,0)
271     // GTK+ < 3.16 does not properly handle freeing child context before parent
272     GtkStyleContext* sc = m_context;
273     do {
274         GtkStyleContext* parent = gtk_style_context_get_parent(sc);
275         if (parent)
276         {
277             g_object_ref(parent);
278             gtk_style_context_set_parent(sc, NULL);
279         }
280         g_object_unref(sc);
281         sc = parent;
282     } while (sc);
283 #endif
284 }
285 
AddButton()286 wxGtkStyleContext& wxGtkStyleContext::AddButton()
287 {
288     return Add(GTK_TYPE_BUTTON, "button", "button", NULL);
289 }
290 
AddCheckButton()291 wxGtkStyleContext& wxGtkStyleContext::AddCheckButton()
292 {
293     return Add(GTK_TYPE_CHECK_BUTTON, "checkbutton", NULL);
294 }
295 
296 #if GTK_CHECK_VERSION(3,10,0)
AddHeaderbar()297 wxGtkStyleContext& wxGtkStyleContext::AddHeaderbar()
298 {
299     return Add(GTK_TYPE_HEADER_BAR, "headerbar", "titlebar", "header-bar", NULL);
300 }
301 #endif
302 
AddLabel()303 wxGtkStyleContext& wxGtkStyleContext::AddLabel()
304 {
305     return Add(GTK_TYPE_LABEL, "label", NULL);
306 }
307 
AddMenu()308 wxGtkStyleContext& wxGtkStyleContext::AddMenu()
309 {
310     return AddWindow("popup").Add(GTK_TYPE_MENU, "menu", "menu", NULL);
311 }
312 
AddMenuItem()313 wxGtkStyleContext& wxGtkStyleContext::AddMenuItem()
314 {
315     return AddMenu().Add(GTK_TYPE_MENU_ITEM, "menuitem", "menuitem", NULL);
316 }
317 
AddTextview(const char * child1,const char * child2)318 wxGtkStyleContext& wxGtkStyleContext::AddTextview(const char* child1, const char* child2)
319 {
320     Add(GTK_TYPE_TEXT_VIEW, "textview", "view", NULL);
321     if (child1 && gtk_check_version(3,20,0) == NULL)
322     {
323         Add(child1);
324         if (child2)
325             Add(child2);
326     }
327     return *this;
328 }
329 
AddTreeview()330 wxGtkStyleContext& wxGtkStyleContext::AddTreeview()
331 {
332     return Add(GTK_TYPE_TREE_VIEW, "treeview", "view", NULL);
333 }
334 
335 #if GTK_CHECK_VERSION(3,20,0)
AddTreeviewHeaderButton(int pos)336 wxGtkStyleContext& wxGtkStyleContext::AddTreeviewHeaderButton(int pos)
337 {
338     AddTreeview().Add("header");
339     GtkStyleContext* sc = gtk_style_context_new();
340 
341     wxGtkWidgetPath siblings;
342     gtk_widget_path_append_type(siblings, GTK_TYPE_BUTTON);
343     gtk_widget_path_iter_set_object_name(siblings, -1, "button");
344     gtk_widget_path_append_type(siblings, GTK_TYPE_BUTTON);
345     gtk_widget_path_iter_set_object_name(siblings, -1, "button");
346     gtk_widget_path_append_type(siblings, GTK_TYPE_BUTTON);
347     gtk_widget_path_iter_set_object_name(siblings, -1, "button");
348 
349     gtk_widget_path_append_with_siblings(m_path, siblings, pos);
350 
351     gtk_style_context_set_path(sc, m_path);
352     gtk_style_context_set_parent(sc, m_context);
353     g_object_unref(m_context);
354     m_context = sc;
355     return *this;
356 }
357 #endif // GTK_CHECK_VERSION(3,20,0)
358 
AddTooltip()359 wxGtkStyleContext& wxGtkStyleContext::AddTooltip()
360 {
361     wxASSERT(m_context == NULL);
362     GtkWidgetPath* path = m_path;
363     gtk_widget_path_append_type(path, GTK_TYPE_WINDOW);
364 #if GTK_CHECK_VERSION(3,20,0)
365     if (gtk_check_version(3,20,0) == NULL)
366         gtk_widget_path_iter_set_object_name(path, -1, "tooltip");
367 #endif
368     gtk_widget_path_iter_add_class(path, -1, "background");
369     gtk_widget_path_iter_add_class(path, -1, "tooltip");
370     gtk_widget_path_iter_set_name(path, -1, "gtk-tooltip");
371     m_context = gtk_style_context_new();
372     gtk_style_context_set_path(m_context, m_path);
373     return *this;
374 }
375 
AddWindow(const char * className2)376 wxGtkStyleContext& wxGtkStyleContext::AddWindow(const char* className2)
377 {
378     return Add(GTK_TYPE_WINDOW, "window", "background", className2, NULL);
379 }
380 
Bg(wxColour & color,int state) const381 void wxGtkStyleContext::Bg(wxColour& color, int state) const
382 {
383     GdkRGBA* rgba;
384     cairo_pattern_t* pattern = NULL;
385     gtk_style_context_set_state(m_context, GtkStateFlags(state));
386     gtk_style_context_get(m_context, GtkStateFlags(state),
387         "background-color", &rgba, "background-image", &pattern, NULL);
388     color = wxColour(*rgba);
389     gdk_rgba_free(rgba);
390 
391     // "background-image" takes precedence over "background-color".
392     // If there is an image, try to get a color out of it.
393     if (pattern)
394     {
395         if (cairo_pattern_get_type(pattern) == CAIRO_PATTERN_TYPE_SURFACE)
396         {
397             cairo_surface_t* surf;
398             cairo_pattern_get_surface(pattern, &surf);
399             if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE)
400             {
401                 const guchar* data = cairo_image_surface_get_data(surf);
402                 const int stride = cairo_image_surface_get_stride(surf);
403                 // choose a pixel in the middle vertically,
404                 // images often have a vertical gradient
405                 const int i = stride * (cairo_image_surface_get_height(surf) / 2);
406                 const unsigned* p = reinterpret_cast<const unsigned*>(data + i);
407                 const unsigned pixel = *p;
408                 guchar r, g, b, a = 0xff;
409                 switch (cairo_image_surface_get_format(surf))
410                 {
411                 case CAIRO_FORMAT_ARGB32:
412                     a = guchar(pixel >> 24);
413                     if (a == 0)
414                         break;
415                     wxFALLTHROUGH;
416                 case CAIRO_FORMAT_RGB24:
417                     r = guchar(pixel >> 16);
418                     g = guchar(pixel >> 8);
419                     b = guchar(pixel);
420                     if (a != 0xff)
421                     {
422                         // un-premultiply
423                         r = guchar((r * 0xff) / a);
424                         g = guchar((g * 0xff) / a);
425                         b = guchar((b * 0xff) / a);
426                     }
427                     color.Set(r, g, b, a);
428                     break;
429                 default:
430                     break;
431                 }
432             }
433         }
434         cairo_pattern_destroy(pattern);
435     }
436 
437     if (color.Alpha() == 0)
438     {
439         // Try TLW as last resort, but not if we're already doing it
440         if (gtk_widget_path_length(m_path) > 1)
441             wxGtkStyleContext().AddWindow().Bg(color, state);
442     }
443 }
444 
Fg(wxColour & color,int state) const445 void wxGtkStyleContext::Fg(wxColour& color, int state) const
446 {
447     GdkRGBA rgba;
448     gtk_style_context_set_state(m_context, GtkStateFlags(state));
449 #ifdef __WXGTK4__
450     gtk_style_context_get_color(m_context, &rgba);
451 #else
452     gtk_style_context_get_color(m_context, GtkStateFlags(state), &rgba);
453 #endif
454     color = wxColour(rgba);
455 }
456 
Border(wxColour & color) const457 void wxGtkStyleContext::Border(wxColour& color) const
458 {
459     GdkRGBA* rgba;
460     gtk_style_context_get(m_context, GTK_STATE_FLAG_NORMAL, "border-color", &rgba, NULL);
461     color = wxColour(*rgba);
462     gdk_rgba_free(rgba);
463 }
464 
465 //-----------------------------------------------------------------------------
466 
GetColour(wxSystemColour index)467 wxColour wxSystemSettingsNative::GetColour(wxSystemColour index)
468 {
469     if (unsigned(index) > wxSYS_COLOUR_MAX)
470         index = wxSYS_COLOUR_MAX;
471 
472     wxColour& color = gs_systemColorCache[index];
473     if (color.IsOk())
474         return color;
475 
476     static bool once;
477     if (!once)
478     {
479         once = true;
480         g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name",
481             G_CALLBACK(notify_gtk_theme_name), NULL);
482     }
483 
484     wxGtkStyleContext sc;
485 
486     switch (index)
487     {
488     case wxSYS_COLOUR_ACTIVECAPTION:
489     case wxSYS_COLOUR_INACTIVECAPTION:
490     case wxSYS_COLOUR_GRADIENTACTIVECAPTION:
491     case wxSYS_COLOUR_GRADIENTINACTIVECAPTION:
492 #if GTK_CHECK_VERSION(3,10,0)
493         if (gtk_check_version(3,10,0) == NULL)
494         {
495             int state = GTK_STATE_FLAG_NORMAL;
496             if (index == wxSYS_COLOUR_INACTIVECAPTION ||
497                 index == wxSYS_COLOUR_GRADIENTINACTIVECAPTION)
498             {
499                 state = GTK_STATE_FLAG_BACKDROP;
500             }
501             sc.AddHeaderbar().Bg(color, state);
502             break;
503         }
504         wxFALLTHROUGH;
505 #endif
506     case wxSYS_COLOUR_3DLIGHT:
507     case wxSYS_COLOUR_ACTIVEBORDER:
508     case wxSYS_COLOUR_BTNFACE:
509     case wxSYS_COLOUR_DESKTOP:
510     case wxSYS_COLOUR_INACTIVEBORDER:
511     case wxSYS_COLOUR_SCROLLBAR:
512     case wxSYS_COLOUR_WINDOWFRAME:
513         sc.AddButton().Bg(color);
514         break;
515     case wxSYS_COLOUR_HIGHLIGHT:
516         sc.AddTextview("text", "selection");
517         sc.Bg(color, GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED);
518         break;
519     case wxSYS_COLOUR_HIGHLIGHTTEXT:
520         sc.AddTextview("text", "selection");
521         sc.Fg(color, GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED);
522         break;
523     case wxSYS_COLOUR_WINDOWTEXT:
524         sc.AddTextview("text").Fg(color);
525         break;
526     case wxSYS_COLOUR_BTNHIGHLIGHT:
527         sc.AddButton().Bg(color, GTK_STATE_FLAG_PRELIGHT);
528         break;
529     case wxSYS_COLOUR_BTNSHADOW:
530         sc.AddButton().Border(color);
531         break;
532     case wxSYS_COLOUR_CAPTIONTEXT:
533 #if GTK_CHECK_VERSION(3,10,0)
534         if (gtk_check_version(3,10,0) == NULL)
535         {
536             sc.AddHeaderbar().AddLabel().Fg(color);
537             break;
538         }
539         wxFALLTHROUGH;
540 #endif
541     case wxSYS_COLOUR_BTNTEXT:
542         sc.AddButton().AddLabel().Fg(color);
543         break;
544     case wxSYS_COLOUR_INACTIVECAPTIONTEXT:
545 #if GTK_CHECK_VERSION(3,10,0)
546         if (gtk_check_version(3,10,0) == NULL)
547         {
548             sc.AddHeaderbar().AddLabel().Fg(color, GTK_STATE_FLAG_BACKDROP);
549             break;
550         }
551         wxFALLTHROUGH;
552 #endif
553     case wxSYS_COLOUR_GRAYTEXT:
554         sc.AddLabel().Fg(color, GTK_STATE_FLAG_INSENSITIVE);
555         break;
556     case wxSYS_COLOUR_HOTLIGHT:
557         sc.Add(GTK_TYPE_LINK_BUTTON, "button", "link", NULL);
558         if (wx_is_at_least_gtk3(12))
559             sc.Fg(color, GTK_STATE_FLAG_LINK);
560 #ifndef __WXGTK4__
561         else
562         {
563             wxGCC_WARNING_SUPPRESS(deprecated-declarations)
564             wxGtkValue value( GDK_TYPE_COLOR);
565             gtk_style_context_get_style_property(sc, "link-color", value);
566             GdkColor* link_color = static_cast<GdkColor*>(g_value_get_boxed(value));
567             GdkColor gdkColor = { 0, 0, 0, 0xeeee };
568             if (link_color)
569                 gdkColor = *link_color;
570             color = wxColour(gdkColor);
571             wxGCC_WARNING_RESTORE()
572         }
573 #endif
574         break;
575     case wxSYS_COLOUR_INFOBK:
576         sc.AddTooltip().Bg(color);
577         break;
578     case wxSYS_COLOUR_INFOTEXT:
579         sc.AddTooltip().AddLabel().Fg(color);
580         break;
581     case wxSYS_COLOUR_LISTBOX:
582         sc.AddTreeview().Bg(color);
583         break;
584     case wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT:
585         sc.AddTreeview().Fg(color, GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED);
586         break;
587     case wxSYS_COLOUR_LISTBOXTEXT:
588         sc.AddTreeview().Fg(color);
589         break;
590     case wxSYS_COLOUR_MENU:
591         sc.AddMenu().Bg(color);
592         break;
593     case wxSYS_COLOUR_MENUBAR:
594         sc.Add(GTK_TYPE_MENU_BAR, "menubar", "menubar", NULL).Bg(color);
595         break;
596     case wxSYS_COLOUR_MENUHILIGHT:
597         sc.AddMenuItem().Bg(color, GTK_STATE_FLAG_PRELIGHT);
598         break;
599     case wxSYS_COLOUR_MENUTEXT:
600         sc.AddMenuItem().AddLabel().Fg(color);
601         break;
602     case wxSYS_COLOUR_APPWORKSPACE:
603     case wxSYS_COLOUR_WINDOW:
604         sc.AddTextview().Bg(color);
605         break;
606     case wxSYS_COLOUR_3DDKSHADOW:
607         color.Set(0, 0, 0);
608         break;
609     default:
610         wxFAIL_MSG("invalid system colour index");
611         color.Set(0, 0, 0, 0);
612         break;
613     }
614 
615     return color;
616 }
617 #else // !__WXGTK3__
ButtonStyle()618 static const GtkStyle* ButtonStyle()
619 {
620     return gtk_widget_get_style(ButtonWidget());
621 }
622 
ListStyle()623 static const GtkStyle* ListStyle()
624 {
625     return gtk_widget_get_style(ListWidget());
626 }
627 
TextCtrlStyle()628 static const GtkStyle* TextCtrlStyle()
629 {
630     return gtk_widget_get_style(TextCtrlWidget());
631 }
632 
MenuItemStyle()633 static const GtkStyle* MenuItemStyle()
634 {
635     return gtk_widget_get_style(MenuItemWidget());
636 }
637 
MenuBarStyle()638 static const GtkStyle* MenuBarStyle()
639 {
640     return gtk_widget_get_style(MenuBarWidget());
641 }
642 
ToolTipStyle()643 static const GtkStyle* ToolTipStyle()
644 {
645     return gtk_widget_get_style(ToolTipWidget());
646 }
647 
GetColour(wxSystemColour index)648 wxColour wxSystemSettingsNative::GetColour( wxSystemColour index )
649 {
650     wxColor color;
651     switch (index)
652     {
653         case wxSYS_COLOUR_SCROLLBAR:
654         case wxSYS_COLOUR_BACKGROUND:
655         //case wxSYS_COLOUR_DESKTOP:
656         case wxSYS_COLOUR_INACTIVECAPTION:
657         case wxSYS_COLOUR_GRADIENTINACTIVECAPTION:
658         case wxSYS_COLOUR_MENU:
659         case wxSYS_COLOUR_WINDOWFRAME:
660         case wxSYS_COLOUR_ACTIVEBORDER:
661         case wxSYS_COLOUR_INACTIVEBORDER:
662         case wxSYS_COLOUR_BTNFACE:
663         //case wxSYS_COLOUR_3DFACE:
664         case wxSYS_COLOUR_3DLIGHT:
665             color = wxColor(ButtonStyle()->bg[GTK_STATE_NORMAL]);
666             break;
667 
668         case wxSYS_COLOUR_WINDOW:
669             color = wxColor(TextCtrlStyle()->base[GTK_STATE_NORMAL]);
670             break;
671 
672         case wxSYS_COLOUR_MENUBAR:
673             color = wxColor(MenuBarStyle()->bg[GTK_STATE_NORMAL]);
674             break;
675 
676         case wxSYS_COLOUR_3DDKSHADOW:
677             color = *wxBLACK;
678             break;
679 
680         case wxSYS_COLOUR_GRAYTEXT:
681         case wxSYS_COLOUR_BTNSHADOW:
682         //case wxSYS_COLOUR_3DSHADOW:
683             {
684                 wxColour faceColour(GetColour(wxSYS_COLOUR_3DFACE));
685                 color =
686                    wxColour((unsigned char) (faceColour.Red() * 2 / 3),
687                             (unsigned char) (faceColour.Green() * 2 / 3),
688                             (unsigned char) (faceColour.Blue() * 2 / 3));
689             }
690             break;
691 
692         case wxSYS_COLOUR_BTNHIGHLIGHT:
693         //case wxSYS_COLOUR_BTNHILIGHT:
694         //case wxSYS_COLOUR_3DHIGHLIGHT:
695         //case wxSYS_COLOUR_3DHILIGHT:
696             color = *wxWHITE;
697             break;
698 
699         case wxSYS_COLOUR_HIGHLIGHT:
700             color = wxColor(ButtonStyle()->bg[GTK_STATE_SELECTED]);
701             break;
702 
703         case wxSYS_COLOUR_LISTBOX:
704             color = wxColor(ListStyle()->base[GTK_STATE_NORMAL]);
705             break;
706 
707         case wxSYS_COLOUR_LISTBOXTEXT:
708             color = wxColor(ListStyle()->text[GTK_STATE_NORMAL]);
709             break;
710 
711         case wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT:
712             // This is for the text in a list control (or tree) when the
713             // item is selected, but not focused
714             color = wxColor(ListStyle()->text[GTK_STATE_ACTIVE]);
715             break;
716 
717         case wxSYS_COLOUR_MENUTEXT:
718         case wxSYS_COLOUR_WINDOWTEXT:
719         case wxSYS_COLOUR_CAPTIONTEXT:
720         case wxSYS_COLOUR_INACTIVECAPTIONTEXT:
721         case wxSYS_COLOUR_BTNTEXT:
722             color = wxColor(ButtonStyle()->fg[GTK_STATE_NORMAL]);
723             break;
724 
725         case wxSYS_COLOUR_INFOBK:
726             color = wxColor(ToolTipStyle()->bg[GTK_STATE_NORMAL]);
727             break;
728 
729         case wxSYS_COLOUR_INFOTEXT:
730             color = wxColor(ToolTipStyle()->fg[GTK_STATE_NORMAL]);
731             break;
732 
733         case wxSYS_COLOUR_HIGHLIGHTTEXT:
734             color = wxColor(ButtonStyle()->fg[GTK_STATE_SELECTED]);
735             break;
736 
737         case wxSYS_COLOUR_APPWORKSPACE:
738             color = *wxWHITE;    // ?
739             break;
740 
741         case wxSYS_COLOUR_ACTIVECAPTION:
742         case wxSYS_COLOUR_GRADIENTACTIVECAPTION:
743         case wxSYS_COLOUR_MENUHILIGHT:
744             color = wxColor(MenuItemStyle()->bg[GTK_STATE_SELECTED]);
745             break;
746 
747         case wxSYS_COLOUR_HOTLIGHT:
748             {
749                 GdkColor c = { 0, 0, 0, 0xeeee };
750                 if (gtk_check_version(2,10,0) == NULL)
751                 {
752                     GdkColor* linkColor = NULL;
753                     gtk_widget_style_get(ButtonWidget(), "link-color", &linkColor, NULL);
754                     if (linkColor)
755                     {
756                         c = *linkColor;
757                         gdk_color_free(linkColor);
758                     }
759                 }
760                 color = wxColour(c);
761             }
762             break;
763 
764         case wxSYS_COLOUR_MAX:
765         default:
766             wxFAIL_MSG( wxT("unknown system colour index") );
767             color = *wxWHITE;
768             break;
769     }
770 
771     wxASSERT(color.IsOk());
772     return color;
773 }
774 #endif // !__WXGTK3__
775 
GetFont(wxSystemFont index)776 wxFont wxSystemSettingsNative::GetFont( wxSystemFont index )
777 {
778     wxFont font;
779     switch (index)
780     {
781         case wxSYS_OEM_FIXED_FONT:
782         case wxSYS_ANSI_FIXED_FONT:
783         case wxSYS_SYSTEM_FIXED_FONT:
784             font = *wxNORMAL_FONT;
785             break;
786 
787         case wxSYS_ANSI_VAR_FONT:
788         case wxSYS_SYSTEM_FONT:
789         case wxSYS_DEVICE_DEFAULT_FONT:
790         case wxSYS_DEFAULT_GUI_FONT:
791             if (!gs_fontSystem.IsOk())
792             {
793                 wxNativeFontInfo info;
794 #ifdef __WXGTK3__
795                 static bool once;
796                 if (!once)
797                 {
798                     once = true;
799                     g_signal_connect(gtk_settings_get_default(), "notify::gtk-font-name",
800                         G_CALLBACK(notify_gtk_font_name), NULL);
801                 }
802                 ContainerWidget();
803                 int scale = 1;
804 #if GTK_CHECK_VERSION(3,10,0)
805                 if (wx_is_at_least_gtk3(10))
806                     scale = gtk_widget_get_scale_factor(gs_tlw_parent);
807 #endif
808                 wxGtkStyleContext sc(scale);
809                 sc.AddButton().AddLabel();
810                 gtk_style_context_get(sc, GTK_STATE_FLAG_NORMAL,
811                     GTK_STYLE_PROPERTY_FONT, &info.description, NULL);
812 #else
813                 info.description = ButtonStyle()->font_desc;
814 #endif
815                 gs_fontSystem = wxFont(info);
816 
817 #if wxUSE_FONTENUM
818                 // (try to) heal the default font (on some common systems e.g. Ubuntu
819                 // it's "Sans Serif" but the real font is called "Sans"):
820                 if (!wxFontEnumerator::IsValidFacename(gs_fontSystem.GetFaceName()) &&
821                     gs_fontSystem.GetFaceName() == "Sans Serif")
822                 {
823                     gs_fontSystem.SetFaceName("Sans");
824                 }
825 #endif // wxUSE_FONTENUM
826 
827 #ifndef __WXGTK3__
828                 info.description = NULL;
829 #endif
830             }
831             font = gs_fontSystem;
832             break;
833 
834         default:
835             break;
836     }
837 
838     wxASSERT( font.IsOk() );
839 
840     return font;
841 }
842 
843 // helper: return the GtkSettings either for the screen the current window is
844 // on or for the default screen if window is NULL
GetSettingsForWindowScreen(GdkWindow * window)845 static GtkSettings *GetSettingsForWindowScreen(GdkWindow *window)
846 {
847     return window ? gtk_settings_get_for_screen(gdk_window_get_screen(window))
848                   : gtk_settings_get_default();
849 }
850 
GetBorderWidth(wxSystemMetric index,const wxWindow * win)851 static int GetBorderWidth(wxSystemMetric index, const wxWindow* win)
852 {
853     if (win->m_wxwindow)
854     {
855         wxPizza* pizza = WX_PIZZA(win->m_wxwindow);
856         GtkBorder border;
857         pizza->get_border(border);
858         switch (index)
859         {
860             case wxSYS_BORDER_X:
861             case wxSYS_EDGE_X:
862             case wxSYS_FRAMESIZE_X:
863                 return border.left;
864             default:
865                 return border.top;
866         }
867     }
868     return -1;
869 }
870 
871 #ifdef __WXGTK4__
GetMonitorGeom(GdkWindow * window)872 static GdkRectangle GetMonitorGeom(GdkWindow* window)
873 {
874     GdkMonitor* monitor;
875     if (window)
876         monitor = gdk_display_get_monitor_at_window(gdk_window_get_display(window), window);
877     else
878         monitor = gdk_display_get_primary_monitor(gdk_display_get_default());
879     GdkRectangle rect;
880     gdk_monitor_get_geometry(monitor, &rect);
881     return rect;
882 }
883 #endif
884 
GetScrollbarWidth()885 static int GetScrollbarWidth()
886 {
887     int width;
888 #ifdef __WXGTK3__
889     if (wx_is_at_least_gtk3(20))
890     {
891         GtkBorder border;
892 #if GTK_CHECK_VERSION(3,10,0)
893         wxGtkStyleContext sc(gtk_widget_get_scale_factor(ScrollBarWidget()));
894 #else
895         wxGtkStyleContext sc;
896 #endif
897         sc.Add(GTK_TYPE_SCROLLBAR, "scrollbar", "scrollbar", "vertical", "right", NULL);
898 
899         gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
900 
901         sc.Add("contents").Add("trough").Add("slider");
902 
903         gtk_style_context_get(sc, GTK_STATE_FLAG_NORMAL, "min-width", &width, NULL);
904         width += border.left + border.right;
905 
906         gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
907         width += border.left + border.right;
908         gtk_style_context_get_padding(sc, GTK_STATE_FLAG_NORMAL, &border);
909         width += border.left + border.right;
910         gtk_style_context_get_margin(sc, GTK_STATE_FLAG_NORMAL, &border);
911         width += border.left + border.right;
912     }
913     else
914 #endif
915     {
916         int slider_width, trough_border;
917         gtk_widget_style_get(ScrollBarWidget(),
918             "slider-width", &slider_width, "trough-border", &trough_border, NULL);
919         width = slider_width + (2 * trough_border);
920     }
921     return width;
922 }
923 
GetMetric(wxSystemMetric index,const wxWindow * win)924 int wxSystemSettingsNative::GetMetric( wxSystemMetric index, const wxWindow* win )
925 {
926     GdkWindow *window = NULL;
927     if (win)
928         window = gtk_widget_get_window(win->GetHandle());
929 
930     switch (index)
931     {
932         case wxSYS_BORDER_X:
933         case wxSYS_BORDER_Y:
934         case wxSYS_EDGE_X:
935         case wxSYS_EDGE_Y:
936         case wxSYS_FRAMESIZE_X:
937         case wxSYS_FRAMESIZE_Y:
938             if (win)
939             {
940                 wxTopLevelWindow *tlw = wxDynamicCast(win, wxTopLevelWindow);
941                 if (!tlw)
942                     return GetBorderWidth(index, win);
943                 else if (window)
944                 {
945                     // Get the frame extents from the windowmanager.
946                     // In most cases the top extent is the titlebar, so we use the bottom extent
947                     // for the heights.
948                     int right, bottom;
949                     if (wxGetFrameExtents(window, NULL, &right, NULL, &bottom))
950                     {
951                         switch (index)
952                         {
953                             case wxSYS_BORDER_X:
954                             case wxSYS_EDGE_X:
955                             case wxSYS_FRAMESIZE_X:
956                                 return right; // width of right extent
957                             default:
958                                 return bottom; // height of bottom extent
959                         }
960                     }
961                 }
962             }
963 
964             return -1; // no window specified
965 
966         case wxSYS_CURSOR_X:
967         case wxSYS_CURSOR_Y:
968                 return gdk_display_get_default_cursor_size(
969                             window ? gdk_window_get_display(window)
970                                    : gdk_display_get_default());
971 
972         case wxSYS_DCLICK_X:
973         case wxSYS_DCLICK_Y:
974             gint dclick_distance;
975             g_object_get(GetSettingsForWindowScreen(window),
976                             "gtk-double-click-distance", &dclick_distance, NULL);
977 
978             return dclick_distance * 2;
979 
980         case wxSYS_DCLICK_MSEC:
981             gint dclick;
982             g_object_get(GetSettingsForWindowScreen(window),
983                             "gtk-double-click-time", &dclick, NULL);
984             return dclick;
985 
986         case wxSYS_CARET_ON_MSEC:
987         case wxSYS_CARET_OFF_MSEC:
988             {
989                 gboolean should_blink = true;
990                 gint blink_time = -1;
991                 g_object_get(GetSettingsForWindowScreen(window),
992                                 "gtk-cursor-blink", &should_blink,
993                                 "gtk-cursor-blink-time", &blink_time,
994                                 NULL);
995                 if (!should_blink)
996                     return 0;
997 
998                 if (blink_time > 0)
999                     return blink_time / 2;
1000 
1001                 return -1;
1002             }
1003 
1004         case wxSYS_CARET_TIMEOUT_MSEC:
1005             {
1006                 gboolean should_blink = true;
1007                 gint timeout = 0;
1008                 g_object_get(GetSettingsForWindowScreen(window),
1009                                 "gtk-cursor-blink", &should_blink,
1010                                 "gtk-cursor-blink-timeout", &timeout,
1011                                 NULL);
1012                 if (!should_blink)
1013                     return 0;
1014 
1015                 // GTK+ returns this value in seconds, not milliseconds,
1016                 // Special value of 2147483647 means that the cursor never
1017                 // blinks and we handle any value that would overflow int after
1018                 // multiplication in the same manner as it looks quite
1019                 // unnecessary to support cursor blinking once a month.
1020                 if (timeout > 0 && timeout < 2147483647 / 1000)
1021                     return timeout * 1000;
1022 
1023                 return -1;  // no timeout, blink forever
1024             }
1025 
1026         case wxSYS_DRAG_X:
1027         case wxSYS_DRAG_Y:
1028             gint drag_threshold;
1029             g_object_get(GetSettingsForWindowScreen(window),
1030                             "gtk-dnd-drag-threshold", &drag_threshold, NULL);
1031 
1032             // The correct thing here would be to double the value
1033             // since that is what the API wants. But the values
1034             // are much bigger under GNOME than under Windows and
1035             // just seem to much in many cases to be useful.
1036             // drag_threshold *= 2;
1037 
1038             return drag_threshold;
1039 
1040         case wxSYS_ICON_X:
1041         case wxSYS_ICON_Y:
1042             return 32;
1043 
1044         case wxSYS_SCREEN_X:
1045 #ifdef __WXGTK4__
1046             return GetMonitorGeom(window).width;
1047 #else
1048             wxGCC_WARNING_SUPPRESS(deprecated-declarations)
1049             if (window)
1050                 return gdk_screen_get_width(gdk_window_get_screen(window));
1051             else
1052                 return gdk_screen_width();
1053             wxGCC_WARNING_RESTORE()
1054 #endif
1055 
1056         case wxSYS_SCREEN_Y:
1057 #ifdef __WXGTK4__
1058             return GetMonitorGeom(window).height;
1059 #else
1060             wxGCC_WARNING_SUPPRESS(deprecated-declarations)
1061             if (window)
1062                 return gdk_screen_get_height(gdk_window_get_screen(window));
1063             else
1064                 return gdk_screen_height();
1065             wxGCC_WARNING_RESTORE()
1066 #endif
1067 
1068         case wxSYS_HSCROLL_Y:
1069         case wxSYS_VSCROLL_X:
1070             if (gs_scrollWidth == 0)
1071                 gs_scrollWidth = GetScrollbarWidth();
1072             return gs_scrollWidth;
1073 
1074         case wxSYS_CAPTION_Y:
1075             if (!window)
1076                 // No realized window specified, and no implementation for that case yet.
1077                 return -1;
1078 
1079             wxASSERT_MSG( wxDynamicCast(win, wxTopLevelWindow),
1080                           wxT("Asking for caption height of a non toplevel window") );
1081 
1082             // Get the height of the top windowmanager border.
1083             // This is the titlebar in most cases. The titlebar might be elsewhere, and
1084             // we could check which is the thickest wm border to decide on which side the
1085             // titlebar is, but this might lead to interesting behaviours in used code.
1086             // Reconsider when we have a way to report to the user on which side it is.
1087             {
1088                 int top;
1089                 if (wxGetFrameExtents(window, NULL, NULL, &top, NULL))
1090                 {
1091                     return top; // top frame extent
1092                 }
1093             }
1094 
1095             // Try a default approach without a window pointer, if possible
1096             // ...
1097 
1098             return -1;
1099 
1100         case wxSYS_PENWINDOWS_PRESENT:
1101             // No MS Windows for Pen computing extension available in X11 based gtk+.
1102             return 0;
1103 
1104         default:
1105             return -1;   // metric is unknown
1106     }
1107 }
1108 
HasFeature(wxSystemFeature index)1109 bool wxSystemSettingsNative::HasFeature(wxSystemFeature index)
1110 {
1111     switch (index)
1112     {
1113         case wxSYS_CAN_ICONIZE_FRAME:
1114             return false;
1115 
1116         case wxSYS_CAN_DRAW_FRAME_DECORATIONS:
1117             return true;
1118 
1119         default:
1120             return false;
1121     }
1122 }
1123 
1124 class wxSystemSettingsModule: public wxModule
1125 {
1126 public:
OnInit()1127     virtual bool OnInit() wxOVERRIDE { return true; }
1128     virtual void OnExit() wxOVERRIDE;
1129     wxDECLARE_DYNAMIC_CLASS(wxSystemSettingsModule);
1130 };
1131 wxIMPLEMENT_DYNAMIC_CLASS(wxSystemSettingsModule, wxModule);
1132 
OnExit()1133 void wxSystemSettingsModule::OnExit()
1134 {
1135 #ifdef __WXGTK3__
1136     GtkSettings* settings = gtk_settings_get_default();
1137     if (settings)
1138     {
1139         g_signal_handlers_disconnect_by_func(settings,
1140             (void*)notify_gtk_theme_name, NULL);
1141         g_signal_handlers_disconnect_by_func(settings,
1142             (void*)notify_gtk_font_name, NULL);
1143     }
1144 #endif
1145     if (gs_tlw_parent)
1146     {
1147         gtk_widget_destroy(gs_tlw_parent);
1148         gs_tlw_parent = NULL;
1149     }
1150 }
1151