1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/aui/tabartgtk.cpp
3 // Purpose:     implementation of the wxAuiGTKTabArt
4 // Author:      Jens Lody and Teodor Petrov
5 // Modified by:
6 // Created:     2012-03-23
7 // Copyright:   (c) 2012 Jens Lody <jens@codeblocks.org>
8 //                  and Teodor Petrov
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 
24 #if wxUSE_AUI
25 
26 #ifndef WX_PRECOMP
27     #include "wx/dc.h"
28     #include "wx/dcclient.h"
29     #include "wx/settings.h"
30     #include "wx/image.h"
31 #endif
32 
33 #include "wx/gtk/dc.h"
34 #include "wx/gtk/private.h"
35 
36 #include "wx/aui/auibook.h"
37 #include "wx/aui/tabartgtk.h"
38 #include "wx/renderer.h"
39 
40 namespace
41 {
42 
43 static int s_CloseIconSize = 16; // default size
44 
45 }
46 
wxAuiGtkTabArt()47 wxAuiGtkTabArt::wxAuiGtkTabArt()
48 
49 {
50 }
51 
Clone()52 wxAuiTabArt* wxAuiGtkTabArt::Clone()
53 {
54     wxAuiGtkTabArt* clone = new wxAuiGtkTabArt();
55 
56     clone->SetNormalFont(m_normalFont);
57     clone->SetSelectedFont(m_normalFont);
58     clone->SetMeasuringFont(m_normalFont);
59 
60     return clone;
61 }
62 
DrawBackground(wxDC & dc,wxWindow * WXUNUSED (wnd),const wxRect & rect)63 void wxAuiGtkTabArt::DrawBackground(wxDC& dc, wxWindow* WXUNUSED(wnd), const wxRect& rect)
64 {
65     wxGTKDCImpl *impldc = (wxGTKDCImpl*) dc.GetImpl();
66     GdkWindow* window = impldc->GetGDKWindow();
67 
68     gtk_style_apply_default_background(gtk_widget_get_style(wxGTKPrivate::GetNotebookWidget()),
69                                        window,
70                                        true,
71                                        GTK_STATE_NORMAL,
72                                        NULL,
73                                        rect.x, rect.y, rect.width, rect.height);
74 }
75 
DrawBorder(wxDC & WXUNUSED (dc),wxWindow * wnd,const wxRect & rect)76 void wxAuiGtkTabArt::DrawBorder(wxDC& WXUNUSED(dc), wxWindow* wnd, const wxRect& rect)
77 {
78     int generic_border_width = wxAuiGenericTabArt::GetBorderWidth(wnd);
79 
80     if (!wnd) return;
81     if (!wnd->m_wxwindow) return;
82     if (!gtk_widget_is_drawable(wnd->m_wxwindow)) return;
83 
84     GtkStyle *style_notebook = gtk_widget_get_style(wxGTKPrivate::GetNotebookWidget());
85 
86     gtk_paint_box(style_notebook, wnd->GTKGetDrawingWindow(), GTK_STATE_NORMAL, GTK_SHADOW_OUT,
87                   NULL, wnd->m_wxwindow,
88                   const_cast<char*>("notebook"),
89                   rect.x + generic_border_width + 1, rect.y + generic_border_width + 1,
90                   rect.width - (generic_border_width + 1), rect.height - (generic_border_width + 1));
91 }
92 
ButtonStateAndShadow(int button_state,GtkStateType & state,GtkShadowType & shadow)93 void ButtonStateAndShadow(int button_state, GtkStateType &state, GtkShadowType &shadow)
94 {
95 
96     if (button_state & wxAUI_BUTTON_STATE_DISABLED)
97     {
98         state = GTK_STATE_INSENSITIVE;
99         shadow = GTK_SHADOW_ETCHED_IN;
100     }
101     else if (button_state & wxAUI_BUTTON_STATE_HOVER)
102     {
103         state = GTK_STATE_PRELIGHT;
104         shadow = GTK_SHADOW_OUT;
105     }
106     else if (button_state & wxAUI_BUTTON_STATE_PRESSED)
107     {
108         state = GTK_STATE_ACTIVE;
109         shadow = GTK_SHADOW_IN;
110     }
111     else
112     {
113         state = GTK_STATE_NORMAL;
114         shadow = GTK_SHADOW_OUT;
115     }
116 }
117 
DrawCloseButton(wxDC & dc,GtkWidget * widget,int button_state,wxRect const & in_rect,int orientation,GdkRectangle * clipRect)118 wxRect DrawCloseButton(wxDC& dc,
119                        GtkWidget *widget,
120                        int button_state,
121                        wxRect const &in_rect,
122                        int orientation,
123                        GdkRectangle* clipRect)
124 {
125     GtkStyle *style_button = gtk_widget_get_style(wxGTKPrivate::GetButtonWidget());
126     int xthickness = style_button->xthickness;
127     int ythickness = style_button->ythickness;
128 
129     wxBitmap bmp(gtk_widget_render_icon(widget, GTK_STOCK_CLOSE, GTK_ICON_SIZE_SMALL_TOOLBAR, "tab"));
130 
131     if(bmp.GetScaledWidth() != s_CloseIconSize || bmp.GetScaledHeight() != s_CloseIconSize)
132     {
133         wxImage img = bmp.ConvertToImage();
134         img.Rescale(s_CloseIconSize, s_CloseIconSize);
135         bmp = img;
136     }
137 
138     int button_size = s_CloseIconSize + 2 * xthickness;
139 
140     wxRect out_rect;
141 
142     if (orientation == wxLEFT)
143         out_rect.x = in_rect.x - ythickness;
144     else
145         out_rect.x = in_rect.x + in_rect.width - button_size - ythickness;
146 
147     out_rect.y = in_rect.y + (in_rect.height - button_size) / 2;
148     out_rect.width = button_size;
149     out_rect.height = button_size;
150 
151     wxGTKDCImpl *impldc = (wxGTKDCImpl*) dc.GetImpl();
152     GdkWindow* window = impldc->GetGDKWindow();
153 
154     if (button_state == wxAUI_BUTTON_STATE_HOVER)
155     {
156         gtk_paint_box(style_button, window,
157                       GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, clipRect, widget, "button",
158                      out_rect.x, out_rect.y, out_rect.width, out_rect.height);
159     }
160     else if (button_state == wxAUI_BUTTON_STATE_PRESSED)
161     {
162         gtk_paint_box(style_button, window,
163                       GTK_STATE_ACTIVE, GTK_SHADOW_IN, clipRect, widget, "button",
164                       out_rect.x, out_rect.y, out_rect.width, out_rect.height);
165     }
166 
167 
168     dc.DrawBitmap(bmp, out_rect.x + xthickness, out_rect.y + ythickness, true);
169 
170     return out_rect;
171 }
172 
DrawTab(wxDC & dc,wxWindow * wnd,const wxAuiNotebookPage & page,const wxRect & in_rect,int close_button_state,wxRect * out_tab_rect,wxRect * out_button_rect,int * x_extent)173 void wxAuiGtkTabArt::DrawTab(wxDC& dc, wxWindow* wnd, const wxAuiNotebookPage& page,
174                              const wxRect& in_rect, int close_button_state, wxRect* out_tab_rect,
175                              wxRect* out_button_rect, int* x_extent)
176 {
177     GtkWidget *widget = wnd->GetHandle();
178     GtkStyle *style_notebook = gtk_widget_get_style(wxGTKPrivate::GetNotebookWidget());
179 
180     wxRect const &window_rect = wnd->GetRect();
181 
182     int focus_width = 0;
183 
184     gtk_widget_style_get(wxGTKPrivate::GetNotebookWidget(),
185                          "focus-line-width", &focus_width,
186                          NULL);
187 
188     int tab_pos;
189     if (m_flags &wxAUI_NB_BOTTOM)
190         tab_pos = wxAUI_NB_BOTTOM;
191     else //if (m_flags & wxAUI_NB_TOP) {}
192         tab_pos = wxAUI_NB_TOP;
193 
194     // TODO: else if (m_flags &wxAUI_NB_LEFT) {}
195     // TODO: else if (m_flags &wxAUI_NB_RIGHT) {}
196 
197     // figure out the size of the tab
198     wxSize tab_size = GetTabSize(dc, wnd, page.caption, page.bitmap,
199                                     page.active, close_button_state, x_extent);
200 
201     wxRect tab_rect = in_rect;
202     tab_rect.width = tab_size.x;
203     tab_rect.height = tab_size.y;
204     tab_rect.y += 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
205 
206     if (page.active)
207         tab_rect.height += 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
208 
209     int gap_rect_height = 10 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
210     int gap_rect_x = 1, gap_start = 0, gap_width = 0;
211     int gap_rect_y = tab_rect.y - gap_rect_height;
212     int gap_rect_width = window_rect.width;
213 
214     switch (tab_pos)
215     {
216         case wxAUI_NB_TOP:
217             tab_rect.y -= 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
218             if (!page.active)
219                 tab_rect.y += 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
220             gap_rect_y = tab_rect.y + tab_rect.height - GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder / 2;
221             wxFALLTHROUGH;
222         case wxAUI_NB_BOTTOM:
223             gap_start = tab_rect.x - GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder / 2;
224             gap_width = tab_rect.width;
225             break;
226         // TODO: case wxAUI_NB_LEFT: break;
227         // TODO: case wxAUI_NB_RIGHT: break;
228     }
229     tab_rect.y += GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder / 2;
230     gap_rect_y += GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder / 2;
231 
232     int padding = focus_width + GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
233 
234     int clip_width = tab_rect.width;
235     if (tab_rect.x + tab_rect.width > in_rect.x + in_rect.width)
236         clip_width = (in_rect.x + in_rect.width) - tab_rect.x;
237 
238     dc.SetClippingRegion(tab_rect.x, tab_rect.y - GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder, clip_width, tab_rect.height + GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder);
239 
240     GdkRectangle area;
241     area.x = tab_rect.x - GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder;
242     area.y = tab_rect.y - 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
243     area.width = clip_width + GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder;
244     area.height = tab_rect.height + 2 * GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder;
245 
246     wxGTKDCImpl *impldc = (wxGTKDCImpl*) dc.GetImpl();
247     GdkWindow* window = impldc->GetGDKWindow();
248 
249     // Before drawing the active tab itself, draw a box without border, because some themes
250     // have transparent gaps and a line would be visible at the bottom of the tab
251     if (page.active)
252         gtk_paint_box(style_notebook, window, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
253                       NULL, widget,
254                       const_cast<char*>("notebook"),
255                       gap_rect_x, gap_rect_y,
256                       gap_rect_width, gap_rect_height);
257 
258     if (tab_pos == wxAUI_NB_BOTTOM)
259     {
260         if (page.active)
261         {
262             gtk_paint_box_gap(style_notebook, window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
263                               NULL, widget,
264                               const_cast<char*>("notebook"),
265                               gap_rect_x, gap_rect_y,
266                               gap_rect_width, gap_rect_height,
267                               GTK_POS_BOTTOM, gap_start , gap_width);
268         }
269         gtk_paint_extension(style_notebook, window,
270                            page.active ? GTK_STATE_NORMAL : GTK_STATE_ACTIVE, GTK_SHADOW_OUT,
271                            &area, widget,
272                            const_cast<char*>("tab"),
273                            tab_rect.x, tab_rect.y,
274                            tab_rect.width, tab_rect.height,
275                            GTK_POS_TOP);
276     }
277     else
278     {
279         if (page.active)
280         {
281             gtk_paint_box_gap(style_notebook, window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
282                               NULL, widget,
283                               const_cast<char*>("notebook"),
284                               gap_rect_x, gap_rect_y,
285                               gap_rect_width, gap_rect_height,
286                               GTK_POS_TOP, gap_start , gap_width);
287         }
288         gtk_paint_extension(style_notebook, window,
289                            page.active ? GTK_STATE_NORMAL : GTK_STATE_ACTIVE, GTK_SHADOW_OUT,
290                            &area, widget,
291                            const_cast<char*>("tab"),
292                            tab_rect.x, tab_rect.y,
293                            tab_rect.width, tab_rect.height,
294                            GTK_POS_BOTTOM);
295     }
296 
297     // After drawing the inactive tab itself, draw a box with the same dimensions as the gap-box,
298     // otherwise we don't get a gap-box, if the active tab is invisible
299     if (!page.active)
300         gtk_paint_box(style_notebook, window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
301                       NULL, widget,
302                       const_cast<char*>("notebook"),
303                       gap_rect_x, gap_rect_y,
304                       gap_rect_width, gap_rect_height);
305 
306     wxCoord textX = tab_rect.x + padding + style_notebook->xthickness;
307 
308     if (page.bitmap.IsOk())
309     {
310         int bitmap_offset;
311         bitmap_offset = textX;
312 
313         // draw bitmap
314         int bitmapY = tab_rect.y +(tab_rect.height - page.bitmap.GetScaledHeight()) / 2;
315         if(!page.active)
316         {
317             if (tab_pos == wxAUI_NB_TOP)
318                 bitmapY += style_notebook->ythickness / 2;
319             else
320                 bitmapY -= style_notebook->ythickness / 2;
321         }
322         dc.DrawBitmap(page.bitmap,
323                       bitmap_offset,
324                       bitmapY,
325                       true);
326 
327         textX += page.bitmap.GetScaledWidth() + padding;
328     }
329 
330     wxCoord textW, textH, textY;
331 
332     dc.SetFont(m_normalFont);
333     dc.GetTextExtent(page.caption, &textW, &textH);
334     textY = tab_rect.y + (tab_rect.height - textH) / 2;
335     if(!page.active)
336     {
337         if (tab_pos == wxAUI_NB_TOP)
338             textY += style_notebook->ythickness / 2;
339         else
340             textY -= style_notebook->ythickness / 2;
341     }
342 
343     // draw tab text
344     GdkColor text_colour = page.active ? style_notebook->fg[GTK_STATE_NORMAL] : style_notebook->fg[GTK_STATE_ACTIVE];
345     dc.SetTextForeground(wxColor(text_colour));
346     GdkRectangle focus_area;
347 
348     int padding_focus = padding - focus_width;
349     focus_area.x = tab_rect.x + padding_focus;
350     focus_area.y = textY - focus_width;
351     focus_area.width = tab_rect.width - 2 * padding_focus;
352     focus_area.height = textH + 2 * focus_width;
353 
354     if(page.active && (wnd->FindFocus() == wnd) && focus_area.x <= (area.x + area.width))
355     {
356         // clipping seems not to work here, so we we have to recalc the focus-area manually
357         if((focus_area.x + focus_area.width) > (area.x + area.width))
358             focus_area.width = area.x + area.width - focus_area.x + focus_width - GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder;
359         gtk_paint_focus (style_notebook, window,
360                          GTK_STATE_ACTIVE, NULL, widget, "tab",
361                          focus_area.x, focus_area.y, focus_area.width, focus_area.height);
362     }
363 
364     dc.DrawText(page.caption, textX, textY);
365 
366     // draw close-button on tab (if enabled)
367     if (close_button_state != wxAUI_BUTTON_STATE_HIDDEN)
368     {
369         wxRect rect(tab_rect.x, tab_rect.y, tab_rect.width - style_notebook->xthickness, tab_rect.height);
370         if(!page.active)
371         {
372             if (tab_pos == wxAUI_NB_TOP)
373                 rect.y += style_notebook->ythickness / 2;
374             else
375                 rect.y -= style_notebook->ythickness / 2;
376         }
377         *out_button_rect = DrawCloseButton(dc, widget, close_button_state, rect, wxRIGHT, &area);
378     }
379 
380     if ( clip_width < tab_rect.width )
381         tab_rect.width = clip_width;
382     *out_tab_rect = tab_rect;
383 
384     dc.DestroyClippingRegion();
385 }
386 
DrawSimpleArrow(wxDC & dc,GtkWidget * widget,int button_state,wxRect const & in_rect,int orientation,GtkArrowType arrow_type)387 wxRect DrawSimpleArrow(wxDC& dc,
388                        GtkWidget *widget,
389                        int button_state,
390                        wxRect const &in_rect,
391                        int orientation,
392                        GtkArrowType arrow_type)
393 {
394     int scroll_arrow_hlength, scroll_arrow_vlength;
395     gtk_widget_style_get(widget,
396                          "scroll-arrow-hlength", &scroll_arrow_hlength,
397                          "scroll-arrow-vlength", &scroll_arrow_vlength,
398                          NULL);
399 
400     GtkStateType state;
401     GtkShadowType shadow;
402     ButtonStateAndShadow(button_state, state, shadow);
403 
404     wxRect out_rect;
405 
406     if (orientation == wxLEFT)
407         out_rect.x = in_rect.x;
408     else
409         out_rect.x = in_rect.x + in_rect.width - scroll_arrow_hlength;
410     out_rect.y = (in_rect.y + in_rect.height - 3 * gtk_widget_get_style(wxGTKPrivate::GetNotebookWidget())->ythickness - scroll_arrow_vlength) / 2;
411     out_rect.width = scroll_arrow_hlength;
412     out_rect.height = scroll_arrow_vlength;
413 
414     wxGTKDCImpl *impldc = (wxGTKDCImpl*) dc.GetImpl();
415     GdkWindow* window = impldc->GetGDKWindow();
416     gtk_paint_arrow (gtk_widget_get_style(wxGTKPrivate::GetButtonWidget()), window, state, shadow, NULL, widget, "notebook",
417                      arrow_type, TRUE, out_rect.x, out_rect.y, out_rect.width, out_rect.height);
418 
419     return out_rect;
420 }
421 
DrawButton(wxDC & dc,wxWindow * wnd,const wxRect & in_rect,int bitmap_id,int button_state,int orientation,wxRect * out_rect)422 void wxAuiGtkTabArt::DrawButton(wxDC& dc, wxWindow* wnd,
423                             const wxRect& in_rect,
424                             int bitmap_id,
425                             int button_state,
426                             int orientation,
427                             wxRect* out_rect)
428 {
429     GtkWidget *widget = wnd->GetHandle();
430     wxRect rect = in_rect;
431     if (m_flags &wxAUI_NB_BOTTOM)
432         rect.y += 2 * gtk_widget_get_style(wxGTKPrivate::GetButtonWidget())->ythickness;
433 
434     switch (bitmap_id)
435     {
436         case wxAUI_BUTTON_CLOSE:
437             rect.y -= 2 * gtk_widget_get_style(wxGTKPrivate::GetButtonWidget())->ythickness;
438             rect = DrawCloseButton(dc, widget, button_state, rect, orientation, NULL);
439             break;
440 
441         case wxAUI_BUTTON_LEFT:
442             rect = DrawSimpleArrow(dc, widget, button_state, rect, orientation, GTK_ARROW_LEFT);
443             break;
444 
445         case wxAUI_BUTTON_RIGHT:
446             rect = DrawSimpleArrow(dc, widget, button_state, rect, orientation, GTK_ARROW_RIGHT);
447             break;
448 
449         case wxAUI_BUTTON_WINDOWLIST:
450             {
451                 rect.height -= 4 * gtk_widget_get_style(wxGTKPrivate::GetButtonWidget())->ythickness;
452                 rect.width = rect.height;
453                 rect.x = in_rect.x + in_rect.width - rect.width;
454 
455                 if (button_state == wxAUI_BUTTON_STATE_HOVER)
456                     wxRendererNative::Get().DrawComboBoxDropButton(wnd, dc, rect, wxCONTROL_CURRENT);
457                 else if (button_state == wxAUI_BUTTON_STATE_PRESSED)
458                     wxRendererNative::Get().DrawComboBoxDropButton(wnd, dc, rect, wxCONTROL_PRESSED);
459                 else
460                     wxRendererNative::Get().DrawDropArrow(wnd, dc, rect);
461             }
462             break;
463     }
464 
465     *out_rect = rect;
466 }
467 
468 
GetBestTabCtrlSize(wxWindow * wnd,const wxAuiNotebookPageArray & pages,const wxSize & required_bmp_size)469 int wxAuiGtkTabArt::GetBestTabCtrlSize(wxWindow* wnd,
470                                    const wxAuiNotebookPageArray& pages,
471                                    const wxSize& required_bmp_size)
472 {
473     SetMeasuringFont(m_normalFont);
474     SetSelectedFont(m_normalFont);
475     int tab_height = 3 * gtk_widget_get_style(wxGTKPrivate::GetNotebookWidget())->ythickness + wxAuiGenericTabArt::GetBestTabCtrlSize(wnd, pages, required_bmp_size);
476     return tab_height;
477 }
478 
GetBorderWidth(wxWindow * wnd)479 int wxAuiGtkTabArt::GetBorderWidth(wxWindow* wnd)
480 {
481     return wxAuiGenericTabArt::GetBorderWidth(wnd) + wxMax(GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_hborder,
482                                                            GTK_NOTEBOOK (wxGTKPrivate::GetNotebookWidget())->tab_vborder);
483 }
484 
GetAdditionalBorderSpace(wxWindow * wnd)485 int wxAuiGtkTabArt::GetAdditionalBorderSpace(wxWindow* wnd)
486 {
487     return 2 * GetBorderWidth(wnd);
488 }
489 
GetTabSize(wxDC & dc,wxWindow * wnd,const wxString & caption,const wxBitmap & bitmap,bool active,int close_button_state,int * x_extent)490 wxSize wxAuiGtkTabArt::GetTabSize(wxDC& dc,
491                               wxWindow* wnd,
492                               const wxString& caption,
493                               const wxBitmap& bitmap,
494                               bool active,
495                               int close_button_state,
496                               int* x_extent)
497 {
498     wxSize s = wxAuiGenericTabArt::GetTabSize(dc, wnd, caption, bitmap, active, close_button_state, x_extent);
499 
500     int overlap = 0;
501     gtk_widget_style_get (wnd->GetHandle(),
502         "focus-line-width", &overlap,
503         NULL);
504     *x_extent -= overlap;
505     return s;
506 }
507 #endif  // wxUSE_AUI
508