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