1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/renderer.cpp
3 // Purpose:     implementation of wxRendererNative for wxGTK
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     20.07.2003
7 // Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence:     wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21 
22 
23 #include "wx/renderer.h"
24 
25 #ifndef WX_PRECOMP
26     #include "wx/window.h"
27     #include "wx/dcclient.h"
28     #include "wx/settings.h"
29     #include "wx/module.h"
30 #endif
31 
32 #include "wx/dcgraph.h"
33 #ifndef __WXGTK3__
34     #include "wx/gtk/dc.h"
35     #include "wx/gtk/private/wrapgtk.h"
36     #if wxUSE_GRAPHICS_CONTEXT && defined(GDK_WINDOWING_X11)
37         #include <gdk/gdkx.h>
38         #include <cairo-xlib.h>
39     #endif
40 #endif
41 
42 #include "wx/gtk/private.h"
43 #include "wx/gtk/private/stylecontext.h"
44 #include "wx/gtk/private/value.h"
45 
46 #if defined(__WXGTK3__) && !GTK_CHECK_VERSION(3,14,0)
47     #define GTK_STATE_FLAG_CHECKED (1 << 11)
48 #endif
49 
50 // ----------------------------------------------------------------------------
51 // wxRendererGTK: our wxRendererNative implementation
52 // ----------------------------------------------------------------------------
53 
54 class WXDLLEXPORT wxRendererGTK : public wxDelegateRendererNative
55 {
56 public:
57     // draw the header control button (used by wxListCtrl)
58     virtual int  DrawHeaderButton(wxWindow *win,
59                                   wxDC& dc,
60                                   const wxRect& rect,
61                                   int flags = 0,
62                                   wxHeaderSortIconType sortArrow = wxHDR_SORT_ICON_NONE,
63                                   wxHeaderButtonParams* params = NULL) wxOVERRIDE;
64 
65     virtual int GetHeaderButtonHeight(wxWindow *win) wxOVERRIDE;
66 
67     virtual int GetHeaderButtonMargin(wxWindow *win) wxOVERRIDE;
68 
69 
70     // draw the expanded/collapsed icon for a tree control item
71     virtual void DrawTreeItemButton(wxWindow *win,
72                                     wxDC& dc,
73                                     const wxRect& rect,
74                                     int flags = 0) wxOVERRIDE;
75 
76     virtual void DrawSplitterBorder(wxWindow *win,
77                                     wxDC& dc,
78                                     const wxRect& rect,
79                                     int flags = 0) wxOVERRIDE;
80     virtual void DrawSplitterSash(wxWindow *win,
81                                   wxDC& dc,
82                                   const wxSize& size,
83                                   wxCoord position,
84                                   wxOrientation orient,
85                                   int flags = 0) wxOVERRIDE;
86 
87     virtual void DrawComboBoxDropButton(wxWindow *win,
88                                         wxDC& dc,
89                                         const wxRect& rect,
90                                         int flags = 0) wxOVERRIDE;
91 
92     virtual void DrawDropArrow(wxWindow *win,
93                                wxDC& dc,
94                                const wxRect& rect,
95                                int flags = 0) wxOVERRIDE;
96 
97     virtual void DrawCheckBox(wxWindow *win,
98                               wxDC& dc,
99                               const wxRect& rect,
100                               int flags = 0) wxOVERRIDE;
101 
102     virtual void DrawPushButton(wxWindow *win,
103                                 wxDC& dc,
104                                 const wxRect& rect,
105                                 int flags = 0) wxOVERRIDE;
106 
107     virtual void DrawItemSelectionRect(wxWindow *win,
108                                        wxDC& dc,
109                                        const wxRect& rect,
110                                        int flags = 0) wxOVERRIDE;
111 
112     virtual void DrawChoice(wxWindow* win,
113                             wxDC& dc,
114                             const wxRect& rect,
115                             int flags=0) wxOVERRIDE;
116 
117     virtual void DrawComboBox(wxWindow* win,
118                                 wxDC& dc,
119                                 const wxRect& rect,
120                                 int flags=0) wxOVERRIDE;
121 
122     virtual void DrawTextCtrl(wxWindow* win,
123                                 wxDC& dc,
124                                 const wxRect& rect,
125                                 int flags=0) wxOVERRIDE;
126 
127     virtual void DrawRadioBitmap(wxWindow* win,
128                                 wxDC& dc,
129                                 const wxRect& rect,
130                                 int flags=0) wxOVERRIDE;
131 
132     virtual void DrawFocusRect(wxWindow* win, wxDC& dc, const wxRect& rect, int flags = 0) wxOVERRIDE;
133 
134     virtual wxSize GetCheckBoxSize(wxWindow *win, int flags = 0) wxOVERRIDE;
135 
136     virtual wxSplitterRenderParams GetSplitterParams(const wxWindow *win) wxOVERRIDE;
137 };
138 
139 // ============================================================================
140 // implementation
141 // ============================================================================
142 
143 /* static */
GetDefault()144 wxRendererNative& wxRendererNative::GetDefault()
145 {
146     static wxRendererGTK s_rendererGTK;
147 
148     return s_rendererGTK;
149 }
150 
151 #ifdef __WXGTK3__
152 #define NULL_RECT
153 typedef cairo_t wxGTKDrawable;
154 
wxGetGTKDrawable(const wxDC & dc)155 static cairo_t* wxGetGTKDrawable(const wxDC& dc)
156 {
157     wxGraphicsContext* gc = dc.GetGraphicsContext();
158     wxCHECK_MSG(gc, NULL, "cannot use wxRendererNative on wxDC of this type");
159     return static_cast<cairo_t*>(gc->GetNativeContext());
160 }
161 
162 static const GtkStateFlags stateTypeToFlags[] = {
163     GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_ACTIVE, GTK_STATE_FLAG_PRELIGHT,
164     GTK_STATE_FLAG_SELECTED, GTK_STATE_FLAG_INSENSITIVE, GTK_STATE_FLAG_INCONSISTENT,
165     GTK_STATE_FLAG_FOCUSED
166 };
167 
168 #else
169 #define NULL_RECT NULL,
170 typedef GdkWindow wxGTKDrawable;
171 
wxGetGTKDrawable(wxDC & dc)172 static GdkWindow* wxGetGTKDrawable(wxDC& dc)
173 {
174     GdkWindow* gdk_window = NULL;
175 
176 #if wxUSE_GRAPHICS_CONTEXT && defined(GDK_WINDOWING_X11)
177     cairo_t* cr = NULL;
178     wxGraphicsContext* gc = dc.GetGraphicsContext();
179     if (gc)
180         cr = static_cast<cairo_t*>(gc->GetNativeContext());
181     if (cr)
182     {
183         cairo_surface_t* surf = cairo_get_target(cr);
184         if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_XLIB)
185         {
186             gdk_window = static_cast<GdkWindow*>(
187                 gdk_xid_table_lookup(cairo_xlib_surface_get_drawable(surf)));
188         }
189     }
190     if (gdk_window == NULL)
191 #endif
192     {
193         wxDCImpl *impl = dc.GetImpl();
194         wxGTKDCImpl *gtk_impl = wxDynamicCast( impl, wxGTKDCImpl );
195         if (gtk_impl)
196             gdk_window = gtk_impl->GetGDKWindow();
197         else
198             wxFAIL_MSG("cannot use wxRendererNative on wxDC of this type");
199     }
200 
201     return gdk_window;
202 }
203 #endif
204 
205 // ----------------------------------------------------------------------------
206 // list/tree controls drawing
207 // ----------------------------------------------------------------------------
208 
209 int
DrawHeaderButton(wxWindow * win,wxDC & dc,const wxRect & rect,int flags,wxHeaderSortIconType sortArrow,wxHeaderButtonParams * params)210 wxRendererGTK::DrawHeaderButton(wxWindow *win,
211                                 wxDC& dc,
212                                 const wxRect& rect,
213                                 int flags,
214                                 wxHeaderSortIconType sortArrow,
215                                 wxHeaderButtonParams* params)
216 {
217     GtkWidget *button = wxGTKPrivate::GetHeaderButtonWidget();
218     if (flags & wxCONTROL_SPECIAL)
219         button = wxGTKPrivate::GetHeaderButtonWidgetFirst();
220     if (flags & wxCONTROL_DIRTY)
221         button = wxGTKPrivate::GetHeaderButtonWidgetLast();
222 
223     GtkStateType state = GTK_STATE_NORMAL;
224     if (flags & wxCONTROL_DISABLED)
225         state = GTK_STATE_INSENSITIVE;
226     else
227     {
228         if (flags & wxCONTROL_CURRENT)
229             state = GTK_STATE_PRELIGHT;
230     }
231 
232 #ifdef __WXGTK3__
233     cairo_t* cr = wxGetGTKDrawable(dc);
234     if (cr == NULL)
235         return 0;
236 
237     // AddTreeviewHeaderButton() is only available in 3.20 or later.
238 #if GTK_CHECK_VERSION(3,20,0)
239     if (gtk_check_version(3,20,0) == NULL)
240     {
241         int pos = 1;
242         if (flags & wxCONTROL_SPECIAL)
243             pos = 0;
244         if (flags & wxCONTROL_DIRTY)
245             pos = 2;
246 
247         wxGtkStyleContext sc(dc.GetContentScaleFactor());
248         sc.AddTreeviewHeaderButton(pos);
249 
250         gtk_style_context_set_state(sc, stateTypeToFlags[state]);
251         gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height);
252         gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height);
253     }
254     else
255 #endif // GTK >= 3.20
256     {
257         GtkStyleContext* sc = gtk_widget_get_style_context(button);
258         gtk_style_context_save(sc);
259         gtk_style_context_set_state(sc, stateTypeToFlags[state]);
260         gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height);
261         gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height);
262         gtk_style_context_restore(sc);
263     }
264 #else
265     int x_diff = 0;
266     if (win->GetLayoutDirection() == wxLayout_RightToLeft)
267         x_diff = rect.width;
268 
269     GdkWindow* gdk_window = wxGetGTKDrawable(dc);
270     gtk_paint_box
271     (
272         gtk_widget_get_style(button),
273         gdk_window,
274         state,
275         GTK_SHADOW_OUT,
276         NULL,
277         button,
278         "button",
279         dc.LogicalToDeviceX(rect.x) - x_diff, rect.y, rect.width, rect.height
280     );
281 #endif
282 
283     return DrawHeaderButtonContents(win, dc, rect, flags, sortArrow, params);
284 }
285 
GetHeaderButtonHeight(wxWindow * WXUNUSED (win))286 int wxRendererGTK::GetHeaderButtonHeight(wxWindow *WXUNUSED(win))
287 {
288     GtkWidget *button = wxGTKPrivate::GetHeaderButtonWidget();
289 
290     GtkRequisition req;
291 #ifdef __WXGTK3__
292     gtk_widget_get_preferred_height(button, NULL, &req.height);
293 #else
294     GTK_WIDGET_GET_CLASS(button)->size_request(button, &req);
295 #endif
296 
297     return req.height;
298 }
299 
GetHeaderButtonMargin(wxWindow * WXUNUSED (win))300 int wxRendererGTK::GetHeaderButtonMargin(wxWindow *WXUNUSED(win))
301 {
302     return 0; // TODO: How to determine the real margin?
303 }
304 
305 
306 // draw a ">" or "v" button
307 void
DrawTreeItemButton(wxWindow * WXUNUSED_IN_GTK3 (win),wxDC & dc,const wxRect & rect,int flags)308 wxRendererGTK::DrawTreeItemButton(wxWindow* WXUNUSED_IN_GTK3(win),
309                                   wxDC& dc, const wxRect& rect, int flags)
310 {
311     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
312     if (drawable == NULL)
313         return;
314 
315     GtkWidget *tree = wxGTKPrivate::GetTreeWidget();
316 
317 #ifdef __WXGTK3__
318     int state = GTK_STATE_FLAG_NORMAL;
319     if (flags & wxCONTROL_EXPANDED)
320     {
321         state = GTK_STATE_FLAG_ACTIVE;
322         if (gtk_check_version(3,14,0) == NULL)
323             state = GTK_STATE_FLAG_CHECKED;
324     }
325     if (flags & wxCONTROL_CURRENT)
326         state |= GTK_STATE_FLAG_PRELIGHT;
327 
328     int expander_size;
329     gtk_widget_style_get(tree, "expander-size", &expander_size, NULL);
330     // +1 to match GtkTreeView behavior
331     expander_size++;
332     const int x = rect.x + (rect.width - expander_size) / 2;
333     const int y = rect.y + (rect.width - expander_size) / 2;
334 
335     GtkStyleContext* sc = gtk_widget_get_style_context(tree);
336     gtk_style_context_save(sc);
337     gtk_style_context_set_state(sc, GtkStateFlags(state));
338     gtk_style_context_add_class(sc, GTK_STYLE_CLASS_EXPANDER);
339     gtk_render_expander(sc, drawable, x, y, expander_size, expander_size);
340     gtk_style_context_restore(sc);
341 #else
342     int x_diff = 0;
343     if (win->GetLayoutDirection() == wxLayout_RightToLeft)
344         x_diff = rect.width;
345 
346     GtkStateType state;
347     if ( flags & wxCONTROL_CURRENT )
348         state = GTK_STATE_PRELIGHT;
349     else
350         state = GTK_STATE_NORMAL;
351 
352     // x and y parameters specify the center of the expander
353     gtk_paint_expander
354     (
355         gtk_widget_get_style(tree),
356         drawable,
357         state,
358         NULL,
359         tree,
360         "treeview",
361         dc.LogicalToDeviceX(rect.x) + rect.width / 2 - x_diff,
362         dc.LogicalToDeviceY(rect.y) + rect.height / 2,
363         flags & wxCONTROL_EXPANDED ? GTK_EXPANDER_EXPANDED
364                                    : GTK_EXPANDER_COLLAPSED
365     );
366 #endif
367 }
368 
369 
370 // ----------------------------------------------------------------------------
371 // splitter sash drawing
372 // ----------------------------------------------------------------------------
373 
GetGtkSplitterFullSize(GtkWidget * widget)374 static int GetGtkSplitterFullSize(GtkWidget* widget)
375 {
376     gint handle_size;
377     gtk_widget_style_get(widget, "handle_size", &handle_size, NULL);
378     // Narrow handles don't work well with wxSplitterWindow
379     if (handle_size < 5)
380         handle_size = 5;
381 
382     return handle_size;
383 }
384 
385 wxSplitterRenderParams
GetSplitterParams(const wxWindow * WXUNUSED (win))386 wxRendererGTK::GetSplitterParams(const wxWindow *WXUNUSED(win))
387 {
388     // we don't draw any border, hence 0 for the second field
389     return wxSplitterRenderParams
390            (
391                GetGtkSplitterFullSize(wxGTKPrivate::GetSplitterWidget()),
392                0,
393                true     // hot sensitive
394            );
395 }
396 
397 void
DrawSplitterBorder(wxWindow * WXUNUSED (win),wxDC & WXUNUSED (dc),const wxRect & WXUNUSED (rect),int WXUNUSED (flags))398 wxRendererGTK::DrawSplitterBorder(wxWindow * WXUNUSED(win),
399                                   wxDC& WXUNUSED(dc),
400                                   const wxRect& WXUNUSED(rect),
401                                   int WXUNUSED(flags))
402 {
403     // nothing to do
404 }
405 
406 void
DrawSplitterSash(wxWindow * win,wxDC & dc,const wxSize & size,wxCoord position,wxOrientation orient,int flags)407 wxRendererGTK::DrawSplitterSash(wxWindow* win,
408                                 wxDC& dc,
409                                 const wxSize& size,
410                                 wxCoord position,
411                                 wxOrientation orient,
412                                 int flags)
413 {
414     if (gtk_widget_get_window(win->m_wxwindow) == NULL)
415     {
416         // window not realized yet
417         return;
418     }
419 
420     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
421     if (drawable == NULL)
422         return;
423 
424     // are we drawing vertical or horizontal splitter?
425     const bool isVert = orient == wxVERTICAL;
426 
427     GtkWidget* widget = wxGTKPrivate::GetSplitterWidget(orient);
428     const int full_size = GetGtkSplitterFullSize(widget);
429 
430     GdkRectangle rect;
431 
432     if ( isVert )
433     {
434         rect.x = position;
435         rect.y = 0;
436         rect.width = full_size;
437         rect.height = size.y;
438     }
439     else // horz
440     {
441         rect.x = 0;
442         rect.y = position;
443         rect.height = full_size;
444         rect.width = size.x;
445     }
446 
447 #ifdef __WXGTK3__
448     wxGtkStyleContext sc(dc.GetContentScaleFactor());
449     sc.AddWindow();
450     gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
451 
452     sc.Add(GTK_TYPE_PANED, "paned", "pane-separator", NULL);
453     if (gtk_check_version(3,20,0) == NULL)
454         sc.Add("separator");
455 
456     gtk_style_context_set_state(sc,
457         flags & wxCONTROL_CURRENT ? GTK_STATE_FLAG_PRELIGHT : GTK_STATE_FLAG_NORMAL);
458     gtk_render_handle(sc, drawable, rect.x, rect.y, rect.width, rect.height);
459 #else
460     int x_diff = 0;
461     if (win->GetLayoutDirection() == wxLayout_RightToLeft)
462         x_diff = rect.width;
463 
464     GdkWindow* gdk_window = wxGetGTKDrawable(dc);
465     if (gdk_window == NULL)
466         return;
467     gtk_paint_handle
468     (
469         gtk_widget_get_style(win->m_wxwindow),
470         gdk_window,
471         flags & wxCONTROL_CURRENT ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
472         GTK_SHADOW_NONE,
473         NULL /* no clipping */,
474         win->m_wxwindow,
475         "paned",
476         dc.LogicalToDeviceX(rect.x) - x_diff,
477         dc.LogicalToDeviceY(rect.y),
478         rect.width,
479         rect.height,
480         isVert ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL
481     );
482 #endif
483 }
484 
485 void
DrawDropArrow(wxWindow *,wxDC & dc,const wxRect & rect,int flags)486 wxRendererGTK::DrawDropArrow(wxWindow*,
487                              wxDC& dc,
488                              const wxRect& rect,
489                              int flags)
490 {
491     GtkWidget *button = wxGTKPrivate::GetButtonWidget();
492 
493     // If we give WX_PIZZA(win->m_wxwindow)->bin_window as
494     // a window for gtk_paint_xxx function, then it won't
495     // work for wxMemoryDC. So that is why we assume wxDC
496     // is wxWindowDC (wxClientDC, wxMemoryDC and wxPaintDC
497     // are derived from it) and use its m_window.
498 
499     // draw arrow so that there is even space horizontally
500     // on both sides
501     const int size = rect.width / 2;
502     const int x = rect.x + (size + 1) / 2;
503     const int y = rect.y + (rect.height - size + 1) / 2;
504 
505     GtkStateType state;
506 
507     if ( flags & wxCONTROL_PRESSED )
508         state = GTK_STATE_ACTIVE;
509     else if ( flags & wxCONTROL_DISABLED )
510         state = GTK_STATE_INSENSITIVE;
511     else if ( flags & wxCONTROL_CURRENT )
512         state = GTK_STATE_PRELIGHT;
513     else
514         state = GTK_STATE_NORMAL;
515 
516 #ifdef __WXGTK3__
517     cairo_t* cr = wxGetGTKDrawable(dc);
518     if (cr)
519     {
520         gtk_widget_set_state_flags(button, stateTypeToFlags[state], true);
521         GtkStyleContext* sc = gtk_widget_get_style_context(button);
522         gtk_render_arrow(sc, cr, G_PI, x, y, size);
523     }
524 #else
525     GdkWindow* gdk_window = wxGetGTKDrawable(dc);
526     if (gdk_window == NULL)
527         return;
528     // draw arrow on button
529     gtk_paint_arrow
530     (
531         gtk_widget_get_style(button),
532         gdk_window,
533         state,
534         flags & wxCONTROL_PRESSED ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
535         NULL,
536         button,
537         "arrow",
538         GTK_ARROW_DOWN,
539         FALSE,
540         x, y,
541         size, size
542     );
543 #endif
544 }
545 
546 void
DrawComboBoxDropButton(wxWindow * win,wxDC & dc,const wxRect & rect,int flags)547 wxRendererGTK::DrawComboBoxDropButton(wxWindow *win,
548                                       wxDC& dc,
549                                       const wxRect& rect,
550                                       int flags)
551 {
552     DrawPushButton(win,dc,rect,flags);
553     DrawDropArrow(win,dc,rect);
554 }
555 
556 // Helper used by GetCheckBoxSize() and DrawCheckBox().
557 namespace
558 {
559 
560 struct CheckBoxInfo
561 {
562 #ifdef __WXGTK3__
CheckBoxInfo__anonc4cc220c0111::CheckBoxInfo563     CheckBoxInfo(wxGtkStyleContext& sc, int flags)
564     {
565         wxUnusedVar(flags);
566 
567         sc.AddCheckButton();
568         if (gtk_check_version(3,20,0) == NULL)
569         {
570             sc.Add("check");
571             gtk_style_context_get(sc, GTK_STATE_FLAG_NORMAL,
572                                   "min-width", &indicator_width,
573                                   "min-height", &indicator_height,
574                                   NULL);
575 
576             GtkBorder border, padding;
577             gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
578             gtk_style_context_get_padding(sc, GTK_STATE_FLAG_NORMAL, &padding);
579 
580             margin_left = border.left + padding.left;
581             margin_top = border.top + padding.top;
582             margin_right = border.right + padding.right;
583             margin_bottom = border.bottom + padding.bottom;
584         }
585         else
586         {
587             wxGtkValue value( G_TYPE_INT);
588 
589             gtk_style_context_get_style_property(sc, "indicator-size", value);
590             indicator_width =
591             indicator_height = g_value_get_int(value);
592 
593             gtk_style_context_get_style_property(sc, "indicator-spacing", value);
594             margin_left =
595             margin_top =
596             margin_right =
597             margin_bottom = g_value_get_int(value);
598         }
599     }
600 #else // !__WXGTK3__
601     CheckBoxInfo(GtkWidget* button, int flags)
602     {
603         gint indicator_size, indicator_margin;
604         gtk_widget_style_get(button,
605                              "indicator_size", &indicator_size,
606                              "indicator_spacing", &indicator_margin,
607                              NULL);
608 
609         // If wxCONTROL_CELL is set then we want to get the size of wxCheckBox
610         // control to draw the check mark centered and at the same position as
611         // wxCheckBox does, so offset the check mark itself by the focus margin
612         // in the same way as gtk_real_check_button_draw_indicator() does it, see
613         // https://github.com/GNOME/gtk/blob/GTK_2_16_0/gtk/gtkcheckbutton.c#L374
614         if ( flags & wxCONTROL_CELL )
615         {
616             gint focus_width, focus_pad;
617             gtk_widget_style_get(button,
618                                  "focus-line-width", &focus_width,
619                                  "focus-padding", &focus_pad,
620                                  NULL);
621 
622             indicator_margin += focus_width + focus_pad;
623         }
624 
625         // In GTK 2 width and height are the same and so are left/right and
626         // top/bottom.
627         indicator_width =
628         indicator_height = indicator_size;
629 
630         margin_left =
631         margin_top =
632         margin_right =
633         margin_bottom = indicator_margin;
634     }
635 #endif // __WXGTK3__/!__WXGTK3__
636 
637     // Make sure we fit into the provided rectangle, eliminating margins and
638     // even reducing the size if necessary.
FitInto__anonc4cc220c0111::CheckBoxInfo639     void FitInto(const wxRect& rect)
640     {
641         if ( indicator_width > rect.width )
642         {
643             indicator_width = rect.width;
644             margin_left =
645             margin_right = 0;
646         }
647         else if ( indicator_width + margin_left + margin_right > rect.width )
648         {
649             margin_left =
650             margin_right = (rect.width - indicator_width) / 2;
651         }
652 
653         if ( indicator_height > rect.height )
654         {
655             indicator_height = rect.height;
656             margin_top =
657             margin_bottom = 0;
658         }
659         else if ( indicator_height + margin_top + margin_bottom > rect.height )
660         {
661             margin_top =
662             margin_bottom = (rect.height - indicator_height) / 2;
663         }
664     }
665 
666     gint indicator_width,
667          indicator_height;
668     gint margin_left,
669          margin_top,
670          margin_right,
671          margin_bottom;
672 };
673 
674 } // anonymous namespace
675 
676 wxSize
GetCheckBoxSize(wxWindow * win,int flags)677 wxRendererGTK::GetCheckBoxSize(wxWindow* win, int flags)
678 {
679     wxSize size;
680     // Even though we don't use the window in this implementation, still check
681     // that it's valid to avoid surprises when running the same code under the
682     // other platforms.
683     wxCHECK_MSG(win, size, "Must have a valid window");
684 
685 #ifdef __WXGTK3__
686     wxGtkStyleContext sc(win->GetContentScaleFactor());
687 
688     const CheckBoxInfo info(sc, flags);
689 #else // !__WXGTK3__
690     GtkWidget* button = wxGTKPrivate::GetCheckButtonWidget();
691 
692     const CheckBoxInfo info(button, flags);
693 #endif // __WXGTK3__/!__WXGTK3__
694 
695     size.x = info.indicator_width + info.margin_left + info.margin_right;
696     size.y = info.indicator_height + info.margin_top + info.margin_bottom;
697 
698     return size;
699 }
700 
701 void
DrawCheckBox(wxWindow *,wxDC & dc,const wxRect & rect,int flags)702 wxRendererGTK::DrawCheckBox(wxWindow*,
703                             wxDC& dc,
704                             const wxRect& rect,
705                             int flags )
706 {
707 #ifdef __WXGTK3__
708     cairo_t* cr = wxGetGTKDrawable(dc);
709     if (cr == NULL)
710         return;
711 
712     int state = GTK_STATE_FLAG_NORMAL;
713     if (flags & wxCONTROL_CHECKED)
714     {
715         state = GTK_STATE_FLAG_ACTIVE;
716         if (gtk_check_version(3,14,0) == NULL)
717             state = GTK_STATE_FLAG_CHECKED;
718     }
719     if (flags & wxCONTROL_DISABLED)
720         state |= GTK_STATE_FLAG_INSENSITIVE;
721     if (flags & wxCONTROL_UNDETERMINED)
722         state |= GTK_STATE_FLAG_INCONSISTENT;
723     if (flags & wxCONTROL_CURRENT)
724         state |= GTK_STATE_FLAG_PRELIGHT;
725 
726     wxGtkStyleContext sc(dc.GetContentScaleFactor());
727 
728     CheckBoxInfo info(sc, flags);
729     info.FitInto(rect);
730 
731     const int w = info.indicator_width + info.margin_left + info.margin_right;
732     const int h = info.indicator_height + info.margin_top + info.margin_bottom;
733 
734     int x = rect.x + (rect.width  - w) / 2;
735     int y = rect.y + (rect.height - h) / 2;
736 
737     const bool isRTL = dc.GetLayoutDirection() == wxLayout_RightToLeft;
738     if (isRTL)
739     {
740         // checkbox is not mirrored
741         cairo_save(cr);
742         cairo_scale(cr, -1, 1);
743         x = -x - w;
744     }
745 
746     if (gtk_check_version(3,20,0) == NULL)
747     {
748         gtk_style_context_set_state(sc, GtkStateFlags(state));
749         gtk_render_background(sc, cr, x, y, w, h);
750         gtk_render_frame(sc, cr, x, y, w, h);
751 
752         // check is rendered in content area
753         gtk_render_check(sc, cr,
754                          x + info.margin_left, y + info.margin_top,
755                          info.indicator_width, info.indicator_height);
756     }
757     else
758     {
759         // need save/restore for GTK+ 3.6 & 3.8
760         gtk_style_context_save(sc);
761         gtk_style_context_set_state(sc, GtkStateFlags(state));
762         gtk_render_background(sc, cr, x, y, w, h);
763         gtk_render_frame(sc, cr, x, y, w, h);
764         gtk_style_context_add_class(sc, "check");
765         gtk_render_check(sc, cr, x, y, w, h);
766         gtk_style_context_restore(sc);
767     }
768     if (isRTL)
769         cairo_restore(cr);
770 
771 #else // !__WXGTK3__
772     GtkWidget* button = wxGTKPrivate::GetCheckButtonWidget();
773 
774     CheckBoxInfo info(button, flags);
775     info.FitInto(rect);
776 
777     GtkStateType state;
778 
779     if ( flags & wxCONTROL_PRESSED )
780         state = GTK_STATE_ACTIVE;
781     else if ( flags & wxCONTROL_DISABLED )
782         state = GTK_STATE_INSENSITIVE;
783     else if ( flags & wxCONTROL_CURRENT )
784         state = GTK_STATE_PRELIGHT;
785     else
786         state = GTK_STATE_NORMAL;
787 
788     GtkShadowType shadow_type;
789 
790     if ( flags & wxCONTROL_UNDETERMINED )
791         shadow_type = GTK_SHADOW_ETCHED_IN;
792     else if ( flags & wxCONTROL_CHECKED )
793         shadow_type = GTK_SHADOW_IN;
794     else
795         shadow_type = GTK_SHADOW_OUT;
796 
797     GdkWindow* gdk_window = wxGetGTKDrawable(dc);
798     if (gdk_window == NULL)
799         return;
800 
801     gtk_paint_check
802     (
803         gtk_widget_get_style(button),
804         gdk_window,
805         state,
806         shadow_type,
807         NULL,
808         button,
809         "cellcheck",
810         dc.LogicalToDeviceX(rect.x) + info.margin_left,
811         dc.LogicalToDeviceY(rect.y) + (rect.height - info.indicator_height) / 2,
812         info.indicator_width, info.indicator_height
813     );
814 #endif // __WXGTK3__/!__WXGTK3__
815 }
816 
817 void
DrawPushButton(wxWindow *,wxDC & dc,const wxRect & rect,int flags)818 wxRendererGTK::DrawPushButton(wxWindow*,
819                               wxDC& dc,
820                               const wxRect& rect,
821                               int flags)
822 {
823     GtkWidget *button = wxGTKPrivate::GetButtonWidget();
824 
825     // draw button
826     GtkStateType state;
827 
828     if ( flags & wxCONTROL_PRESSED )
829         state = GTK_STATE_ACTIVE;
830     else if ( flags & wxCONTROL_DISABLED )
831         state = GTK_STATE_INSENSITIVE;
832     else if ( flags & wxCONTROL_CURRENT )
833         state = GTK_STATE_PRELIGHT;
834     else
835         state = GTK_STATE_NORMAL;
836 
837 #ifdef __WXGTK3__
838     cairo_t* cr = wxGetGTKDrawable(dc);
839     if (cr)
840     {
841         GtkStyleContext* sc = gtk_widget_get_style_context(button);
842         gtk_style_context_save(sc);
843         gtk_style_context_set_state(sc, stateTypeToFlags[state]);
844         gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height);
845         gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height);
846         gtk_style_context_restore(sc);
847     }
848 #else
849     GdkWindow* gdk_window = wxGetGTKDrawable(dc);
850     if (gdk_window == NULL)
851         return;
852 
853     gtk_paint_box
854     (
855         gtk_widget_get_style(button),
856         gdk_window,
857         state,
858         flags & wxCONTROL_PRESSED ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
859         NULL,
860         button,
861         "button",
862         dc.LogicalToDeviceX(rect.x),
863         dc.LogicalToDeviceY(rect.y),
864         rect.width,
865         rect.height
866     );
867 #endif
868 }
869 
870 void
DrawItemSelectionRect(wxWindow * win,wxDC & dc,const wxRect & rect,int flags)871 wxRendererGTK::DrawItemSelectionRect(wxWindow* win,
872                                      wxDC& dc,
873                                      const wxRect& rect,
874                                      int flags )
875 {
876     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
877     if (drawable == NULL)
878         return;
879 
880     if (flags & wxCONTROL_SELECTED)
881     {
882         GtkWidget* treeWidget = wxGTKPrivate::GetTreeWidget();
883 
884 #ifdef __WXGTK3__
885         GtkStyleContext* sc = gtk_widget_get_style_context(treeWidget);
886         gtk_style_context_save(sc);
887         int state = GTK_STATE_FLAG_SELECTED;
888         if (flags & wxCONTROL_FOCUSED)
889             state |= GTK_STATE_FLAG_FOCUSED;
890         gtk_style_context_set_state(sc, GtkStateFlags(state));
891         gtk_style_context_add_class(sc, GTK_STYLE_CLASS_CELL);
892         gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
893         gtk_style_context_restore(sc);
894 #else
895         int x_diff = 0;
896         if (win->GetLayoutDirection() == wxLayout_RightToLeft)
897             x_diff = rect.width;
898 
899         // the wxCONTROL_FOCUSED state is deduced
900         // directly from the m_wxwindow by GTK+
901         gtk_paint_flat_box(gtk_widget_get_style(treeWidget),
902                         drawable,
903                         GTK_STATE_SELECTED,
904                         GTK_SHADOW_NONE,
905                         NULL_RECT
906                         win->m_wxwindow,
907                         "cell_even",
908                         dc.LogicalToDeviceX(rect.x) - x_diff,
909                         dc.LogicalToDeviceY(rect.y),
910                         rect.width,
911                         rect.height );
912 #endif
913     }
914 
915     if ((flags & wxCONTROL_CURRENT) && (flags & wxCONTROL_FOCUSED))
916         DrawFocusRect(win, dc, rect, flags);
917 }
918 
DrawFocusRect(wxWindow * win,wxDC & dc,const wxRect & rect,int flags)919 void wxRendererGTK::DrawFocusRect(wxWindow* win, wxDC& dc, const wxRect& rect, int flags)
920 {
921     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
922     if (drawable == NULL)
923         return;
924 
925     GtkStateType state;
926     if (flags & wxCONTROL_SELECTED)
927         state = GTK_STATE_SELECTED;
928     else
929         state = GTK_STATE_NORMAL;
930 
931 #ifdef __WXGTK3__
932     GtkStyleContext* sc = gtk_widget_get_style_context(win->m_widget);
933     gtk_style_context_save(sc);
934     gtk_style_context_set_state(sc, stateTypeToFlags[state]);
935     gtk_render_focus(sc, drawable, rect.x, rect.y, rect.width, rect.height);
936     gtk_style_context_restore(sc);
937 #else
938     gtk_paint_focus( gtk_widget_get_style(win->m_widget),
939                      drawable,
940                      state,
941                      NULL_RECT
942                      win->m_wxwindow,
943                      NULL,
944                      dc.LogicalToDeviceX(rect.x),
945                      dc.LogicalToDeviceY(rect.y),
946                      rect.width,
947                      rect.height );
948 #endif
949 }
950 
951 // Uses the theme to draw the border and fill for something like a wxTextCtrl
DrawTextCtrl(wxWindow *,wxDC & dc,const wxRect & rect,int flags)952 void wxRendererGTK::DrawTextCtrl(wxWindow*, wxDC& dc, const wxRect& rect, int flags)
953 {
954     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
955     if (drawable == NULL)
956         return;
957 
958 #ifdef __WXGTK3__
959     int state = GTK_STATE_FLAG_NORMAL;
960     if (flags & wxCONTROL_FOCUSED)
961         state = GTK_STATE_FLAG_FOCUSED;
962     if (flags & wxCONTROL_DISABLED)
963         state = GTK_STATE_FLAG_INSENSITIVE;
964 
965     wxGtkStyleContext sc(dc.GetContentScaleFactor());
966     sc.Add(GTK_TYPE_ENTRY, "entry", "entry", NULL);
967 
968     gtk_style_context_set_state(sc, GtkStateFlags(state));
969     gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
970     gtk_render_frame(sc, drawable, rect.x, rect.y, rect.width, rect.height);
971 #else
972     GtkWidget* entry = wxGTKPrivate::GetTextEntryWidget();
973 
974     GtkStateType state = GTK_STATE_NORMAL;
975     if ( flags & wxCONTROL_DISABLED )
976         state = GTK_STATE_INSENSITIVE;
977 
978     gtk_widget_set_can_focus(entry, (flags & wxCONTROL_CURRENT) != 0);
979 
980     gtk_paint_shadow
981     (
982         gtk_widget_get_style(entry),
983         drawable,
984         state,
985         GTK_SHADOW_OUT,
986         NULL_RECT
987         entry,
988         "entry",
989         dc.LogicalToDeviceX(rect.x),
990         dc.LogicalToDeviceY(rect.y),
991         rect.width,
992         rect.height
993   );
994 #endif
995 }
996 
997 // Draw the equivalent of a wxComboBox
DrawComboBox(wxWindow * win,wxDC & dc,const wxRect & rect,int flags)998 void wxRendererGTK::DrawComboBox(wxWindow* win, wxDC& dc, const wxRect& rect, int flags)
999 {
1000     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
1001     if (drawable == NULL)
1002         return;
1003 
1004     GtkWidget* combo = wxGTKPrivate::GetComboBoxWidget();
1005 
1006     GtkStateType state = GTK_STATE_NORMAL;
1007     if ( flags & wxCONTROL_DISABLED )
1008        state = GTK_STATE_INSENSITIVE;
1009 
1010     gtk_widget_set_can_focus(combo, (flags & wxCONTROL_CURRENT) != 0);
1011 
1012 #ifdef __WXGTK3__
1013     GtkStyleContext* sc = gtk_widget_get_style_context(combo);
1014     gtk_style_context_save(sc);
1015     gtk_style_context_set_state(sc, stateTypeToFlags[state]);
1016     gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height);
1017     gtk_render_frame(sc, drawable, rect.x, rect.y, rect.width, rect.height);
1018     gtk_style_context_restore(sc);
1019     wxRect r = rect;
1020     r.x += r.width - r.height;
1021     r.width = r.height;
1022     DrawComboBoxDropButton(win, dc, r, flags);
1023 #else
1024     wxUnusedVar(win);
1025     gtk_paint_shadow
1026     (
1027         gtk_widget_get_style(combo),
1028         drawable,
1029         state,
1030         GTK_SHADOW_OUT,
1031         NULL_RECT
1032         combo,
1033         "combobox",
1034         dc.LogicalToDeviceX(rect.x),
1035         dc.LogicalToDeviceY(rect.y),
1036         rect.width,
1037         rect.height
1038     );
1039 
1040     wxRect r = rect;
1041     int extent = rect.height / 2;
1042     r.x += rect.width - extent - extent/2;
1043     r.y += extent/2;
1044     r.width = extent;
1045     r.height = extent;
1046 
1047     gtk_paint_arrow
1048     (
1049         gtk_widget_get_style(combo),
1050         drawable,
1051         state,
1052         GTK_SHADOW_OUT,
1053         NULL_RECT
1054         combo,
1055         "arrow",
1056         GTK_ARROW_DOWN,
1057         TRUE,
1058         dc.LogicalToDeviceX(r.x),
1059         dc.LogicalToDeviceY(r.y),
1060         r.width,
1061         r.height
1062     );
1063 
1064     r = rect;
1065     r.x += rect.width - 2*extent;
1066     r.width = 2;
1067 
1068     gtk_paint_box
1069     (
1070         gtk_widget_get_style(combo),
1071         drawable,
1072         state,
1073         GTK_SHADOW_ETCHED_OUT,
1074         NULL_RECT
1075         combo,
1076         "vseparator",
1077         dc.LogicalToDeviceX(r.x),
1078         dc.LogicalToDeviceY(r.y+1),
1079         r.width,
1080         r.height-2
1081     );
1082 #endif
1083 }
1084 
DrawChoice(wxWindow * win,wxDC & dc,const wxRect & rect,int flags)1085 void wxRendererGTK::DrawChoice(wxWindow* win, wxDC& dc,
1086                            const wxRect& rect, int flags)
1087 {
1088     DrawComboBox( win, dc, rect, flags );
1089 }
1090 
1091 
1092 // Draw a themed radio button
DrawRadioBitmap(wxWindow *,wxDC & dc,const wxRect & rect,int flags)1093 void wxRendererGTK::DrawRadioBitmap(wxWindow*, wxDC& dc, const wxRect& rect, int flags)
1094 {
1095     wxGTKDrawable* drawable = wxGetGTKDrawable(dc);
1096     if (drawable == NULL)
1097         return;
1098 
1099 #ifdef __WXGTK3__
1100     int state = GTK_STATE_FLAG_NORMAL;
1101     if (flags & wxCONTROL_CHECKED)
1102     {
1103         state = GTK_STATE_FLAG_ACTIVE;
1104         if (gtk_check_version(3,14,0) == NULL)
1105             state = GTK_STATE_FLAG_CHECKED;
1106     }
1107     if (flags & wxCONTROL_DISABLED)
1108         state |= GTK_STATE_FLAG_INSENSITIVE;
1109     if (flags & wxCONTROL_UNDETERMINED)
1110         state |= GTK_STATE_FLAG_INCONSISTENT;
1111     if (flags & wxCONTROL_CURRENT)
1112         state |= GTK_STATE_FLAG_PRELIGHT;
1113 
1114     int min_width, min_height;
1115     wxGtkStyleContext sc(dc.GetContentScaleFactor());
1116     sc.Add(GTK_TYPE_RADIO_BUTTON, "radiobutton", NULL);
1117     if (gtk_check_version(3,20,0) == NULL)
1118     {
1119         sc.Add("radio");
1120         gtk_style_context_get(sc, GTK_STATE_FLAG_NORMAL,
1121             "min-width", &min_width, "min-height", &min_height, NULL);
1122     }
1123     else
1124     {
1125         wxGtkValue value( G_TYPE_INT);
1126         gtk_style_context_get_style_property(sc, "indicator-size", value);
1127         min_width = g_value_get_int(value);
1128         min_height = min_width;
1129     }
1130 
1131     // need save/restore for GTK+ 3.6 & 3.8
1132     gtk_style_context_save(sc);
1133     gtk_style_context_set_state(sc, GtkStateFlags(state));
1134     const int x = rect.x + (rect.width - min_width) / 2;
1135     const int y = rect.y + (rect.height - min_height) / 2;
1136     gtk_render_background(sc, drawable, x, y, min_width, min_height);
1137     gtk_render_frame(sc, drawable, x, y, min_width, min_height);
1138     gtk_style_context_add_class(sc, "radio");
1139     gtk_render_option(sc, drawable, x, y, min_width, min_height);
1140     gtk_style_context_restore(sc);
1141 #else
1142     GtkWidget* button = wxGTKPrivate::GetRadioButtonWidget();
1143 
1144     GtkShadowType shadow_type = GTK_SHADOW_OUT;
1145     if ( flags & wxCONTROL_CHECKED )
1146         shadow_type = GTK_SHADOW_IN;
1147     else if ( flags & wxCONTROL_UNDETERMINED )
1148         shadow_type = GTK_SHADOW_ETCHED_IN;
1149 
1150     GtkStateType state = GTK_STATE_NORMAL;
1151     if ( flags & wxCONTROL_DISABLED )
1152         state = GTK_STATE_INSENSITIVE;
1153     if ( flags & wxCONTROL_PRESSED )
1154         state = GTK_STATE_ACTIVE;
1155 /*
1156     Don't know when to set this
1157        state_type = GTK_STATE_PRELIGHT;
1158 */
1159 
1160     gtk_paint_option
1161     (
1162         gtk_widget_get_style(button),
1163         drawable,
1164         state,
1165         shadow_type,
1166         NULL_RECT
1167         button,
1168         "radiobutton",
1169         dc.LogicalToDeviceX(rect.x),
1170         dc.LogicalToDeviceY(rect.y),
1171         rect.width, rect.height
1172     );
1173 #endif
1174 }
1175