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