1. Don't draw our own custom label if we don't have custom foreground colour,
[wxWidgets.git] / src / msw / statbox.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/statbox.cpp
3 // Purpose: wxStaticBox
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
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_STATBOX
28
29 #include "wx/statbox.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/app.h"
33 #include "wx/dcclient.h"
34 #include "wx/dcmemory.h"
35 #include "wx/image.h"
36 #endif
37
38 #include "wx/notebook.h"
39 #include "wx/sysopt.h"
40
41 #include "wx/msw/uxtheme.h"
42 #include "wx/msw/private.h"
43 #include "wx/msw/missing.h"
44
45 // the values coincide with those in tmschema.h
46 #define BP_GROUPBOX 4
47
48 #define GBS_NORMAL 1
49
50 #define TMT_FONT 210
51
52 // ----------------------------------------------------------------------------
53 // wxWin macros
54 // ----------------------------------------------------------------------------
55
56 #if wxUSE_EXTENDED_RTTI
57 WX_DEFINE_FLAGS( wxStaticBoxStyle )
58
59 wxBEGIN_FLAGS( wxStaticBoxStyle )
60 // new style border flags, we put them first to
61 // use them for streaming out
62 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
63 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
64 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
65 wxFLAGS_MEMBER(wxBORDER_RAISED)
66 wxFLAGS_MEMBER(wxBORDER_STATIC)
67 wxFLAGS_MEMBER(wxBORDER_NONE)
68
69 // old style border flags
70 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
71 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
72 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
73 wxFLAGS_MEMBER(wxRAISED_BORDER)
74 wxFLAGS_MEMBER(wxSTATIC_BORDER)
75 wxFLAGS_MEMBER(wxBORDER)
76
77 // standard window styles
78 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
79 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
80 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
81 wxFLAGS_MEMBER(wxWANTS_CHARS)
82 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
83 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
84 wxFLAGS_MEMBER(wxVSCROLL)
85 wxFLAGS_MEMBER(wxHSCROLL)
86
87 wxEND_FLAGS( wxStaticBoxStyle )
88
89 IMPLEMENT_DYNAMIC_CLASS_XTI(wxStaticBox, wxControl,"wx/statbox.h")
90
91 wxBEGIN_PROPERTIES_TABLE(wxStaticBox)
92 wxPROPERTY( Label,wxString, SetLabel, GetLabel, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
93 wxPROPERTY_FLAGS( WindowStyle , wxStaticBoxStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
94 /*
95 TODO PROPERTIES :
96 label
97 */
98 wxEND_PROPERTIES_TABLE()
99
100 wxBEGIN_HANDLERS_TABLE(wxStaticBox)
101 wxEND_HANDLERS_TABLE()
102
103 wxCONSTRUCTOR_6( wxStaticBox , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
104 #else
105 IMPLEMENT_DYNAMIC_CLASS(wxStaticBox, wxControl)
106 #endif
107
108 // ============================================================================
109 // implementation
110 // ============================================================================
111
112 // ----------------------------------------------------------------------------
113 // wxStaticBox
114 // ----------------------------------------------------------------------------
115
116 bool wxStaticBox::Create(wxWindow *parent,
117 wxWindowID id,
118 const wxString& label,
119 const wxPoint& pos,
120 const wxSize& size,
121 long style,
122 const wxString& name)
123 {
124 if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
125 return false;
126
127 if ( !MSWCreateControl(wxT("BUTTON"), label, pos, size) )
128 return false;
129
130 // Always use LTR layout. Otherwise, the label would be mirrored.
131 SetLayoutDirection(wxLayout_LeftToRight);
132
133 #ifndef __WXWINCE__
134 if (!wxSystemOptions::IsFalse(wxT("msw.staticbox.optimized-paint")))
135 Connect(wxEVT_PAINT, wxPaintEventHandler(wxStaticBox::OnPaint));
136 #endif // !__WXWINCE__
137
138 return true;
139 }
140
141 wxBorder wxStaticBox::GetDefaultBorder() const
142 {
143 return wxBORDER_NONE;
144 }
145
146 WXDWORD wxStaticBox::MSWGetStyle(long style, WXDWORD *exstyle) const
147 {
148 long styleWin = wxStaticBoxBase::MSWGetStyle(style, exstyle);
149
150 // no need for it anymore, must be removed for wxRadioBox child
151 // buttons to be able to repaint themselves
152 styleWin &= ~WS_CLIPCHILDREN;
153
154 if ( exstyle )
155 {
156 #ifndef __WXWINCE__
157 if (wxSystemOptions::IsFalse(wxT("msw.staticbox.optimized-paint")))
158 *exstyle = WS_EX_TRANSPARENT;
159 else
160 #endif
161 *exstyle = 0;
162 }
163
164 styleWin |= BS_GROUPBOX;
165
166 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
167 {
168 // Make sure label is on the right
169 styleWin |= BS_RIGHT;
170 }
171
172 return styleWin;
173 }
174
175 wxSize wxStaticBox::DoGetBestSize() const
176 {
177 int cx, cy;
178 wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
179
180 int wBox;
181 GetTextExtent(GetLabelText(wxGetWindowText(m_hWnd)), &wBox, &cy);
182
183 wBox += 3*cx;
184 int hBox = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy);
185
186 wxSize best(wBox, hBox);
187 CacheBestSize(best);
188 return best;
189 }
190
191 void wxStaticBox::GetBordersForSizer(int *borderTop, int *borderOther) const
192 {
193 wxStaticBoxBase::GetBordersForSizer(borderTop, borderOther);
194
195 // need extra space, don't know how much but this seems to be enough
196 *borderTop += GetCharHeight()/3;
197 }
198
199 // all the hacks below are not necessary for WinCE
200 #ifndef __WXWINCE__
201
202 WXLRESULT wxStaticBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
203 {
204 if ( nMsg == WM_NCHITTEST )
205 {
206 // This code breaks some other processing such as enter/leave tracking
207 // so it's off by default.
208
209 static int s_useHTClient = -1;
210 if (s_useHTClient == -1)
211 s_useHTClient = wxSystemOptions::GetOptionInt(wxT("msw.staticbox.htclient"));
212 if (s_useHTClient == 1)
213 {
214 int xPos = GET_X_LPARAM(lParam);
215 int yPos = GET_Y_LPARAM(lParam);
216
217 ScreenToClient(&xPos, &yPos);
218
219 // Make sure you can drag by the top of the groupbox, but let
220 // other (enclosed) controls get mouse events also
221 if ( yPos < 10 )
222 return (long)HTCLIENT;
223 }
224 }
225
226 if ( nMsg == WM_PRINTCLIENT )
227 {
228 // we have to process WM_PRINTCLIENT ourselves as otherwise child
229 // windows' background (eg buttons in radio box) would never be drawn
230 // unless we have a parent with non default background
231
232 // so check first if we have one
233 if ( !HandlePrintClient((WXHDC)wParam) )
234 {
235 // no, we don't, erase the background ourselves
236 // (don't use our own) - see PaintBackground for explanation
237 wxBrush brush(GetParent()->GetBackgroundColour());
238 wxFillRect(GetHwnd(), (HDC)wParam, GetHbrushOf(brush));
239 }
240
241 return 0;
242 }
243
244 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
245 }
246
247 // ----------------------------------------------------------------------------
248 // static box drawing
249 // ----------------------------------------------------------------------------
250
251 /*
252 We draw the static box ourselves because it's the only way to prevent it
253 from flickering horribly on resize (because everything inside the box is
254 erased twice: once when the box itself is repainted and second time when
255 the control inside it is repainted) without using WS_EX_TRANSPARENT style as
256 we used to do and which resulted in other problems.
257 */
258
259 // MSWGetRegionWithoutSelf helper: removes the given rectangle from region
260 static inline void
261 SubtractRectFromRgn(HRGN hrgn, int left, int top, int right, int bottom)
262 {
263 AutoHRGN hrgnRect(::CreateRectRgn(left, top, right, bottom));
264 if ( !hrgnRect )
265 {
266 wxLogLastError(_T("CreateRectRgn()"));
267 return;
268 }
269
270 ::CombineRgn(hrgn, hrgn, hrgnRect, RGN_DIFF);
271 }
272
273 void wxStaticBox::MSWGetRegionWithoutSelf(WXHRGN hRgn, int w, int h)
274 {
275 HRGN hrgn = (HRGN)hRgn;
276
277 // remove the area occupied by the static box borders from the region
278 int borderTop, border;
279 GetBordersForSizer(&borderTop, &border);
280
281 // top
282 SubtractRectFromRgn(hrgn, 0, 0, w, borderTop);
283
284 // bottom
285 SubtractRectFromRgn(hrgn, 0, h - border, w, h);
286
287 // left
288 SubtractRectFromRgn(hrgn, 0, 0, border, h);
289
290 // right
291 SubtractRectFromRgn(hrgn, w - border, 0, w, h);
292 }
293
294 WXHRGN wxStaticBox::MSWGetRegionWithoutChildren()
295 {
296 RECT rc;
297 ::GetWindowRect(GetHwnd(), &rc);
298 HRGN hrgn = ::CreateRectRgn(rc.left, rc.top, rc.right + 1, rc.bottom + 1);
299 bool foundThis = false;
300
301 // iterate over all child windows (not just wxWindows but all windows)
302 for ( HWND child = ::GetWindow(GetHwndOf(GetParent()), GW_CHILD);
303 child;
304 child = ::GetWindow(child, GW_HWNDNEXT) )
305 {
306 if ( ! ::IsWindowVisible(child) )
307 {
308 // if the window isn't visible then it doesn't need clipped
309 continue;
310 }
311
312 LONG style = ::GetWindowLong(child, GWL_STYLE);
313 wxString str(wxGetWindowClass(child));
314 str.UpperCase();
315 if ( str == wxT("BUTTON") && (style & BS_GROUPBOX) == BS_GROUPBOX )
316 {
317 if ( child == GetHwnd() )
318 foundThis = true;
319
320 // Any static boxes below this one in the Z-order can't be clipped
321 // since if we have the case where a static box with a low Z-order
322 // is nested inside another static box with a high Z-order then the
323 // nested static box would be painted over. Doing it this way
324 // unfortunately results in flicker if the Z-order of nested static
325 // boxes is not inside (lowest) to outside (highest) but at least
326 // they are still shown.
327 if ( foundThis )
328 continue;
329 }
330
331 ::GetWindowRect(child, &rc);
332 if ( ::RectInRegion(hrgn, &rc) )
333 {
334 // need to remove WS_CLIPSIBLINGS from all sibling windows
335 // that are within this staticbox if set
336 if ( style & WS_CLIPSIBLINGS )
337 {
338 style &= ~WS_CLIPSIBLINGS;
339 ::SetWindowLong(child, GWL_STYLE, style);
340
341 // MSDN: "If you have changed certain window data using
342 // SetWindowLong, you must call SetWindowPos to have the
343 // changes take effect."
344 ::SetWindowPos(child, NULL, 0, 0, 0, 0,
345 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
346 SWP_FRAMECHANGED);
347 }
348
349 AutoHRGN hrgnChild(::CreateRectRgnIndirect(&rc));
350 ::CombineRgn(hrgn, hrgn, hrgnChild, RGN_DIFF);
351 }
352 }
353
354 return (WXHRGN)hrgn;
355 }
356
357 // helper for OnPaint(): really erase the background, i.e. do it even if we
358 // don't have any non default brush for doing it (DoEraseBackground() doesn't
359 // do anything in such case)
360 void wxStaticBox::PaintBackground(wxDC& dc, const RECT& rc)
361 {
362 // note that we do not use the box background colour here, it shouldn't
363 // apply to its interior for several reasons:
364 // 1. wxGTK doesn't do it
365 // 2. controls inside the box don't get correct bg colour because they
366 // are not our children so we'd have some really ugly colour mix if
367 // we did it
368 // 3. this is backwards compatible behaviour and some people rely on it,
369 // see http://groups.google.com/groups?selm=4252E932.3080801%40able.es
370 wxWindow *parent = GetParent();
371 HBRUSH hbr = (HBRUSH)parent->MSWGetBgBrush(dc.GetHDC(), GetHWND());
372
373 // if there is no special brush for painting this control, just use the
374 // solid background colour
375 wxBrush brush;
376 if ( !hbr )
377 {
378 brush = wxBrush(parent->GetBackgroundColour());
379 hbr = GetHbrushOf(brush);
380 }
381
382 ::FillRect(GetHdcOf(dc), &rc, hbr);
383 }
384
385 void wxStaticBox::PaintForeground(wxDC& dc, const RECT& rc)
386 {
387 MSWDefWindowProc(WM_PAINT, (WPARAM)GetHdcOf(dc), 0);
388
389 // when using XP themes, neither setting the text colour nor transparent
390 // background mode doesn't change anything: the static box def window proc
391 // still draws the label in its own colours, so we need to redraw the text
392 // ourselves if we have a non default fg colour
393 if ( m_hasFgCol && wxUxThemeEngine::GetIfActive() )
394 {
395 // draw over the text in default colour in our colour
396 HDC hdc = GetHdcOf(dc);
397 ::SetTextColor(hdc, GetForegroundColour().GetPixel());
398
399 const bool rtl = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft;
400 if ( rtl )
401 ::SetTextAlign(hdc, TA_RTLREADING | TA_RIGHT);
402
403 // Get dimensions of the label
404 const wxString label = GetLabel();
405 int width, height;
406 dc.GetTextExtent(wxStripMenuCodes(label, wxStrip_Mnemonics),
407 &width, &height);
408
409 int x;
410 int y = height;
411
412 // first we need to correctly paint the background of the label
413 // as Windows ignores the brush offset when doing it
414 //
415 // FIXME: value of x is hardcoded as this is what it is on my system,
416 // no idea if it's true everywhere
417 RECT dimensions = {0, 0, 0, y};
418 if ( !rtl )
419 {
420 x = 9;
421 dimensions.left = x;
422 dimensions.right = x + width;
423 }
424 else
425 {
426 x = rc.right - 7;
427 dimensions.left = x - width;
428 dimensions.right = x;
429 }
430
431 // need to adjust the rectangle to cover all the label background
432 dimensions.left -= 2;
433 dimensions.right += 2;
434 dimensions.bottom += 2;
435 PaintBackground(dc, dimensions);
436
437 // choose the correct font
438 AutoHFONT font;
439 SelectInHDC selFont;
440 if ( m_hasFont )
441 {
442 selFont.Init(hdc, GetHfontOf(GetFont()));
443 }
444 else // no font set, use the one set by the theme
445 {
446 wxUxThemeHandle hTheme(this, L"BUTTON");
447 if ( hTheme )
448 {
449 // GetThemeFont() expects its parameter to be LOGFONTW and not
450 // LOGFONTA even in ANSI programs and will happily corrupt
451 // memory after the struct end if we pass a LOGFONTA (which is
452 // smaller) to it!
453 LOGFONTW lfw;
454 if ( wxUxThemeEngine::Get()->GetThemeFont
455 (
456 hTheme,
457 hdc,
458 BP_GROUPBOX,
459 GBS_NORMAL,
460 TMT_FONT,
461 (LOGFONT *)&lfw
462 ) == S_OK )
463 {
464 #if wxUSE_UNICODE
465 // ok, no conversion necessary
466 const LOGFONT& lf = lfw;
467 #else // !wxUSE_UNICODE
468 // most of the fields are the same in LOGFONTA and LOGFONTW
469 LOGFONT lf;
470 memcpy(&lf, &lfw, sizeof(lf));
471
472 // but the face name must be converted
473 WideCharToMultiByte(CP_ACP, 0, lfw.lfFaceName, -1,
474 lf.lfFaceName, sizeof(lf.lfFaceName),
475 NULL, NULL);
476 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
477
478 font.Init(lf);
479 if ( font )
480 selFont.Init(hdc, font);
481 }
482 }
483 }
484
485 // now draw the text
486 if ( !rtl )
487 {
488 RECT rc2 = { x, 0, x + width, y };
489 ::DrawText(hdc, label, label.length(), &rc2,
490 DT_SINGLELINE | DT_VCENTER);
491 }
492 else // RTL
493 {
494 RECT rc2 = { x, 0, x - width, y };
495 ::DrawText(hdc, label, label.length(), &rc2,
496 DT_SINGLELINE | DT_VCENTER | DT_RTLREADING);
497 }
498 }
499 }
500
501 void wxStaticBox::OnPaint(wxPaintEvent& WXUNUSED(event))
502 {
503 RECT rc;
504 ::GetClientRect(GetHwnd(), &rc);
505
506 // draw the entire box in a memory DC
507 wxMemoryDC memdc;
508 wxBitmap bitmap(rc.right, rc.bottom);
509 memdc.SelectObject(bitmap);
510
511 PaintBackground(memdc, rc);
512 PaintForeground(memdc, rc);
513
514 // now only blit the static box border itself, not the interior, to avoid
515 // flicker when background is drawn below
516 //
517 // note that it seems to be faster to do 4 small blits here and then paint
518 // directly into wxPaintDC than painting background in wxMemoryDC and then
519 // blitting everything at once to wxPaintDC, this is why we do it like this
520 wxPaintDC dc(this);
521 int borderTop, border;
522 GetBordersForSizer(&borderTop, &border);
523
524 // top
525 dc.Blit(border, 0, rc.right - border, borderTop,
526 &memdc, border, 0);
527 // bottom
528 dc.Blit(border, rc.bottom - border, rc.right - border, rc.bottom,
529 &memdc, border, rc.bottom - border);
530 // left
531 dc.Blit(0, 0, border, rc.bottom,
532 &memdc, 0, 0);
533 // right
534 dc.Blit(rc.right - border, 0, rc.right, rc.bottom,
535 &memdc, rc.right - border, 0);
536
537
538 // create the region excluding box children
539 AutoHRGN hrgn((HRGN)MSWGetRegionWithoutChildren());
540 RECT rcWin;
541 ::GetWindowRect(GetHwnd(), &rcWin);
542 ::OffsetRgn(hrgn, -rcWin.left, -rcWin.top);
543
544 // and also the box itself
545 MSWGetRegionWithoutSelf((WXHRGN) hrgn, rc.right, rc.bottom);
546 HDCClipper clipToBg(GetHdcOf(dc), hrgn);
547
548 // paint the inside of the box (excluding box itself and child controls)
549 PaintBackground(dc, rc);
550 }
551
552 #endif // !__WXWINCE__
553
554 #endif // wxUSE_STATBOX