support for GTK3
[wxWidgets.git] / src / generic / statusbr.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/statusbr.cpp
3 // Purpose: wxStatusBarGeneric class implementation
4 // Author: Julian Smart
5 // Modified by: Francesco Montorsi
6 // Created: 01/02/97
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_STATUSBAR
20
21 #include "wx/statusbr.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/settings.h"
25 #include "wx/dcclient.h"
26 #include "wx/toplevel.h"
27 #include "wx/control.h"
28 #endif
29
30 #ifdef __WXGTK20__
31 #include <gtk/gtk.h>
32 #include "wx/gtk/private.h"
33 #include "wx/gtk/private/gtk2-compat.h"
34 #endif
35
36 // we only have to do it here when we use wxStatusBarGeneric in addition to the
37 // standard wxStatusBar class, if wxStatusBarGeneric is the same as
38 // wxStatusBar, then the corresponding IMPLEMENT_DYNAMIC_CLASS is already in
39 // common/statbar.cpp
40 #if defined(__WXMAC__) || \
41 (defined(wxUSE_NATIVE_STATUSBAR) && wxUSE_NATIVE_STATUSBAR)
42 #include "wx/generic/statusbr.h"
43
44 IMPLEMENT_DYNAMIC_CLASS(wxStatusBarGeneric, wxWindow)
45 #endif // wxUSE_NATIVE_STATUSBAR
46
47 // Default status border dimensions
48 #define wxTHICK_LINE_BORDER 2
49
50 // Margin between the field text and the field rect
51 #define wxFIELD_TEXT_MARGIN 2
52
53 // ----------------------------------------------------------------------------
54 // GTK+ signal handler
55 // ----------------------------------------------------------------------------
56
57 #if defined( __WXGTK20__ )
58 #if GTK_CHECK_VERSION(2,12,0)
59 extern "C" {
60 static
61 gboolean statusbar_query_tooltip(GtkWidget* WXUNUSED(widget),
62 gint x,
63 gint y,
64 gboolean WXUNUSED(keyboard_mode),
65 GtkTooltip *tooltip,
66 wxStatusBar* statbar)
67 {
68 int n = statbar->GetFieldFromPoint(wxPoint(x,y));
69 if (n == wxNOT_FOUND)
70 return FALSE;
71
72 // should we show the tooltip for the n-th pane of the statusbar?
73 if (!statbar->GetField(n).IsEllipsized())
74 return FALSE; // no, it's not useful
75
76 const wxString& str = statbar->GetStatusText(n);
77 if (str.empty())
78 return FALSE;
79
80 gtk_tooltip_set_text(tooltip, wxGTK_CONV_SYS(str));
81 return TRUE;
82 }
83 }
84 #endif
85 #endif
86
87 // ----------------------------------------------------------------------------
88 // wxStatusBarGeneric
89 // ----------------------------------------------------------------------------
90
91 BEGIN_EVENT_TABLE(wxStatusBarGeneric, wxWindow)
92 EVT_PAINT(wxStatusBarGeneric::OnPaint)
93 EVT_SIZE(wxStatusBarGeneric::OnSize)
94 #ifdef __WXGTK20__
95 EVT_LEFT_DOWN(wxStatusBarGeneric::OnLeftDown)
96 EVT_RIGHT_DOWN(wxStatusBarGeneric::OnRightDown)
97 #endif
98 EVT_SYS_COLOUR_CHANGED(wxStatusBarGeneric::OnSysColourChanged)
99 END_EVENT_TABLE()
100
101 void wxStatusBarGeneric::Init()
102 {
103 m_borderX = wxTHICK_LINE_BORDER;
104 m_borderY = wxTHICK_LINE_BORDER;
105 }
106
107 wxStatusBarGeneric::~wxStatusBarGeneric()
108 {
109 }
110
111 bool wxStatusBarGeneric::Create(wxWindow *parent,
112 wxWindowID id,
113 long style,
114 const wxString& name)
115 {
116 style |= wxTAB_TRAVERSAL | wxFULL_REPAINT_ON_RESIZE;
117 if ( !wxWindow::Create(parent, id,
118 wxDefaultPosition, wxDefaultSize,
119 style, name) )
120 return false;
121
122 // The status bar should have a themed background
123 SetThemeEnabled( true );
124
125 InitColours();
126
127 #ifdef __WXPM__
128 SetFont(*wxSMALL_FONT);
129 #endif
130
131 int height = (int)((11*GetCharHeight())/10 + 2*GetBorderY());
132 SetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, height);
133
134 SetFieldsCount(1);
135
136 #if defined( __WXGTK20__ )
137 #if GTK_CHECK_VERSION(2,12,0)
138 if (HasFlag(wxSTB_SHOW_TIPS)
139 #ifndef __WXGTK3__
140 && gtk_check_version(2,12,0) == NULL
141 #endif
142 )
143 {
144 g_object_set(m_widget, "has-tooltip", TRUE, NULL);
145 g_signal_connect(m_widget, "query-tooltip",
146 G_CALLBACK(statusbar_query_tooltip), this);
147 }
148 #endif
149 #endif
150
151 return true;
152 }
153
154 wxSize wxStatusBarGeneric::DoGetBestSize() const
155 {
156 int width, height;
157
158 // best width is the width of the parent
159 if (GetParent())
160 GetParent()->GetClientSize(&width, NULL);
161 else
162 width = 80; // a dummy value
163
164 // best height is as calculated above in Create()
165 height = (int)((11*GetCharHeight())/10 + 2*GetBorderY());
166
167 return wxSize(width, height);
168 }
169
170 void wxStatusBarGeneric::DoUpdateStatusText(int number)
171 {
172 wxRect rect;
173 GetFieldRect(number, rect);
174
175 Refresh(true, &rect);
176
177 // it's common to show some text in the status bar before starting a
178 // relatively lengthy operation, ensure that the text is shown to the
179 // user immediately and not after the lengthy operation end
180 Update();
181 }
182
183 void wxStatusBarGeneric::SetStatusWidths(int n, const int widths_field[])
184 {
185 // only set status widths when n == number of statuswindows
186 wxCHECK_RET( (size_t)n == m_panes.GetCount(), wxT("status bar field count mismatch") );
187
188 wxStatusBarBase::SetStatusWidths(n, widths_field);
189
190 // update cache
191 DoUpdateFieldWidths();
192 }
193
194 void wxStatusBarGeneric::DoUpdateFieldWidths()
195 {
196 m_lastClientSize = GetClientSize();
197
198 // recompute the cache of the field widths if the status bar width has changed
199 m_widthsAbs = CalculateAbsWidths(m_lastClientSize.x);
200 }
201
202 bool wxStatusBarGeneric::ShowsSizeGrip() const
203 {
204 if ( !HasFlag(wxSTB_SIZEGRIP) )
205 return false;
206
207 wxTopLevelWindow * const
208 tlw = wxDynamicCast(wxGetTopLevelParent(GetParent()), wxTopLevelWindow);
209 return tlw && !tlw->IsMaximized() && tlw->HasFlag(wxRESIZE_BORDER);
210 }
211
212 void wxStatusBarGeneric::DrawFieldText(wxDC& dc, const wxRect& rect, int i, int textHeight)
213 {
214 wxString text(GetStatusText(i));
215 if (text.empty())
216 return; // optimization
217
218 int xpos = rect.x + wxFIELD_TEXT_MARGIN,
219 maxWidth = rect.width - 2*wxFIELD_TEXT_MARGIN,
220 ypos = (int) (((rect.height - textHeight) / 2) + rect.y + 0.5);
221
222 if (ShowsSizeGrip())
223 {
224 // don't write text over the size grip:
225 // NOTE: overloading DoGetClientSize() and GetClientAreaOrigin() wouldn't
226 // work because the adjustment needs to be done only when drawing
227 // the field text and not also when drawing the background, the
228 // size grip itself, etc
229 if ((GetLayoutDirection() == wxLayout_RightToLeft && i == 0) ||
230 (GetLayoutDirection() != wxLayout_RightToLeft &&
231 i == (int)m_panes.GetCount()-1))
232 {
233 const wxRect& gripRc = GetSizeGripRect();
234
235 // NOTE: we don't need any special treatment wrt to the layout direction
236 // since DrawText() will automatically adjust the origin of the
237 // text accordingly to the layout in use
238
239 maxWidth -= gripRc.width;
240 }
241 }
242
243 // eventually ellipsize the text so that it fits the field width
244
245 wxEllipsizeMode ellmode = (wxEllipsizeMode)-1;
246 if (HasFlag(wxSTB_ELLIPSIZE_START)) ellmode = wxELLIPSIZE_START;
247 else if (HasFlag(wxSTB_ELLIPSIZE_MIDDLE)) ellmode = wxELLIPSIZE_MIDDLE;
248 else if (HasFlag(wxSTB_ELLIPSIZE_END)) ellmode = wxELLIPSIZE_END;
249
250 if (ellmode == (wxEllipsizeMode)-1)
251 {
252 // if we have the wxSTB_SHOW_TIPS we must set the ellipsized flag even if
253 // we don't ellipsize the text but just truncate it
254 if (HasFlag(wxSTB_SHOW_TIPS))
255 SetEllipsizedFlag(i, dc.GetTextExtent(text).GetWidth() > maxWidth);
256
257 dc.SetClippingRegion(rect);
258 }
259 else
260 {
261 text = wxControl::Ellipsize(text, dc,
262 ellmode,
263 maxWidth,
264 wxELLIPSIZE_FLAGS_EXPAND_TABS);
265 // Ellipsize() will do something only if necessary
266
267 // update the ellipsization status for this pane; this is used later to
268 // decide whether a tooltip should be shown or not for this pane
269 // (if we have wxSTB_SHOW_TIPS)
270 SetEllipsizedFlag(i, text != GetStatusText(i));
271 }
272
273 #if defined( __WXGTK__ ) || defined(__WXMAC__)
274 xpos++;
275 ypos++;
276 #endif
277
278 // draw the text
279 dc.DrawText(text, xpos, ypos);
280
281 if (ellmode == (wxEllipsizeMode)-1)
282 dc.DestroyClippingRegion();
283 }
284
285 void wxStatusBarGeneric::DrawField(wxDC& dc, int i, int textHeight)
286 {
287 wxRect rect;
288 GetFieldRect(i, rect);
289
290 if (rect.GetWidth() <= 0)
291 return; // happens when the status bar is shrunk in a very small area!
292
293 int style = m_panes[i].GetStyle();
294 if (style != wxSB_FLAT)
295 {
296 // Draw border
297 // For wxSB_NORMAL: paint a grey background, plus 3-d border (one black rectangle)
298 // Inside this, left and top sides (dark grey). Bottom and right (white).
299 // Reverse it for wxSB_RAISED
300
301 dc.SetPen((style == wxSB_RAISED) ? m_mediumShadowPen : m_hilightPen);
302
303 #ifndef __WXPM__
304
305 // Right and bottom lines
306 dc.DrawLine(rect.x + rect.width, rect.y,
307 rect.x + rect.width, rect.y + rect.height);
308 dc.DrawLine(rect.x + rect.width, rect.y + rect.height,
309 rect.x, rect.y + rect.height);
310
311 dc.SetPen((style == wxSB_RAISED) ? m_hilightPen : m_mediumShadowPen);
312
313 // Left and top lines
314 dc.DrawLine(rect.x, rect.y + rect.height,
315 rect.x, rect.y);
316 dc.DrawLine(rect.x, rect.y,
317 rect.x + rect.width, rect.y);
318 #else
319
320 dc.DrawLine(rect.x + rect.width, rect.height + 2,
321 rect.x, rect.height + 2);
322 dc.DrawLine(rect.x + rect.width, rect.y,
323 rect.x + rect.width, rect.y + rect.height);
324
325 dc.SetPen((style == wxSB_RAISED) ? m_hilightPen : m_mediumShadowPen);
326 dc.DrawLine(rect.x, rect.y,
327 rect.x + rect.width, rect.y);
328 dc.DrawLine(rect.x, rect.y + rect.height,
329 rect.x, rect.y);
330 #endif
331 }
332
333 DrawFieldText(dc, rect, i, textHeight);
334 }
335
336 bool wxStatusBarGeneric::GetFieldRect(int n, wxRect& rect) const
337 {
338 wxCHECK_MSG( (n >= 0) && ((size_t)n < m_panes.GetCount()), false,
339 wxT("invalid status bar field index") );
340
341 // We can be called from the user-defined EVT_SIZE handler in which case
342 // the widths haven't been updated yet and we need to do it now. This is
343 // not very efficient as we keep testing the size but there is no other way
344 // to make the code needing the up-to-date fields sizes in its EVT_SIZE to
345 // work.
346 if ( GetClientSize().x != m_lastClientSize.x )
347 {
348 const_cast<wxStatusBarGeneric*>(this)->DoUpdateFieldWidths();
349 }
350
351 if (m_widthsAbs.IsEmpty())
352 return false;
353
354 rect.x = 0;
355 for ( int i = 0; i < n; i++ )
356 rect.x += m_widthsAbs[i];
357 rect.x += m_borderX;
358
359 rect.y = m_borderY;
360 rect.width = m_widthsAbs[n] - 2*m_borderX;
361 rect.height = m_lastClientSize.y - 2*m_borderY;
362
363 return true;
364 }
365
366 int wxStatusBarGeneric::GetFieldFromPoint(const wxPoint& pt) const
367 {
368 if (m_widthsAbs.IsEmpty())
369 return wxNOT_FOUND;
370
371 // NOTE: we explicitly don't take in count the borders since they are only
372 // useful when rendering the status text, not for hit-test computations
373
374 if (pt.y <= 0 || pt.y >= m_lastClientSize.y)
375 return wxNOT_FOUND;
376
377 int x = 0;
378 for ( size_t i = 0; i < m_panes.GetCount(); i++ )
379 {
380 if (pt.x > x && pt.x < x+m_widthsAbs[i])
381 return i;
382
383 x += m_widthsAbs[i];
384 }
385
386 return wxNOT_FOUND;
387 }
388
389 void wxStatusBarGeneric::InitColours()
390 {
391 #if defined(__WXPM__)
392 m_mediumShadowPen = wxPen(wxColour(127, 127, 127));
393 m_hilightPen = *wxWHITE_PEN;
394
395 SetBackgroundColour(*wxLIGHT_GREY);
396 SetForegroundColour(*wxBLACK);
397 #else // !__WXPM__
398 m_mediumShadowPen = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
399 m_hilightPen = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DHILIGHT));
400 #endif // __WXPM__/!__WXPM__
401 }
402
403 void wxStatusBarGeneric::SetMinHeight(int height)
404 {
405 // check that this min height is not less than minimal height for the
406 // current font (min height is as calculated above in Create() except for border)
407 int minHeight = (int)((11*GetCharHeight())/10);
408
409 if ( height > minHeight )
410 SetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, height + 2*m_borderY);
411 }
412
413 wxRect wxStatusBarGeneric::GetSizeGripRect() const
414 {
415 int width, height;
416 wxWindow::DoGetClientSize(&width, &height);
417
418 if (GetLayoutDirection() == wxLayout_RightToLeft)
419 return wxRect(2, 2, height-2, height-4);
420 else
421 return wxRect(width-height-2, 2, height-2, height-4);
422 }
423
424 // ----------------------------------------------------------------------------
425 // wxStatusBarGeneric - event handlers
426 // ----------------------------------------------------------------------------
427
428 void wxStatusBarGeneric::OnPaint(wxPaintEvent& WXUNUSED(event) )
429 {
430 wxPaintDC dc(this);
431
432 #ifdef __WXGTK20__
433 // Draw grip first
434 if ( ShowsSizeGrip() )
435 {
436 const wxRect& rc = GetSizeGripRect();
437 #ifdef __WXGTK3__
438 GtkWidget* toplevel = gtk_widget_get_toplevel(m_widget);
439 if (toplevel && !gtk_window_get_has_resize_grip(GTK_WINDOW(toplevel)))
440 {
441 GtkStyleContext* sc = gtk_widget_get_style_context(toplevel);
442 gtk_style_context_save(sc);
443 gtk_style_context_add_class(sc, GTK_STYLE_CLASS_GRIP);
444 GtkJunctionSides sides = GTK_JUNCTION_CORNER_BOTTOMRIGHT;
445 if (GetLayoutDirection() == wxLayout_RightToLeft)
446 sides = GTK_JUNCTION_CORNER_BOTTOMLEFT;
447 gtk_style_context_set_junction_sides(sc, sides);
448 gtk_render_handle(sc,
449 static_cast<cairo_t*>(dc.GetImpl()->GetCairoContext()),
450 rc.x, rc.y, rc.width, rc.height);
451 gtk_style_context_restore(sc);
452 }
453 #else
454 GdkWindowEdge edge =
455 GetLayoutDirection() == wxLayout_RightToLeft ? GDK_WINDOW_EDGE_SOUTH_WEST :
456 GDK_WINDOW_EDGE_SOUTH_EAST;
457 gtk_paint_resize_grip(gtk_widget_get_style(m_widget),
458 GTKGetDrawingWindow(),
459 gtk_widget_get_state(m_widget),
460 NULL,
461 m_widget,
462 "statusbar",
463 edge,
464 rc.x, rc.y, rc.width, rc.height );
465 #endif
466 }
467 #endif // __WXGTK20__
468
469 if (GetFont().IsOk())
470 dc.SetFont(GetFont());
471
472 // compute char height only once for all panes:
473 int textHeight = dc.GetCharHeight();
474
475 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
476 for (size_t i = 0; i < m_panes.GetCount(); i ++)
477 DrawField(dc, i, textHeight);
478 }
479
480 void wxStatusBarGeneric::OnSysColourChanged(wxSysColourChangedEvent& event)
481 {
482 InitColours();
483
484 // Propagate the event to the non-top-level children
485 wxWindow::OnSysColourChanged(event);
486 }
487
488 #ifdef __WXGTK20__
489 void wxStatusBarGeneric::OnLeftDown(wxMouseEvent& event)
490 {
491 int width, height;
492 GetClientSize(&width, &height);
493
494 GtkWidget* ancestor = gtk_widget_get_toplevel(m_widget);
495 #ifdef __WXGTK3__
496 if (ancestor && gtk_window_get_has_resize_grip(GTK_WINDOW(ancestor)))
497 ancestor = NULL;
498 #endif
499
500 if (ancestor && ShowsSizeGrip() && event.GetX() > width - height)
501 {
502 GdkWindow *source = GTKGetDrawingWindow();
503
504 int org_x = 0;
505 int org_y = 0;
506 gdk_window_get_origin( source, &org_x, &org_y );
507
508 if (GetLayoutDirection() == wxLayout_RightToLeft)
509 {
510 gtk_window_begin_resize_drag (GTK_WINDOW (ancestor),
511 GDK_WINDOW_EDGE_SOUTH_WEST,
512 1,
513 org_x - event.GetX() + GetSize().x ,
514 org_y + event.GetY(),
515 0);
516 }
517 else
518 {
519 gtk_window_begin_resize_drag (GTK_WINDOW (ancestor),
520 GDK_WINDOW_EDGE_SOUTH_EAST,
521 1,
522 org_x + event.GetX(),
523 org_y + event.GetY(),
524 0);
525 }
526 }
527 else
528 {
529 event.Skip( true );
530 }
531 }
532
533 void wxStatusBarGeneric::OnRightDown(wxMouseEvent& event)
534 {
535 int width, height;
536 GetClientSize(&width, &height);
537
538 GtkWidget* ancestor = gtk_widget_get_toplevel(m_widget);
539 #ifdef __WXGTK3__
540 if (ancestor && gtk_window_get_has_resize_grip(GTK_WINDOW(ancestor)))
541 ancestor = NULL;
542 #endif
543
544 if (ancestor && ShowsSizeGrip() && event.GetX() > width - height)
545 {
546 GdkWindow *source = GTKGetDrawingWindow();
547
548 int org_x = 0;
549 int org_y = 0;
550 gdk_window_get_origin( source, &org_x, &org_y );
551
552 gtk_window_begin_move_drag (GTK_WINDOW (ancestor),
553 2,
554 org_x + event.GetX(),
555 org_y + event.GetY(),
556 0);
557 }
558 else
559 {
560 event.Skip( true );
561 }
562 }
563 #endif // __WXGTK20__
564
565 void wxStatusBarGeneric::OnSize(wxSizeEvent& event)
566 {
567 DoUpdateFieldWidths();
568
569 event.Skip();
570 }
571
572 #endif // wxUSE_STATUSBAR