]>
git.saurik.com Git - wxWidgets.git/blob - src/msw/msgdlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/msgdlg.cpp
3 // Purpose: wxMessageDialog
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
21 #include "wx/msgdlg.h"
23 // there is no hook support under CE so we can't use the code for message box
26 #define wxUSE_MSGBOX_HOOK 1
28 #define wxUSE_MSGBOX_HOOK 0
34 #include "wx/dialog.h"
36 #include "wx/hashmap.h"
40 #include "wx/msw/private.h"
41 #include "wx/msw/private/button.h"
42 #include "wx/msw/private/metrics.h"
45 #include "wx/fontutil.h"
46 #include "wx/textbuf.h"
47 #include "wx/display.h"
52 #include "wx/msw/wince/missing.h"
55 IMPLEMENT_CLASS(wxMessageDialog
, wxDialog
)
59 // there can potentially be one message box per thread so we use a hash map
60 // with thread ids as keys and (currently shown) message boxes as values
62 // TODO: replace this with wxTLS once it's available
63 WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog
*,
64 wxIntegerHash
, wxIntegerEqual
,
67 // the order in this array is the one in which buttons appear in the
69 const wxMessageDialog::ButtonAccessors
wxMessageDialog::ms_buttons
[] =
71 { IDYES
, &wxMessageDialog::GetYesLabel
},
72 { IDNO
, &wxMessageDialog::GetNoLabel
},
73 { IDOK
, &wxMessageDialog::GetOKLabel
},
74 { IDCANCEL
, &wxMessageDialog::GetCancelLabel
},
80 wxMessageDialogMap
& HookMap()
82 static wxMessageDialogMap s_Map
;
88 All this code is used for adjusting the message box layout when we mess
89 with its contents. It's rather complicated because we try hard to avoid
90 assuming much about the standard layout details and so, instead of just
91 laying out everything ourselves (which would have been so much simpler!)
92 we try to only modify the existing controls positions by offsetting them
93 from their default ones in the hope that this will continue to work with
94 the future Windows versions.
97 // convert the given RECT from screen to client coordinates in place
98 void ScreenRectToClient(HWND hwnd
, RECT
& rc
)
100 // map from desktop (i.e. screen) coordinates to ones of this window
102 // notice that a RECT is laid out as 2 consecutive POINTs so the cast is
104 ::MapWindowPoints(HWND_DESKTOP
, hwnd
, reinterpret_cast<POINT
*>(&rc
), 2);
107 // set window position to the given rect
108 inline void SetWindowRect(HWND hwnd
, const RECT
& rc
)
112 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
116 // set window position expressed in screen coordinates, whether the window is
117 // child or top level
118 void MoveWindowToScreenRect(HWND hwnd
, RECT rc
)
120 ScreenRectToClient(::GetParent(hwnd
), rc
);
122 SetWindowRect(hwnd
, rc
);
125 // helper of AdjustButtonLabels(): move the given window by dx
127 // works for both child and top level windows
128 void OffsetWindow(HWND hwnd
, int dx
)
130 RECT rc
= wxGetWindowRect(hwnd
);
135 MoveWindowToScreenRect(hwnd
, rc
);
138 } // anonymous namespace
142 wxMessageDialog::HookFunction(int code
, WXWPARAM wParam
, WXLPARAM lParam
)
144 // Find the thread-local instance of wxMessageDialog
145 const DWORD tid
= ::GetCurrentThreadId();
146 wxMessageDialogMap::iterator node
= HookMap().find(tid
);
147 wxCHECK_MSG( node
!= HookMap().end(), false,
148 wxT("bogus thread id in wxMessageDialog::Hook") );
150 wxMessageDialog
* const wnd
= node
->second
;
152 const HHOOK hhook
= (HHOOK
)wnd
->m_hook
;
153 const LRESULT rc
= ::CallNextHookEx(hhook
, code
, wParam
, lParam
);
155 if ( code
== HCBT_ACTIVATE
)
157 // we won't need this hook any longer
158 ::UnhookWindowsHookEx(hhook
);
160 HookMap().erase(tid
);
162 wnd
->SetHWND((HWND
)wParam
);
164 // replace the static text with an edit control if the message box is
165 // too big to fit the display
166 wnd
->ReplaceStaticWithEdit();
168 // update the labels if necessary: we need to do it before centering
169 // the dialog as this can change its size
170 if ( wnd
->HasCustomLabels() )
171 wnd
->AdjustButtonLabels();
173 // centre the message box on its parent if requested
174 if ( wnd
->GetMessageDialogStyle() & wxCENTER
)
175 wnd
->Center(); // center on parent
176 //else: default behaviour, center on screen
178 // there seems to be no reason to leave it set
185 void wxMessageDialog::ReplaceStaticWithEdit()
187 // check if the message box fits the display
188 int nDisplay
= wxDisplay::GetFromWindow(this);
189 if ( nDisplay
== wxNOT_FOUND
)
191 const wxRect rectDisplay
= wxDisplay(nDisplay
).GetClientArea();
193 if ( rectDisplay
.Contains(GetRect()) )
200 // find the static control to replace: normally there are two of them, the
201 // icon and the text itself so search for all of them and ignore the icon
203 HWND hwndStatic
= ::FindWindowEx(GetHwnd(), NULL
, _T("STATIC"), NULL
);
204 if ( ::GetWindowLong(hwndStatic
, GWL_STYLE
) & SS_ICON
)
205 hwndStatic
= ::FindWindowEx(GetHwnd(), hwndStatic
, _T("STATIC"), NULL
);
209 wxLogDebug("Failed to find the static text control in message box.");
213 // set the right font for GetCharHeight() call below
214 wxWindowBase::SetFont(GetMessageFont());
216 // put the new edit control at the same place
217 RECT rc
= wxGetWindowRect(hwndStatic
);
218 ScreenRectToClient(GetHwnd(), rc
);
220 // but make it less tall so that the message box fits on the screen: we try
221 // to make the message box take no more than 7/8 of the screen to leave
222 // some space above and below it
223 const int hText
= (7*rectDisplay
.height
)/8 -
225 2*::GetSystemMetrics(SM_CYFIXEDFRAME
) +
226 ::GetSystemMetrics(SM_CYCAPTION
) +
227 5*GetCharHeight() // buttons + margins
229 const int dh
= (rc
.bottom
- rc
.top
) - hText
; // vertical space we save
232 // and it also must be wider as it needs a vertical scrollbar (in order
233 // to preserve the word wrap, otherwise the number of lines would change
234 // and we want the control to look as similar as possible to the original)
236 // NB: you would have thought that 2*SM_CXEDGE would be enough but it
237 // isn't, somehow, and the text control breaks lines differently from
238 // the static one so fudge by adding some extra space
239 const int dw
= ::GetSystemMetrics(SM_CXVSCROLL
) +
240 4*::GetSystemMetrics(SM_CXEDGE
);
244 // chop of the trailing new line(s) from the message box text, they are
245 // ignored by the static control but result in extra lines and hence extra
246 // scrollbar position in the edit one
247 wxString
text(wxGetWindowText(hwndStatic
));
248 for ( wxString::iterator i
= text
.end() - 1; i
!= text
.begin(); --i
)
252 text
.erase(i
+ 1, text
.end());
257 // do create the new control
258 HWND hwndEdit
= ::CreateWindow
261 wxTextBuffer::Translate(text
).wx_str(),
262 WS_CHILD
| WS_VSCROLL
| WS_VISIBLE
|
263 ES_MULTILINE
| ES_READONLY
| ES_AUTOVSCROLL
,
265 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
274 wxLogDebug("Creation of replacement edit control failed in message box");
278 // copy the font from the original control
279 LRESULT hfont
= ::SendMessage(hwndStatic
, WM_GETFONT
, 0, 0);
280 ::SendMessage(hwndEdit
, WM_SETFONT
, hfont
, 0);
283 ::DestroyWindow(hwndStatic
);
286 // shrink and centre the message box vertically and widen it box to account
287 // for the extra scrollbar
288 RECT rcBox
= wxGetWindowRect(GetHwnd());
289 const int hMsgBox
= rcBox
.bottom
- rcBox
.top
- dh
;
290 rcBox
.top
= (rectDisplay
.height
- hMsgBox
)/2;
291 rcBox
.bottom
= rcBox
.top
+ hMsgBox
+ (rectDisplay
.height
- hMsgBox
)%2
;
293 rcBox
.right
+= dw
- dw
/2;
294 SetWindowRect(GetHwnd(), rcBox
);
296 // and adjust all the buttons positions
297 for ( unsigned n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
299 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
301 continue; // it's ok, not all buttons are always present
303 RECT rc
= wxGetWindowRect(hwndBtn
);
308 MoveWindowToScreenRect(hwndBtn
, rc
);
312 void wxMessageDialog::AdjustButtonLabels()
314 // changing the button labels is the easy part but we also need to ensure
315 // that the buttons are big enough for the label strings and increase their
316 // size (and maybe the size of the message box itself) if they are not
318 // TODO-RTL: check whether this works correctly in RTL
320 // we want to use this font in GetTextExtent() calls below but we don't
321 // want to send WM_SETFONT to the message box, who knows how is it going to
322 // react to it (right now it doesn't seem to do anything but what if this
324 wxWindowBase::SetFont(GetMessageFont());
326 // first iteration: find the widest button and update the buttons labels
327 int wBtnOld
= 0, // current buttons width
328 wBtnNew
= 0; // required new buttons width
329 RECT rcBtn
; // stores the button height and y positions
330 unsigned numButtons
= 0; // total number of buttons in the message box
332 for ( n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
334 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
336 continue; // it's ok, not all buttons are always present
340 const wxString label
= (this->*ms_buttons
[n
].getter
)();
341 const wxSize sizeLabel
= wxWindowBase::GetTextExtent(label
);
343 // check if the button is big enough for this label
344 const RECT rc
= wxGetWindowRect(hwndBtn
);
347 // initialize wBtnOld using the first button width, all the other
348 // ones should have the same one
349 wBtnOld
= rc
.right
- rc
.left
;
351 rcBtn
= rc
; // remember for use below when we reposition the buttons
355 wxASSERT_MSG( wBtnOld
== rc
.right
- rc
.left
,
356 "all buttons are supposed to be of same width" );
359 const int widthNeeded
= wxMSWButton::GetFittingSize(this, sizeLabel
).x
;
360 if ( widthNeeded
> wBtnNew
)
361 wBtnNew
= widthNeeded
;
363 ::SetWindowText(hwndBtn
, label
.wx_str());
366 if ( wBtnNew
<= wBtnOld
)
368 // all buttons fit, nothing else to do
372 // resize the message box to be wider if needed
373 const int wBoxOld
= wxGetClientRect(GetHwnd()).right
;
375 const int CHAR_WIDTH
= GetCharWidth();
376 const int MARGIN_OUTER
= 2*CHAR_WIDTH
; // margin between box and buttons
377 const int MARGIN_INNER
= CHAR_WIDTH
; // margin between buttons
379 RECT rcBox
= wxGetWindowRect(GetHwnd());
381 const int wAllButtons
= numButtons
*(wBtnNew
+ MARGIN_INNER
) - MARGIN_INNER
;
382 int wBoxNew
= 2*MARGIN_OUTER
+ wAllButtons
;
383 if ( wBoxNew
> wBoxOld
)
385 const int dw
= wBoxNew
- wBoxOld
;
387 rcBox
.right
+= dw
- dw
/2;
389 SetWindowRect(GetHwnd(), rcBox
);
391 // surprisingly, we don't need to resize the static text control, it
392 // seems to adjust itself to the new size, at least under Windows 2003
393 // (TODO: test if this happens on older Windows versions)
395 else // the current width is big enough
401 // finally position all buttons
403 // notice that we have to take into account the difference between window
405 rcBtn
.left
= (rcBox
.left
+ rcBox
.right
- wxGetClientRect(GetHwnd()).right
+
406 wBoxNew
- wAllButtons
) / 2;
407 rcBtn
.right
= rcBtn
.left
+ wBtnNew
;
409 for ( n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
411 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
415 MoveWindowToScreenRect(hwndBtn
, rcBtn
);
417 rcBtn
.left
+= wBtnNew
+ MARGIN_INNER
;
418 rcBtn
.right
+= wBtnNew
+ MARGIN_INNER
;
422 #endif // wxUSE_MSGBOX_HOOK
425 wxFont
wxMessageDialog::GetMessageFont()
427 const NONCLIENTMETRICS
& ncm
= wxMSWImpl::GetNonClientMetrics();
428 return wxNativeFontInfo(ncm
.lfMessageFont
);
431 int wxMessageDialog::ShowModal()
433 if ( !wxTheApp
->GetTopWindow() )
435 // when the message box is shown from wxApp::OnInit() (i.e. before the
436 // message loop is entered), this must be done or the next message box
437 // will never be shown - just try putting 2 calls to wxMessageBox() in
438 // OnInit() to see it
439 while ( wxTheApp
->Pending() )
440 wxTheApp
->Dispatch();
443 // use the top level window as parent if none specified
445 m_parent
= GetParentForModalDialog();
446 HWND hWnd
= m_parent
? GetHwndOf(m_parent
) : NULL
;
448 // translate wx style in MSW
449 unsigned int msStyle
;
450 const long wxStyle
= GetMessageDialogStyle();
451 if ( wxStyle
& wxYES_NO
)
453 #if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
454 if (wxStyle
& wxCANCEL
)
455 msStyle
= MB_YESNOCANCEL
;
457 #endif // !(__SMARTPHONE__ && __WXWINCE__)
460 if ( wxStyle
& wxNO_DEFAULT
)
461 msStyle
|= MB_DEFBUTTON2
;
462 else if ( wxStyle
& wxCANCEL_DEFAULT
)
463 msStyle
|= MB_DEFBUTTON3
;
465 else // without Yes/No we're going to have an OK button
467 if ( wxStyle
& wxCANCEL
)
469 msStyle
= MB_OKCANCEL
;
471 if ( wxStyle
& wxCANCEL_DEFAULT
)
472 msStyle
|= MB_DEFBUTTON2
;
480 if (wxStyle
& wxICON_EXCLAMATION
)
481 msStyle
|= MB_ICONEXCLAMATION
;
482 else if (wxStyle
& wxICON_HAND
)
483 msStyle
|= MB_ICONHAND
;
484 else if (wxStyle
& wxICON_INFORMATION
)
485 msStyle
|= MB_ICONINFORMATION
;
486 else if (wxStyle
& wxICON_QUESTION
)
487 msStyle
|= MB_ICONQUESTION
;
489 if ( wxStyle
& wxSTAY_ON_TOP
)
490 msStyle
|= MB_TOPMOST
;
493 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
494 msStyle
|= MB_RTLREADING
| MB_RIGHT
;
498 msStyle
|= MB_APPLMODAL
;
500 msStyle
|= MB_TASKMODAL
;
502 // per MSDN documentation for MessageBox() we can prefix the message with 2
503 // right-to-left mark characters to tell the function to use RTL layout
504 // (unfortunately this only works in Unicode builds)
505 wxString message
= GetFullMessage();
507 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
509 // NB: not all compilers support \u escapes
510 static const wchar_t wchRLM
= 0x200f;
511 message
.Prepend(wxString(wchRLM
, 2));
513 #endif // wxUSE_UNICODE
515 #if wxUSE_MSGBOX_HOOK
516 // install the hook in any case as we don't know in advance if the message
517 // box is not going to be too big (requiring the replacement of the static
518 // control with an edit one)
519 const DWORD tid
= ::GetCurrentThreadId();
520 m_hook
= ::SetWindowsHookEx(WH_CBT
,
521 &wxMessageDialog::HookFunction
, NULL
, tid
);
522 HookMap()[tid
] = this;
523 #endif // wxUSE_MSGBOX_HOOK
525 // do show the dialog
526 int msAns
= MessageBox(hWnd
, message
.wx_str(), m_caption
.wx_str(), msStyle
);
531 wxFAIL_MSG(_T("unexpected ::MessageBox() return code"));
550 #endif // wxUSE_MSGDLG