1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/button.cpp
3 // Purpose:     wxButton
4 // Author:      Julian Smart
5 // Modified by:
6 // Created:     04/01/98
7 // Copyright:   (c) Julian Smart
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21 
22 
23 #if wxUSE_BUTTON
24 
25 #include "wx/button.h"
26 
27 #ifndef WX_PRECOMP
28     #include "wx/app.h"
29     #include "wx/brush.h"
30     #include "wx/panel.h"
31     #include "wx/bmpbuttn.h"
32     #include "wx/settings.h"
33     #include "wx/dcscreen.h"
34     #include "wx/dcclient.h"
35     #include "wx/toplevel.h"
36     #include "wx/msw/wrapcctl.h"
37     #include "wx/msw/private.h"
38     #include "wx/msw/missing.h"
39 #endif
40 
41 #include "wx/imaglist.h"
42 #include "wx/stockitem.h"
43 #include "wx/msw/private/button.h"
44 #include "wx/msw/private/dc.h"
45 #include "wx/private/window.h"
46 
47 #if wxUSE_MARKUP
48     #include "wx/generic/private/markuptext.h"
49 #endif // wxUSE_MARKUP
50 
51 // set the value for BCM_SETSHIELD (for the UAC shield) if it's not defined in
52 // the header
53 #ifndef BCM_SETSHIELD
54     #define BCM_SETSHIELD       0x160c
55 #endif
56 
57 // ----------------------------------------------------------------------------
58 // macros
59 // ----------------------------------------------------------------------------
60 
wxBEGIN_EVENT_TABLE(wxButton,wxButtonBase)61 wxBEGIN_EVENT_TABLE(wxButton, wxButtonBase)
62     EVT_CHAR_HOOK(wxButton::OnCharHook)
63 wxEND_EVENT_TABLE()
64 
65 // ============================================================================
66 // implementation
67 // ============================================================================
68 
69 // ----------------------------------------------------------------------------
70 // creation/destruction
71 // ----------------------------------------------------------------------------
72 
73 bool wxButton::Create(wxWindow *parent,
74                       wxWindowID id,
75                       const wxString& lbl,
76                       const wxPoint& pos,
77                       const wxSize& size,
78                       long style,
79                       const wxValidator& validator,
80                       const wxString& name)
81 {
82     wxString label;
83     if ( !(style & wxBU_NOTEXT) )
84     {
85         label = lbl;
86         if (label.empty() && wxIsStockID(id))
87         {
88             // On Windows, some buttons aren't supposed to have mnemonics
89             label = wxGetStockLabel
90                     (
91                         id,
92                         id == wxID_OK || id == wxID_CANCEL || id == wxID_CLOSE
93                             ? wxSTOCK_NOFLAGS
94                             : wxSTOCK_WITH_MNEMONIC
95                     );
96         }
97     }
98 
99     if ( !CreateControl(parent, id, pos, size, style, validator, name) )
100         return false;
101 
102     WXDWORD exstyle;
103     WXDWORD msStyle = MSWGetStyle(style, &exstyle);
104 
105     // if the label contains several lines we must explicitly tell the button
106     // about it or it wouldn't draw it correctly ("\n"s would just appear as
107     // black boxes)
108     //
109     // NB: we do it here and not in MSWGetStyle() because we need the label
110     //     value and the label is not set yet when MSWGetStyle() is called
111     msStyle |= wxMSWButton::GetMultilineStyle(label);
112 
113     return MSWCreateControl(wxT("BUTTON"), msStyle, pos, size, label, exstyle);
114 }
115 
~wxButton()116 wxButton::~wxButton()
117 {
118     wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
119     if ( tlw && tlw->GetTmpDefaultItem() == this )
120     {
121         UnsetTmpDefault();
122     }
123 }
124 
125 // ----------------------------------------------------------------------------
126 // flags
127 // ----------------------------------------------------------------------------
128 
MSWGetStyle(long style,WXDWORD * exstyle) const129 WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
130 {
131     // buttons never have an external border, they draw their own one
132     WXDWORD msStyle = wxControl::MSWGetStyle
133                       (
134                         (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
135                       );
136 
137     // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
138     // each other in any resizable dialog which has more than one button in
139     // the bottom
140     msStyle |= WS_CLIPSIBLINGS;
141 
142     // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
143     // and wxBU_RIGHT to get BS_CENTER!
144     if ( style & wxBU_LEFT )
145         msStyle |= BS_LEFT;
146     if ( style & wxBU_RIGHT )
147         msStyle |= BS_RIGHT;
148     if ( style & wxBU_TOP )
149         msStyle |= BS_TOP;
150     if ( style & wxBU_BOTTOM )
151         msStyle |= BS_BOTTOM;
152     // flat 2d buttons
153     if ( style & wxNO_BORDER )
154         msStyle |= BS_FLAT;
155 
156     return msStyle;
157 }
158 
159 /* static */
GetDefaultSize(wxWindow * win)160 wxSize wxButtonBase::GetDefaultSize(wxWindow* win)
161 {
162     static wxPrivate::DpiDependentValue<wxSize> s_sizeBtn;
163 
164     if ( s_sizeBtn.HasChanged(win) )
165     {
166         wxSize base;
167         if ( win )
168         {
169             wxClientDC dc(win);
170             dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
171             base = wxPrivate::GetAverageASCIILetterSize(dc);
172         }
173         else
174         {
175             wxScreenDC dc;
176             dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
177             base = wxPrivate::GetAverageASCIILetterSize(dc);
178         }
179 
180         // The size of a standard button in the dialog units is 50x14,
181         // translate this to pixels.
182         //
183         // Windows' computes dialog units using average character width over
184         // upper- and lower-case ASCII alphabet and not using the average
185         // character width metadata stored in the font; see
186         // http://support.microsoft.com/default.aspx/kb/145994 for detailed
187         // discussion.
188         //
189         // NB: wxMulDivInt32() is used, because it correctly rounds the result
190 
191         s_sizeBtn.SetAtNewDPI(wxSize(wxMulDivInt32(50, base.x, 4),
192                                      wxMulDivInt32(14, base.y, 8)));
193     }
194 
195     return s_sizeBtn.Get();
196 }
197 
198 // ----------------------------------------------------------------------------
199 // default button handling
200 // ----------------------------------------------------------------------------
201 
202 /*
203    In normal Windows programs there is no need to handle default button
204    manually because this is taken care by the system provided you use
205    WM_NEXTDLGCTL and not just SetFocus() to switch focus betweeh the controls
206    (see http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for
207    the full explanation why just calling SetFocus() is not enough).
208 
209    However this only works if the window is a dialog, i.e. uses DefDlgProc(),
210    but not with plain windows using DefWindowProc() and we do want to have
211    default buttons inside frames as well, so we're forced to reimplement all
212    this logic ourselves. It would be great to avoid having to do this but using
213    DefDlgProc() for all the windows would almost certainly result in more
214    problems, we'd need to carefully filter messages and pass some of them to
215    DefWindowProc() and some of them to DefDlgProc() which looks dangerous (what
216    if the handling of some message changes in some Windows version?), so doing
217    this ourselves is probably a lesser evil.
218 
219    Read the rest to learn everything you ever wanted to know about the default
220    buttons but were afraid to ask.
221 
222 
223    In MSW the default button should be activated when the user presses Enter
224    and the current control doesn't process Enter itself somehow. This is
225    handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
226    Another aspect of "defaultness" is that the default button has different
227    appearance: this is due to BS_DEFPUSHBUTTON style which is completely
228    separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
229    be unset if our parent window is not active so it should be unset whenever
230    we lose activation and set back when we regain it.
231 
232    Final complication is that when a button is active, it should be the default
233    one, i.e. pressing Enter on a button always activates it and not another
234    one.
235 
236    We handle this by maintaining a permanent and a temporary default items in
237    wxControlContainer (both may be NULL). When a button becomes the current
238    control (i.e. gets focus) it sets itself as the temporary default which
239    ensures that it has the right appearance and that Enter will be redirected
240    to it. When the button loses focus, it unsets the temporary default and so
241    the default item will be the permanent default -- that is the default button
242    if any had been set or none otherwise, which is just what we want.
243  */
244 
245 // set this button as the (permanently) default one in its panel
SetDefault()246 wxWindow *wxButton::SetDefault()
247 {
248     // set this one as the default button both for wxWidgets ...
249     wxWindow *winOldDefault = wxButtonBase::SetDefault();
250 
251     // ... and Windows
252     SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
253     SetDefaultStyle(this, true);
254 
255     return winOldDefault;
256 }
257 
258 // return the top level parent window if it's not being deleted yet, otherwise
259 // return NULL
GetTLWParentIfNotBeingDeleted(wxWindow * win)260 static wxTopLevelWindow *GetTLWParentIfNotBeingDeleted(wxWindow *win)
261 {
262     for ( ;; )
263     {
264         // IsTopLevel() will return false for a wxTLW being deleted, so we also
265         // need the parent test for this case
266         wxWindow * const parent = win->GetParent();
267         if ( !parent || win->IsTopLevel() )
268         {
269             if ( win->IsBeingDeleted() )
270                 return NULL;
271 
272             break;
273         }
274 
275         win = parent;
276     }
277 
278     wxASSERT_MSG( win, wxT("button without top level parent?") );
279 
280     // Note that this may still return null for a button inside wxPopupWindow.
281     return wxDynamicCast(win, wxTopLevelWindow);
282 }
283 
284 // set this button as being currently default
SetTmpDefault()285 void wxButton::SetTmpDefault()
286 {
287     wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(this);
288     if ( !tlw )
289         return;
290 
291     wxWindow *winOldDefault = tlw->GetDefaultItem();
292 
293     tlw->SetTmpDefaultItem(this);
294 
295     // Notice that the order of these statements is important, the old button
296     // is not reset if we do it the other way round, probably because of
297     // something done by the default DM_SETDEFID handler.
298     SetDefaultStyle(this, true);
299     if ( winOldDefault != this )
300     {
301         // But we mustn't reset the default style on this button itself if it
302         // had already been the default.
303         SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
304     }
305 }
306 
307 // unset this button as currently default, it may still stay permanent default
UnsetTmpDefault()308 void wxButton::UnsetTmpDefault()
309 {
310     wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(this);
311     if ( !tlw )
312         return;
313 
314     tlw->SetTmpDefaultItem(NULL);
315 
316     wxWindow *winOldDefault = tlw->GetDefaultItem();
317 
318     // Just as in SetTmpDefault() above, the order is important here.
319     SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true);
320     if ( winOldDefault != this )
321     {
322         SetDefaultStyle(this, false);
323     }
324 }
325 
326 /* static */
327 void
SetDefaultStyle(wxButton * btn,bool on)328 wxButton::SetDefaultStyle(wxButton *btn, bool on)
329 {
330     // we may be called with NULL pointer -- simpler to do the check here than
331     // in the caller which does wxDynamicCast()
332     if ( !btn )
333         return;
334 
335     // first, let DefDlgProc() know about the new default button
336     if ( on )
337     {
338         // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
339         // focus at all any more
340         if ( !wxTheApp->IsActive() )
341             return;
342 
343         wxWindow * const tlw = wxGetTopLevelParent(btn);
344         wxCHECK_RET( tlw, wxT("button without top level window?") );
345 
346         ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L);
347 
348         // sending DM_SETDEFID also changes the button style to
349         // BS_DEFPUSHBUTTON so there is nothing more to do
350     }
351 
352     // then also change the style as needed
353     long style = ::GetWindowLong(GetHwndOf(btn), GWL_STYLE);
354     if ( !(style & BS_DEFPUSHBUTTON) == on )
355     {
356         // don't do it with the owner drawn buttons because it will
357         // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
358         // BS_DEFPUSHBUTTON != 0)!
359         if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
360         {
361             ::SendMessage(GetHwndOf(btn), BM_SETSTYLE,
362                           on ? style | BS_DEFPUSHBUTTON
363                              : style & ~BS_DEFPUSHBUTTON,
364                           1L /* redraw */);
365         }
366         else // owner drawn
367         {
368             // redraw the button - it will notice itself that it's
369             // [not] the default one [any longer]
370             btn->Refresh();
371         }
372     }
373     //else: already has correct style
374 }
375 
376 // ----------------------------------------------------------------------------
377 // helpers
378 // ----------------------------------------------------------------------------
379 
SendClickEvent()380 bool wxButton::SendClickEvent()
381 {
382     wxCommandEvent event(wxEVT_BUTTON, GetId());
383     event.SetEventObject(this);
384 
385     return ProcessCommand(event);
386 }
387 
Command(wxCommandEvent & event)388 void wxButton::Command(wxCommandEvent & event)
389 {
390     ProcessCommand(event);
391 }
392 
393 // ----------------------------------------------------------------------------
394 // event/message handlers
395 // ----------------------------------------------------------------------------
396 
OnCharHook(wxKeyEvent & event)397 void wxButton::OnCharHook(wxKeyEvent& event)
398 {
399     // We want to ensure that the button always processes Enter key events
400     // itself, even if it's inside some control that normally takes over them
401     // (this happens when the button is part of an in-place editor control for
402     // example).
403     if ( event.GetKeyCode() == WXK_RETURN )
404     {
405         // We should ensure that subsequent key events are still generated even
406         // if we did handle EVT_CHAR_HOOK (normally this would suppress their
407         // generation).
408         event.DoAllowNextEvent();
409     }
410     else
411     {
412         event.Skip();
413     }
414 }
415 
MSWCommand(WXUINT param,WXWORD WXUNUSED (id))416 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
417 {
418     bool processed = false;
419     switch ( param )
420     {
421         // NOTE: Currently all versions of Windows send two BN_CLICKED messages
422         //       for all button types, so we don't catch BN_DOUBLECLICKED
423         //       in order to not get 3 EVT_BUTTON events.  If this is a problem
424         //       then we need to figure out which version of the comctl32 changed
425         //       this behaviour and test for it.
426 
427         case 1:                     // message came from an accelerator
428         case BN_CLICKED:            // normal buttons send this
429             processed = SendClickEvent();
430             break;
431     }
432 
433     return processed;
434 }
435 
MSWWindowProc(WXUINT nMsg,WXWPARAM wParam,WXLPARAM lParam)436 WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
437 {
438     // when we receive focus, we want to temporarily become the default button in
439     // our parent panel so that pressing "Enter" would activate us -- and when
440     // losing it we should restore the previous default button as well
441     if ( nMsg == WM_SETFOCUS )
442     {
443         SetTmpDefault();
444 
445         // let the default processing take place too
446     }
447     else if ( nMsg == WM_KILLFOCUS )
448     {
449         UnsetTmpDefault();
450     }
451 
452     // let the base class do all real processing
453     return wxAnyButton::MSWWindowProc(nMsg, wParam, lParam);
454 }
455 
456 // ----------------------------------------------------------------------------
457 // authentication needed handling
458 // ----------------------------------------------------------------------------
459 
DoGetAuthNeeded() const460 bool wxButton::DoGetAuthNeeded() const
461 {
462     return m_authNeeded;
463 }
464 
DoSetAuthNeeded(bool show)465 void wxButton::DoSetAuthNeeded(bool show)
466 {
467     // show/hide UAC symbol on Windows Vista and later
468     if ( wxGetWinVersion() >= wxWinVersion_6 )
469     {
470         m_authNeeded = show;
471         ::SendMessage(GetHwnd(), BCM_SETSHIELD, 0, show);
472         InvalidateBestSize();
473     }
474 }
475 
476 #endif // wxUSE_BUTTON
477 
478