1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/gtk/settings.cpp
3// Purpose:
4// Author:      Robert Roebling
5// Modified by: Mart Raudsepp (GetMetric)
6// Id:          $Id: settings.cpp 67017 2011-02-25 09:37:28Z JS $
7// Copyright:   (c) 1998 Robert Roebling
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11// For compilers that support precompilation, includes "wx.h".
12#include "wx/wxprec.h"
13
14#include "wx/settings.h"
15
16#ifndef WX_PRECOMP
17    #include "wx/cmndata.h"
18    #include "wx/toplevel.h"
19#endif
20
21#include "wx/fontutil.h"
22
23#include <gtk/gtkversion.h>
24#if GTK_CHECK_VERSION(2, 9, 0)
25    // gtk_object_sink
26    #undef GTK_DISABLE_DEPRECATED
27#endif
28#include <gtk/gtk.h>
29#include <gdk/gdkx.h>
30
31#include <X11/Xatom.h>
32
33// ----------------------------------------------------------------------------
34// wxSystemObjects
35// ----------------------------------------------------------------------------
36
37struct wxSystemObjects
38{
39    wxColour m_colBtnFace,
40             m_colBtnShadow,
41             m_colBtnHighlight,
42             m_colHighlight,
43             m_colHighlightText,
44             m_colListBox,
45             m_colWindow,
46             m_colBtnText,
47             m_colMenuItemHighlight,
48             m_colTooltip,
49             m_colTooltipText,
50             m_colMenubarBg,
51             m_colListBoxText,
52             m_colListBoxUnfocusedText;
53
54    wxFont m_fontSystem;
55};
56
57static wxSystemObjects gs_objects;
58
59void wxClearGtkSystemObjects()
60{
61    gs_objects.m_colBtnFace = wxColour();
62    gs_objects.m_colBtnShadow = wxColour();
63    gs_objects.m_colBtnHighlight = wxColour();
64    gs_objects.m_colHighlightText = wxColour();
65    gs_objects.m_colListBox = wxColour();
66    gs_objects.m_colWindow = wxColour();
67    gs_objects.m_colBtnText = wxColour();
68    gs_objects.m_colMenuItemHighlight = wxColour();
69    gs_objects.m_colTooltip = wxColour();
70    gs_objects.m_colTooltipText = wxColour();
71    gs_objects.m_colMenubarBg = wxColour();
72    gs_objects.m_fontSystem = wxNullFont;
73    gs_objects.m_colListBoxText = wxColour();
74}
75
76// ----------------------------------------------------------------------------
77// wxSystemSettings implementation
78// ----------------------------------------------------------------------------
79
80// kind of widget to use in GetColourFromGTKWidget
81enum wxGtkWidgetType
82{
83    wxGTK_BUTTON,
84    wxGTK_LIST,
85    wxGTK_MENUITEM,
86    wxGTK_TEXTCTRL,
87    wxGTK_MENUBAR,
88};
89
90// the colour we need
91enum wxGtkColourType
92{
93    wxGTK_FG,
94    wxGTK_BG,
95    wxGTK_BASE,
96    wxGTK_TEXT
97};
98
99// wxSystemSettings::GetColour() helper: get the colours from a GTK+
100// widget style, return true if we did get them
101static bool GetColourFromGTKWidget(GdkColor& gdkColor,
102                                   wxGtkWidgetType type = wxGTK_BUTTON,
103                                   GtkStateType state = GTK_STATE_NORMAL,
104                                   wxGtkColourType colour = wxGTK_BG)
105{
106    GtkWidget *widget;
107    switch ( type )
108    {
109        default:
110            wxFAIL_MSG( _T("unexpected GTK widget type") );
111            // fall through
112
113        case wxGTK_BUTTON:
114            widget = gtk_button_new();
115            break;
116
117        case wxGTK_TEXTCTRL:
118            widget = gtk_text_view_new();
119            break;
120
121        case wxGTK_LIST:
122            widget = gtk_tree_view_new_with_model(
123                (GtkTreeModel*)gtk_list_store_new(1, G_TYPE_INT));
124            break;
125
126        case wxGTK_MENUITEM:
127            widget = gtk_menu_item_new();
128            break;
129
130        case wxGTK_MENUBAR:
131            widget = gtk_menu_bar_new();
132            break;
133    }
134
135    GtkStyle *def = gtk_rc_get_style( widget );
136    if ( !def )
137        def = gtk_widget_get_default_style();
138
139    const bool ok = def != NULL;
140    if (ok)
141    {
142        switch ( colour )
143        {
144            default:
145                wxFAIL_MSG( _T("unexpected GTK colour type") );
146                // fall through
147
148            case wxGTK_FG:
149                gdkColor = def->fg[state];
150                break;
151
152            case wxGTK_BG:
153                gdkColor = def->bg[state];
154                break;
155
156            case wxGTK_BASE:
157                gdkColor = def->base[state];
158                break;
159
160            case wxGTK_TEXT:
161                gdkColor = def->text[state];
162                break;
163        }
164    }
165
166    gtk_object_sink((GtkObject*)widget);
167
168    return ok;
169}
170
171static void GetTooltipColors()
172{
173    GtkWidget* widget = gtk_window_new(GTK_WINDOW_POPUP);
174    const char* name = "gtk-tooltip";
175    if (gtk_check_version(2, 11, 0))
176        name = "gtk-tooltips";
177    gtk_widget_set_name(widget, name);
178    gtk_widget_ensure_style(widget);
179
180    GdkColor c = widget->style->bg[GTK_STATE_NORMAL];
181    gs_objects.m_colTooltip = wxColor(c);
182    c = widget->style->fg[GTK_STATE_NORMAL];
183    gs_objects.m_colTooltipText = wxColor(c);
184
185    gtk_widget_destroy(widget);
186}
187
188wxColour wxSystemSettingsNative::GetColour( wxSystemColour index )
189{
190    wxColor color;
191    GdkColor gdkColor;
192    switch (index)
193    {
194        case wxSYS_COLOUR_SCROLLBAR:
195        case wxSYS_COLOUR_BACKGROUND:
196        case wxSYS_COLOUR_INACTIVECAPTION:
197        case wxSYS_COLOUR_MENU:
198        case wxSYS_COLOUR_WINDOWFRAME:
199        case wxSYS_COLOUR_ACTIVEBORDER:
200        case wxSYS_COLOUR_INACTIVEBORDER:
201        case wxSYS_COLOUR_BTNFACE:
202        case wxSYS_COLOUR_3DLIGHT:
203            if (!gs_objects.m_colBtnFace.Ok())
204            {
205                gdkColor.red =
206                gdkColor.green = 0;
207                gdkColor.blue = 0x9c40;
208                GetColourFromGTKWidget(gdkColor);
209                gs_objects.m_colBtnFace = wxColor(gdkColor);
210            }
211            color = gs_objects.m_colBtnFace;
212            break;
213
214        case wxSYS_COLOUR_WINDOW:
215            if (!gs_objects.m_colWindow.Ok())
216            {
217                gdkColor.red =
218                gdkColor.green =
219                gdkColor.blue = 0xFFFF;
220                GetColourFromGTKWidget(gdkColor, wxGTK_TEXTCTRL, GTK_STATE_NORMAL, wxGTK_BASE);
221                gs_objects.m_colWindow = wxColor(gdkColor);
222            }
223            color = gs_objects.m_colWindow;
224            break;
225
226
227        case wxSYS_COLOUR_MENUBAR:
228            if (!gs_objects.m_colMenubarBg.Ok())
229            {
230                gdkColor.red =
231                gdkColor.green = 0;
232                gdkColor.blue = 0x9c40;
233                GetColourFromGTKWidget(gdkColor,wxGTK_MENUBAR);
234                gs_objects.m_colMenubarBg = wxColor(gdkColor);
235            }
236            color = gs_objects.m_colMenubarBg;
237            break;
238
239        case wxSYS_COLOUR_3DDKSHADOW:
240            color = *wxBLACK;
241            break;
242
243        case wxSYS_COLOUR_GRAYTEXT:
244        case wxSYS_COLOUR_BTNSHADOW:
245        //case wxSYS_COLOUR_3DSHADOW:
246            if (!gs_objects.m_colBtnShadow.Ok())
247            {
248                wxColour faceColour(GetColour(wxSYS_COLOUR_3DFACE));
249                gs_objects.m_colBtnShadow =
250                   wxColour((unsigned char) (faceColour.Red() * 2 / 3),
251                            (unsigned char) (faceColour.Green() * 2 / 3),
252                            (unsigned char) (faceColour.Blue() * 2 / 3));
253            }
254            color = gs_objects.m_colBtnShadow;
255            break;
256
257        case wxSYS_COLOUR_3DHIGHLIGHT:
258        //case wxSYS_COLOUR_BTNHIGHLIGHT:
259            color = *wxWHITE;
260            break;
261
262        case wxSYS_COLOUR_HIGHLIGHT:
263            if (!gs_objects.m_colHighlight.Ok())
264            {
265                gdkColor.red =
266                gdkColor.green = 0;
267                gdkColor.blue = 0x9c40;
268                GetColourFromGTKWidget(
269                    gdkColor, wxGTK_BUTTON, GTK_STATE_SELECTED);
270                gs_objects.m_colHighlight = wxColour(gdkColor);
271            }
272            color = gs_objects.m_colHighlight;
273            break;
274
275        case wxSYS_COLOUR_LISTBOX:
276            if (!gs_objects.m_colListBox.Ok())
277            {
278                if ( GetColourFromGTKWidget(gdkColor,
279                                            wxGTK_LIST,
280                                            GTK_STATE_NORMAL,
281                                            wxGTK_BASE) )
282                {
283                    gs_objects.m_colListBox = wxColour(gdkColor);
284                }
285                else
286                {
287                    gs_objects.m_colListBox = *wxWHITE;
288                }
289            }
290            color = gs_objects.m_colListBox;
291            break;
292
293        case wxSYS_COLOUR_LISTBOXTEXT:
294            if (!gs_objects.m_colListBoxText.Ok())
295            {
296                if ( GetColourFromGTKWidget(gdkColor,
297                                            wxGTK_LIST,
298                                            GTK_STATE_NORMAL,
299                                            wxGTK_TEXT) )
300                {
301                    gs_objects.m_colListBoxText = wxColour(gdkColor);
302                }
303                else
304                {
305                    gs_objects.m_colListBoxText = GetColour(wxSYS_COLOUR_WINDOWTEXT);
306                }
307            }
308            color = gs_objects.m_colListBoxText;
309            break;
310
311        case wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT:
312        {
313            // This is for the text in a list control (or tree) when the
314            // item is selected, but not focused
315            if (!gs_objects.m_colListBoxUnfocusedText.Ok())
316            {
317                if (GetColourFromGTKWidget(gdkColor, wxGTK_LIST, GTK_STATE_ACTIVE, wxGTK_TEXT))
318                    gs_objects.m_colListBoxUnfocusedText = wxColour(gdkColor);
319                else
320                    gs_objects.m_colListBoxUnfocusedText = GetColour(wxSYS_COLOUR_WINDOWTEXT);
321            }
322            color = gs_objects.m_colListBoxUnfocusedText;
323            break;
324        }
325        case wxSYS_COLOUR_MENUTEXT:
326        case wxSYS_COLOUR_WINDOWTEXT:
327        case wxSYS_COLOUR_CAPTIONTEXT:
328        case wxSYS_COLOUR_INACTIVECAPTIONTEXT:
329        case wxSYS_COLOUR_BTNTEXT:
330            if (!gs_objects.m_colBtnText.Ok())
331            {
332                gdkColor.red =
333                gdkColor.green =
334                gdkColor.blue = 0;
335                GetColourFromGTKWidget(
336                    gdkColor, wxGTK_BUTTON, GTK_STATE_NORMAL, wxGTK_FG);
337                gs_objects.m_colBtnText = wxColour(gdkColor);
338            }
339            color = gs_objects.m_colBtnText;
340            break;
341
342        case wxSYS_COLOUR_INFOBK:
343            if (!gs_objects.m_colTooltip.Ok()) {
344                GetTooltipColors();
345            }
346            color = gs_objects.m_colTooltip;
347            break;
348
349        case wxSYS_COLOUR_INFOTEXT:
350            if (!gs_objects.m_colTooltipText.Ok()) {
351                GetTooltipColors();
352            }
353            color = gs_objects.m_colTooltipText;
354            break;
355
356        case wxSYS_COLOUR_HIGHLIGHTTEXT:
357            if (!gs_objects.m_colHighlightText.Ok())
358            {
359                gdkColor.red =
360                gdkColor.green =
361                gdkColor.blue = 0;
362                GetColourFromGTKWidget(
363                    gdkColor, wxGTK_BUTTON, GTK_STATE_SELECTED, wxGTK_FG);
364                gs_objects.m_colHighlightText = wxColour(gdkColor);
365            }
366            color = gs_objects.m_colHighlightText;
367            break;
368
369        case wxSYS_COLOUR_APPWORKSPACE:
370            color = *wxWHITE;    // ?
371            break;
372
373        case wxSYS_COLOUR_ACTIVECAPTION:
374        case wxSYS_COLOUR_MENUHILIGHT:
375            if (!gs_objects.m_colMenuItemHighlight.Ok())
376            {
377                gdkColor.red =
378                gdkColor.green =
379                gdkColor.blue = 0;
380                GetColourFromGTKWidget(
381                    gdkColor, wxGTK_MENUITEM, GTK_STATE_SELECTED, wxGTK_BG);
382                gs_objects.m_colMenuItemHighlight = wxColour(gdkColor);
383            }
384            color = gs_objects.m_colMenuItemHighlight;
385            break;
386
387        case wxSYS_COLOUR_HOTLIGHT:
388        case wxSYS_COLOUR_GRADIENTACTIVECAPTION:
389        case wxSYS_COLOUR_GRADIENTINACTIVECAPTION:
390            // TODO
391            color = *wxBLACK;
392            break;
393
394        case wxSYS_COLOUR_MAX:
395        default:
396            wxFAIL_MSG( _T("unknown system colour index") );
397            color = *wxWHITE;
398            break;
399    }
400
401    return color;
402}
403
404wxFont wxSystemSettingsNative::GetFont( wxSystemFont index )
405{
406    wxFont font;
407    switch (index)
408    {
409        case wxSYS_OEM_FIXED_FONT:
410        case wxSYS_ANSI_FIXED_FONT:
411        case wxSYS_SYSTEM_FIXED_FONT:
412            font = *wxNORMAL_FONT;
413            break;
414
415        case wxSYS_ANSI_VAR_FONT:
416        case wxSYS_SYSTEM_FONT:
417        case wxSYS_DEVICE_DEFAULT_FONT:
418        case wxSYS_DEFAULT_GUI_FONT:
419            if (!gs_objects.m_fontSystem.Ok())
420            {
421                GtkWidget *widget = gtk_button_new();
422                GtkStyle *def = gtk_rc_get_style( widget );
423                if ( !def || !def->font_desc )
424                    def = gtk_widget_get_default_style();
425                if ( def && def->font_desc )
426                {
427                    wxNativeFontInfo info;
428                    info.description =
429                        pango_font_description_copy(def->font_desc);
430                    gs_objects.m_fontSystem = wxFont(info);
431                }
432                else
433                {
434                    GtkSettings *settings = gtk_settings_get_default();
435                    gchar *font_name = NULL;
436                    g_object_get ( settings,
437                                   "gtk-font-name",
438                                   &font_name,
439                                   NULL);
440                    if (!font_name)
441                        gs_objects.m_fontSystem = wxFont( 12, wxSWISS, wxNORMAL, wxNORMAL );
442                    else
443                        gs_objects.m_fontSystem = wxFont(wxString::FromAscii(font_name));
444                    g_free (font_name);
445                }
446                gtk_object_sink((GtkObject*)widget);
447            }
448            font = gs_objects.m_fontSystem;
449            break;
450
451        default:
452            break;
453    }
454    return font;
455}
456
457static bool wxXGetWindowProperty(GdkWindow* window, Atom& type, int& format, gulong& nitems, guchar*& data)
458{
459    bool success = false;
460#if GTK_CHECK_VERSION(2, 2, 0)
461    if (gtk_check_version(2, 2, 0) == NULL)
462    {
463        gulong bytes_after;
464        success = XGetWindowProperty(
465            GDK_DISPLAY_XDISPLAY(gdk_drawable_get_display(window)),
466            GDK_WINDOW_XWINDOW(window),
467            gdk_x11_get_xatom_by_name_for_display(
468                gdk_drawable_get_display(window),
469                "_NET_FRAME_EXTENTS"),
470            0, // left, right, top, bottom, CARDINAL[4]/32
471            G_MAXLONG, // size of long
472            false, // do not delete property
473            XA_CARDINAL, // 32 bit
474            &type, &format, &nitems, &bytes_after, &data
475            ) == Success;
476    }
477#endif
478    return success;
479}
480
481int wxSystemSettingsNative::GetMetric( wxSystemMetric index, wxWindow* win )
482{
483    guchar *data = NULL;
484    Atom type;
485    int format;
486    gulong nitems;
487    GdkWindow *window = NULL;
488    if(win && GTK_WIDGET_REALIZED(win->GetHandle()))
489        window = win->GetHandle()->window;
490
491    switch (index)
492    {
493        case wxSYS_BORDER_X:
494        case wxSYS_BORDER_Y:
495        case wxSYS_EDGE_X:
496        case wxSYS_EDGE_Y:
497        case wxSYS_FRAMESIZE_X:
498        case wxSYS_FRAMESIZE_Y:
499            // If a window is specified/realized, and it is a toplevel window, we can query from wm.
500            // The returned border thickness is outside the client area in that case.
501            if (window)
502            {
503                wxTopLevelWindow *tlw = wxDynamicCast(win, wxTopLevelWindow);
504                if (!tlw)
505                    return -1; // not a tlw, not sure how to approach
506                else
507                {
508                    // Check if wm supports frame extents - we can't know
509                    // the border widths if it does not.
510#if GTK_CHECK_VERSION(2,2,0)
511                    if (!gtk_check_version(2,2,0))
512                    {
513                        if (!gdk_x11_screen_supports_net_wm_hint(
514                                gdk_drawable_get_screen(window),
515                                gdk_atom_intern("_NET_FRAME_EXTENTS", false) ) )
516                            return -1;
517                    }
518                    else
519#endif
520                    {
521                        if (!gdk_net_wm_supports(gdk_atom_intern("_NET_FRAME_EXTENTS", false)))
522                            return -1;
523                    }
524
525                    // Get the frame extents from the windowmanager.
526                    // In most cases the top extent is the titlebar, so we use the bottom extent
527                    // for the heights.
528                    if (wxXGetWindowProperty(window, type, format, nitems, data))
529                    {
530                        int border_return = -1;
531
532                        if ((type == XA_CARDINAL) && (format == 32) && (nitems >= 4) && (data))
533                        {
534                            switch(index)
535                            {
536                                case wxSYS_BORDER_X:
537                                case wxSYS_EDGE_X:
538                                case wxSYS_FRAMESIZE_X:
539                                    border_return = ((long*)data)[1]; // width of right extent
540                                    break;
541                                default:
542                                    border_return = ((long*)data)[3]; // height of bottom extent
543                                    break;
544                            }
545                        }
546
547                        if (data)
548                            XFree(data);
549
550                        return border_return;
551                    }
552                }
553            }
554
555            return -1; // no window specified
556
557        case wxSYS_CURSOR_X:
558        case wxSYS_CURSOR_Y:
559#ifdef __WXGTK24__
560            if (!gtk_check_version(2,4,0))
561            {
562                if (window)
563                    return gdk_display_get_default_cursor_size(gdk_drawable_get_display(window));
564                else
565                    return gdk_display_get_default_cursor_size(gdk_display_get_default());
566            }
567            else
568#endif
569                return 16;
570
571        case wxSYS_DCLICK_X:
572        case wxSYS_DCLICK_Y:
573            gint dclick_distance;
574#if GTK_CHECK_VERSION(2,2,0)
575            if (window && !gtk_check_version(2,2,0))
576                g_object_get(gtk_settings_get_for_screen(gdk_drawable_get_screen(window)),
577                                "gtk-double-click-distance", &dclick_distance, NULL);
578            else
579#endif
580                g_object_get(gtk_settings_get_default(),
581                                "gtk-double-click-distance", &dclick_distance, NULL);
582
583            return dclick_distance * 2;
584
585        case wxSYS_DRAG_X:
586        case wxSYS_DRAG_Y:
587            gint drag_threshold;
588#if GTK_CHECK_VERSION(2,2,0)
589            if (window && !gtk_check_version(2,2,0))
590            {
591                g_object_get(
592                        gtk_settings_get_for_screen(gdk_drawable_get_screen(window)),
593                        "gtk-dnd-drag-threshold",
594                        &drag_threshold, NULL);
595            }
596            else
597#endif
598            {
599                g_object_get(gtk_settings_get_default(),
600                             "gtk-dnd-drag-threshold", &drag_threshold, NULL);
601            }
602
603            // The correct thing here would be to double the value
604            // since that is what the API wants. But the values
605            // are much bigger under GNOME than under Windows and
606            // just seem to much in many cases to be useful.
607            // drag_threshold *= 2;
608
609            return drag_threshold;
610
611        // MBN: ditto for icons
612        case wxSYS_ICON_X:     return 32;
613        case wxSYS_ICON_Y:     return 32;
614
615        case wxSYS_SCREEN_X:
616#if GTK_CHECK_VERSION(2,2,0)
617            if (window && !gtk_check_version(2,2,0))
618                return gdk_screen_get_width(gdk_drawable_get_screen(window));
619            else
620#endif
621                return gdk_screen_width();
622
623        case wxSYS_SCREEN_Y:
624#if GTK_CHECK_VERSION(2,2,0)
625            if (window && !gtk_check_version(2,2,0))
626                return gdk_screen_get_height(gdk_drawable_get_screen(window));
627            else
628#endif
629                return gdk_screen_height();
630
631        case wxSYS_HSCROLL_Y:  return 15;
632        case wxSYS_VSCROLL_X:  return 15;
633
634        case wxSYS_CAPTION_Y:
635            if (!window)
636                // No realized window specified, and no implementation for that case yet.
637                return -1;
638
639            // Check if wm supports frame extents - we can't know the caption height if it does not.
640#if GTK_CHECK_VERSION(2,2,0)
641            if (!gtk_check_version(2,2,0))
642            {
643                if (!gdk_x11_screen_supports_net_wm_hint(
644                        gdk_drawable_get_screen(window),
645                        gdk_atom_intern("_NET_FRAME_EXTENTS", false) ) )
646                    return -1;
647            }
648            else
649#endif
650            {
651                if (!gdk_net_wm_supports(gdk_atom_intern("_NET_FRAME_EXTENTS", false)))
652                    return -1;
653            }
654
655            wxASSERT_MSG( wxDynamicCast(win, wxTopLevelWindow),
656                          wxT("Asking for caption height of a non toplevel window") );
657
658            // Get the height of the top windowmanager border.
659            // This is the titlebar in most cases. The titlebar might be elsewhere, and
660            // we could check which is the thickest wm border to decide on which side the
661            // titlebar is, but this might lead to interesting behaviours in used code.
662            // Reconsider when we have a way to report to the user on which side it is.
663            if (wxXGetWindowProperty(window, type, format, nitems, data))
664            {
665                int caption_height = -1;
666
667                if ((type == XA_CARDINAL) && (format == 32) && (nitems >= 3) && (data))
668                {
669                    caption_height = ((long*)data)[2]; // top frame extent
670                }
671
672                if (data)
673                    XFree(data);
674
675                return caption_height;
676            }
677
678            // Try a default approach without a window pointer, if possible
679            // ...
680
681            return -1;
682
683        case wxSYS_PENWINDOWS_PRESENT:
684            // No MS Windows for Pen computing extension available in X11 based gtk+.
685            return 0;
686
687        default:
688            return -1;   // metric is unknown
689    }
690}
691
692bool wxSystemSettingsNative::HasFeature(wxSystemFeature index)
693{
694    switch (index)
695    {
696        case wxSYS_CAN_ICONIZE_FRAME:
697            return false;
698
699        case wxSYS_CAN_DRAW_FRAME_DECORATIONS:
700            return true;
701
702        default:
703            return false;
704    }
705}
706