]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
added EVT_LIST_COMMAND_CACHE_HINT, implemented it for MSW and test in the sample...
[wxWidgets.git] / src / msw / button.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/button.cpp
3 // Purpose: wxButton
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart and Markus Holzem
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "button.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_BUTTON
32
33 #ifndef WX_PRECOMP
34 #include "wx/button.h"
35 #include "wx/brush.h"
36 #include "wx/panel.h"
37 #include "wx/bmpbuttn.h"
38 #include "wx/settings.h"
39 #include "wx/dcscreen.h"
40 #endif
41
42 #include "wx/msw/private.h"
43
44 // ----------------------------------------------------------------------------
45 // macros
46 // ----------------------------------------------------------------------------
47
48 IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
49
50 // this macro tries to adjust the default button height to a reasonable value
51 // using the char height as the base
52 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
53
54 // ============================================================================
55 // implementation
56 // ============================================================================
57
58 // ----------------------------------------------------------------------------
59 // creation/destruction
60 // ----------------------------------------------------------------------------
61
62 bool wxButton::Create(wxWindow *parent,
63 wxWindowID id,
64 const wxString& label,
65 const wxPoint& pos,
66 const wxSize& size,
67 long style,
68 const wxValidator& validator,
69 const wxString& name)
70 {
71 if ( !CreateBase(parent, id, pos, size, style, validator, name) )
72 return FALSE;
73
74 parent->AddChild((wxButton *)this);
75
76 m_backgroundColour = parent->GetBackgroundColour();
77 m_foregroundColour = parent->GetForegroundColour();
78
79 long msStyle = WS_VISIBLE | WS_TABSTOP | WS_CHILD /* | WS_CLIPSIBLINGS */ ;
80
81 if ( m_windowStyle & wxCLIP_SIBLINGS )
82 msStyle |= WS_CLIPSIBLINGS;
83
84 #ifdef __WIN32__
85 if(m_windowStyle & wxBU_LEFT)
86 msStyle |= BS_LEFT;
87 if(m_windowStyle & wxBU_RIGHT)
88 msStyle |= BS_RIGHT;
89 if(m_windowStyle & wxBU_TOP)
90 msStyle |= BS_TOP;
91 if(m_windowStyle & wxBU_BOTTOM)
92 msStyle |= BS_BOTTOM;
93 #endif
94
95 m_hWnd = (WXHWND)CreateWindowEx
96 (
97 MakeExtendedStyle(m_windowStyle),
98 wxT("BUTTON"),
99 label,
100 msStyle,
101 0, 0, 0, 0,
102 GetWinHwnd(parent),
103 (HMENU)m_windowId,
104 wxGetInstance(),
105 NULL
106 );
107
108 if (m_hWnd == 0)
109 {
110 wxString msg;
111 #ifdef __WIN16__
112 msg.Printf(wxT("CreateWindowEx failed"));
113 #else
114 msg.Printf(wxT("CreateWindowEx failed with error number %ld"), (long) GetLastError());
115 #endif
116 wxFAIL_MSG(msg);
117 }
118
119 // Subclass again for purposes of dialog editing mode
120 SubclassWin(m_hWnd);
121
122 SetFont(parent->GetFont());
123
124 SetSize(pos.x, pos.y, size.x, size.y);
125
126 return TRUE;
127 }
128
129 wxButton::~wxButton()
130 {
131 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
132 if ( panel )
133 {
134 if ( panel->GetDefaultItem() == this )
135 {
136 // don't leave the panel with invalid default item
137 panel->SetDefaultItem(NULL);
138 }
139 }
140 }
141
142 // ----------------------------------------------------------------------------
143 // size management including autosizing
144 // ----------------------------------------------------------------------------
145
146 wxSize wxButton::DoGetBestSize() const
147 {
148 wxString label = wxGetWindowText(GetHWND());
149 int wBtn;
150 GetTextExtent(label, &wBtn, NULL);
151
152 int wChar, hChar;
153 wxGetCharSize(GetHWND(), &wChar, &hChar, &GetFont());
154
155 // add a margin - the button is wider than just its label
156 wBtn += 3*wChar;
157
158 // the button height is proportional to the height of the font used
159 int hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar);
160
161 wxSize sz = GetDefaultSize();
162 if (wBtn > sz.x) sz.x = wBtn;
163 if (hBtn > sz.y) sz.y = hBtn;
164
165 return sz;
166 }
167
168 /* static */
169 wxSize wxButtonBase::GetDefaultSize()
170 {
171 static wxSize s_sizeBtn;
172
173 if ( s_sizeBtn.x == 0 )
174 {
175 wxScreenDC dc;
176 dc.SetFont(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT));
177
178 // the size of a standard button in the dialog units is 50x14,
179 // translate this to pixels
180 // NB1: the multipliers come from the Windows convention
181 // NB2: the extra +1/+2 were needed to get the size be the same as the
182 // size of the buttons in the standard dialog - I don't know how
183 // this happens, but on my system this size is 75x23 in pixels and
184 // 23*8 isn't even divisible by 14... Would be nice to understand
185 // why these constants are needed though!
186 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
187 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
188 }
189
190 return s_sizeBtn;
191 }
192
193 // ----------------------------------------------------------------------------
194 // set this button as the default one in its panel
195 // ----------------------------------------------------------------------------
196
197 void wxButton::SetDefault()
198 {
199 wxWindow *parent = GetParent();
200 wxButton *btnOldDefault = NULL;
201 wxPanel *panel = wxDynamicCast(parent, wxPanel);
202 if ( panel )
203 {
204 btnOldDefault = panel->GetDefaultItem();
205 panel->SetDefaultItem(this);
206 }
207
208 if ( parent )
209 {
210 SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
211 }
212
213 if ( btnOldDefault )
214 {
215 // remove the BS_DEFPUSHBUTTON style from the other button
216 long style = GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
217
218 // don't do it with the owner drawn buttons because it will reset
219 // BS_OWNERDRAW style bit too (BS_OWNERDRAW & BS_DEFPUSHBUTTON != 0)!
220 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
221 {
222 style &= ~BS_DEFPUSHBUTTON;
223 SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
224 }
225 else
226 {
227 // redraw the button - it will notice itself that it's not the
228 // default one any longer
229 btnOldDefault->Refresh();
230 }
231 }
232
233 // set this button as the default
234 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
235 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
236 {
237 style |= BS_DEFPUSHBUTTON;
238 SendMessage(GetHwnd(), BM_SETSTYLE, style, 1L);
239 }
240 }
241
242 // ----------------------------------------------------------------------------
243 // helpers
244 // ----------------------------------------------------------------------------
245
246 bool wxButton::SendClickEvent()
247 {
248 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
249 event.SetEventObject(this);
250
251 return ProcessCommand(event);
252 }
253
254 void wxButton::Command(wxCommandEvent & event)
255 {
256 ProcessCommand(event);
257 }
258
259 // ----------------------------------------------------------------------------
260 // event/message handlers
261 // ----------------------------------------------------------------------------
262
263 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
264 {
265 bool processed = FALSE;
266 switch ( param )
267 {
268 case 1: // message came from an accelerator
269 case BN_CLICKED: // normal buttons send this
270 case BN_DOUBLECLICKED: // owner-drawn ones also send this
271 processed = SendClickEvent();
272 break;
273 }
274
275 return processed;
276 }
277
278 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
279 {
280 // when we receive focus, we want to become the default button in our
281 // parent panel
282 if ( nMsg == WM_SETFOCUS )
283 {
284 SetDefault();
285
286 // let the default processign take place too
287 }
288 else if ( nMsg == WM_LBUTTONDBLCLK )
289 {
290 // emulate a click event to force an owner-drawn button to change its
291 // appearance - without this, it won't do it
292 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
293
294 // and conitnue with processing the message normally as well
295 }
296
297 // let the base class do all real processing
298 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
299 }
300
301 // ----------------------------------------------------------------------------
302 // owner-drawn buttons support
303 // ----------------------------------------------------------------------------
304
305 #ifdef __WIN32__
306
307 // drawing helpers
308
309 static void DrawButtonText(HDC hdc,
310 RECT *pRect,
311 const wxString& text,
312 COLORREF col)
313 {
314 COLORREF colOld = SetTextColor(hdc, col);
315 int modeOld = SetBkMode(hdc, TRANSPARENT);
316
317 DrawText(hdc, text, text.length(), pRect,
318 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
319
320 SetBkMode(hdc, modeOld);
321 SetTextColor(hdc, colOld);
322 }
323
324 static void DrawRect(HDC hdc, const RECT& r)
325 {
326 MoveToEx(hdc, r.left, r.top, NULL);
327 LineTo(hdc, r.right, r.top);
328 LineTo(hdc, r.right, r.bottom);
329 LineTo(hdc, r.left, r.bottom);
330 LineTo(hdc, r.left, r.top);
331 }
332
333 void wxButton::MakeOwnerDrawn()
334 {
335 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
336 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
337 {
338 // make it so
339 style |= BS_OWNERDRAW;
340 SetWindowLong(GetHwnd(), GWL_STYLE, style);
341 }
342 }
343
344 bool wxButton::SetBackgroundColour(const wxColour &colour)
345 {
346 if ( !wxControl::SetBackgroundColour(colour) )
347 {
348 // nothing to do
349 return FALSE;
350 }
351
352 MakeOwnerDrawn();
353
354 Refresh();
355
356 return TRUE;
357 }
358
359 bool wxButton::SetForegroundColour(const wxColour &colour)
360 {
361 if ( !wxControl::SetForegroundColour(colour) )
362 {
363 // nothing to do
364 return FALSE;
365 }
366
367 MakeOwnerDrawn();
368
369 Refresh();
370
371 return TRUE;
372 }
373
374 /*
375 The button frame looks like this normally:
376
377 WWWWWWWWWWWWWWWWWWB
378 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
379 WH GB H = light grey (LIGHT)
380 WH GB G = dark grey (SHADOW)
381 WH GB B = black (DKSHADOW)
382 WH GB
383 WGGGGGGGGGGGGGGGGGB
384 BBBBBBBBBBBBBBBBBBB
385
386 When the button is selected, the button becomes like this (the total button
387 size doesn't change):
388
389 BBBBBBBBBBBBBBBBBBB
390 BWWWWWWWWWWWWWWWWBB
391 BWHHHHHHHHHHHHHHGBB
392 BWH GBB
393 BWH GBB
394 BWGGGGGGGGGGGGGGGBB
395 BBBBBBBBBBBBBBBBBBB
396 BBBBBBBBBBBBBBBBBBB
397
398 When the button is pushed (while selected) it is like:
399
400 BBBBBBBBBBBBBBBBBBB
401 BGGGGGGGGGGGGGGGGGB
402 BG GB
403 BG GB
404 BG GB
405 BG GB
406 BGGGGGGGGGGGGGGGGGB
407 BBBBBBBBBBBBBBBBBBB
408 */
409
410 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
411 bool selected, bool pushed)
412 {
413 RECT r;
414 CopyRect(&r, &rectBtn);
415
416 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
417 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
418 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
419 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
420
421 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
422
423 r.right--;
424 r.bottom--;
425
426 if ( pushed )
427 {
428 DrawRect(hdc, r);
429
430 (void)SelectObject(hdc, hpenGrey);
431 InflateRect(&r, -1, -1);
432
433 DrawRect(hdc, r);
434 }
435 else // !pushed
436 {
437 if ( selected )
438 {
439 DrawRect(hdc, r);
440
441 InflateRect(&r, -1, -1);
442 }
443
444 MoveToEx(hdc, r.left, r.bottom, NULL);
445 LineTo(hdc, r.right, r.bottom);
446 LineTo(hdc, r.right, r.top - 1);
447
448 (void)SelectObject(hdc, hpenWhite);
449 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
450 LineTo(hdc, r.left, r.top);
451 LineTo(hdc, r.right, r.top);
452
453 (void)SelectObject(hdc, hpenLightGr);
454 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
455 LineTo(hdc, r.left + 1, r.top + 1);
456 LineTo(hdc, r.right - 1, r.top + 1);
457
458 (void)SelectObject(hdc, hpenGrey);
459 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
460 LineTo(hdc, r.right - 1, r.bottom - 1);
461 LineTo(hdc, r.right - 1, r.top);
462 }
463
464 (void)SelectObject(hdc, hpenOld);
465 DeleteObject(hpenWhite);
466 DeleteObject(hpenLightGr);
467 DeleteObject(hpenGrey);
468 DeleteObject(hpenBlack);
469 }
470
471 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
472 {
473 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
474
475 RECT rectBtn;
476 CopyRect(&rectBtn, &lpDIS->rcItem);
477
478 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
479 colFg = wxColourToRGB(GetForegroundColour());
480
481 HDC hdc = lpDIS->hDC;
482 UINT state = lpDIS->itemState;
483
484 // first, draw the background
485 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
486
487 FillRect(hdc, &rectBtn, hbrushBackground);
488
489 // draw the border for the current state
490 bool selected = (state & ODS_SELECTED) != 0;
491 if ( !selected )
492 {
493 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
494 if ( panel )
495 {
496 selected = panel->GetDefaultItem() == this;
497 }
498 }
499 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
500
501 DrawButtonFrame(hdc, rectBtn, selected, pushed);
502
503 // draw the focus rect if needed
504 if ( state & ODS_FOCUS )
505 {
506 RECT rectFocus;
507 CopyRect(&rectFocus, &rectBtn);
508
509 // I don't know where does this constant come from, but this is how
510 // Windows draws them
511 InflateRect(&rectFocus, -4, -4);
512
513 DrawFocusRect(hdc, &rectFocus);
514 }
515
516 if ( pushed )
517 {
518 // the label is shifted by 1 pixel to create "pushed" effect
519 OffsetRect(&rectBtn, 1, 1);
520 }
521
522 DrawButtonText(hdc, &rectBtn, GetLabel(),
523 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
524 : colFg);
525
526 ::DeleteObject(hbrushBackground);
527
528 return TRUE;
529 }
530
531 #endif // __WIN32__
532
533 #endif // wxUSE_BUTTON
534