]> git.saurik.com Git - wxWidgets.git/blame - src/msw/msgdlg.cpp
Avoid outputting the assert message twice in non-GUI code.
[wxWidgets.git] / src / msw / msgdlg.cpp
CommitLineData
2bda0e17 1/////////////////////////////////////////////////////////////////////////////
e5b50758 2// Name: src/msw/msgdlg.cpp
2bda0e17
KB
3// Purpose: wxMessageDialog
4// Author: Julian Smart
5// Modified by:
6// Created: 04/01/98
7// RCS-ID: $Id$
6c9a19aa 8// Copyright: (c) Julian Smart
65571936 9// Licence: wxWindows licence
2bda0e17
KB
10/////////////////////////////////////////////////////////////////////////////
11
2bda0e17
KB
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
7520f3da 16 #pragma hdrstop
2bda0e17
KB
17#endif
18
a8ff046b
VZ
19#if wxUSE_MSGDLG
20
246c5004
WS
21#include "wx/msgdlg.h"
22
704c499e
VZ
23// there is no hook support under CE so we can't use the code for message box
24// positioning there
25#ifndef __WXWINCE__
26 #define wxUSE_MSGBOX_HOOK 1
27#else
28 #define wxUSE_MSGBOX_HOOK 0
29#endif
30
2bda0e17 31#ifndef WX_PRECOMP
35bc781e 32 #include "wx/app.h"
42c097b8 33 #include "wx/intl.h"
0d7ea902
VZ
34 #include "wx/utils.h"
35 #include "wx/dialog.h"
704c499e
VZ
36 #if wxUSE_MSGBOX_HOOK
37 #include "wx/hashmap.h"
38 #endif
2bda0e17
KB
39#endif
40
41#include "wx/msw/private.h"
23e00c55
VZ
42#include "wx/msw/private/button.h"
43#include "wx/msw/private/metrics.h"
44
45#if wxUSE_MSGBOX_HOOK
46 #include "wx/fontutil.h"
4b02d42e
VZ
47 #include "wx/textbuf.h"
48 #include "wx/display.h"
23e00c55 49#endif
2bda0e17 50
676d6550
JS
51// For MB_TASKMODAL
52#ifdef __WXWINCE__
23e00c55 53 #include "wx/msw/wince/missing.h"
676d6550
JS
54#endif
55
2bda0e17 56IMPLEMENT_CLASS(wxMessageDialog, wxDialog)
2bda0e17 57
704c499e
VZ
58#if wxUSE_MSGBOX_HOOK
59
1d89da8a
VZ
60// there can potentially be one message box per thread so we use a hash map
61// with thread ids as keys and (currently shown) message boxes as values
62//
63// TODO: replace this with wxTLS once it's available
64WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog *,
65 wxIntegerHash, wxIntegerEqual,
66 wxMessageDialogMap);
67
4b02d42e
VZ
68// the order in this array is the one in which buttons appear in the
69// message box
70const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] =
71{
72 { IDYES, &wxMessageDialog::GetYesLabel },
73 { IDNO, &wxMessageDialog::GetNoLabel },
74 { IDOK, &wxMessageDialog::GetOKLabel },
75 { IDCANCEL, &wxMessageDialog::GetCancelLabel },
76};
77
1d89da8a
VZ
78namespace
79{
80
81wxMessageDialogMap& HookMap()
82{
83 static wxMessageDialogMap s_Map;
84
85 return s_Map;
86}
87
4b02d42e
VZ
88/*
89 All this code is used for adjusting the message box layout when we mess
90 with its contents. It's rather complicated because we try hard to avoid
91 assuming much about the standard layout details and so, instead of just
92 laying out everything ourselves (which would have been so much simpler!)
93 we try to only modify the existing controls positions by offsetting them
94 from their default ones in the hope that this will continue to work with
95 the future Windows versions.
96 */
97
98// convert the given RECT from screen to client coordinates in place
99void ScreenRectToClient(HWND hwnd, RECT& rc)
100{
101 // map from desktop (i.e. screen) coordinates to ones of this window
102 //
103 // notice that a RECT is laid out as 2 consecutive POINTs so the cast is
104 // valid
105 ::MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast<POINT *>(&rc), 2);
106}
107
108// set window position to the given rect
109inline void SetWindowRect(HWND hwnd, const RECT& rc)
110{
111 ::MoveWindow(hwnd,
112 rc.left, rc.top,
113 rc.right - rc.left, rc.bottom - rc.top,
114 FALSE);
115}
116
117// set window position expressed in screen coordinates, whether the window is
118// child or top level
119void MoveWindowToScreenRect(HWND hwnd, RECT rc)
120{
121 ScreenRectToClient(::GetParent(hwnd), rc);
122
123 SetWindowRect(hwnd, rc);
124}
125
126// helper of AdjustButtonLabels(): move the given window by dx
127//
128// works for both child and top level windows
129void OffsetWindow(HWND hwnd, int dx)
130{
131 RECT rc = wxGetWindowRect(hwnd);
132
133 rc.left += dx;
134 rc.right += dx;
135
136 MoveWindowToScreenRect(hwnd, rc);
137}
138
1d89da8a
VZ
139} // anonymous namespace
140
141/* static */
142WXLRESULT wxCALLBACK
143wxMessageDialog::HookFunction(int code, WXWPARAM wParam, WXLPARAM lParam)
144{
145 // Find the thread-local instance of wxMessageDialog
146 const DWORD tid = ::GetCurrentThreadId();
147 wxMessageDialogMap::iterator node = HookMap().find(tid);
148 wxCHECK_MSG( node != HookMap().end(), false,
149 wxT("bogus thread id in wxMessageDialog::Hook") );
150
151 wxMessageDialog * const wnd = node->second;
152
153 const HHOOK hhook = (HHOOK)wnd->m_hook;
154 const LRESULT rc = ::CallNextHookEx(hhook, code, wParam, lParam);
155
23e00c55
VZ
156 if ( code == HCBT_ACTIVATE )
157 {
158 // we won't need this hook any longer
159 ::UnhookWindowsHookEx(hhook);
160 wnd->m_hook = NULL;
161 HookMap().erase(tid);
162
163 wnd->SetHWND((HWND)wParam);
164
4b02d42e
VZ
165 // replace the static text with an edit control if the message box is
166 // too big to fit the display
167 wnd->ReplaceStaticWithEdit();
168
b6bcaaca
VZ
169 // update the labels if necessary: we need to do it before centering
170 // the dialog as this can change its size
171 if ( wnd->HasCustomLabels() )
172 wnd->AdjustButtonLabels();
173
23e00c55
VZ
174 // centre the message box on its parent if requested
175 if ( wnd->GetMessageDialogStyle() & wxCENTER )
176 wnd->Center(); // center on parent
177 //else: default behaviour, center on screen
178
23e00c55
VZ
179 // there seems to be no reason to leave it set
180 wnd->SetHWND(NULL);
181 }
182
183 return rc;
184}
185
4b02d42e 186void wxMessageDialog::ReplaceStaticWithEdit()
23e00c55 187{
4b02d42e
VZ
188 // check if the message box fits the display
189 int nDisplay = wxDisplay::GetFromWindow(this);
190 if ( nDisplay == wxNOT_FOUND )
191 nDisplay = 0;
192 const wxRect rectDisplay = wxDisplay(nDisplay).GetClientArea();
23e00c55 193
4b02d42e 194 if ( rectDisplay.Contains(GetRect()) )
23e00c55 195 {
4b02d42e
VZ
196 // nothing to do
197 return;
23e00c55
VZ
198 }
199
23e00c55 200
4b02d42e
VZ
201 // find the static control to replace: normally there are two of them, the
202 // icon and the text itself so search for all of them and ignore the icon
203 // ones
9a83f860 204 HWND hwndStatic = ::FindWindowEx(GetHwnd(), NULL, wxT("STATIC"), NULL);
4b02d42e 205 if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON )
9a83f860 206 hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), NULL);
23e00c55 207
4b02d42e
VZ
208 if ( !hwndStatic )
209 {
210 wxLogDebug("Failed to find the static text control in message box.");
211 return;
212 }
23e00c55 213
4b02d42e
VZ
214 // set the right font for GetCharHeight() call below
215 wxWindowBase::SetFont(GetMessageFont());
216
217 // put the new edit control at the same place
218 RECT rc = wxGetWindowRect(hwndStatic);
219 ScreenRectToClient(GetHwnd(), rc);
220
221 // but make it less tall so that the message box fits on the screen: we try
222 // to make the message box take no more than 7/8 of the screen to leave
223 // some space above and below it
224 const int hText = (7*rectDisplay.height)/8 -
225 (
226 2*::GetSystemMetrics(SM_CYFIXEDFRAME) +
227 ::GetSystemMetrics(SM_CYCAPTION) +
228 5*GetCharHeight() // buttons + margins
229 );
230 const int dh = (rc.bottom - rc.top) - hText; // vertical space we save
231 rc.bottom -= dh;
232
233 // and it also must be wider as it needs a vertical scrollbar (in order
234 // to preserve the word wrap, otherwise the number of lines would change
235 // and we want the control to look as similar as possible to the original)
236 //
237 // NB: you would have thought that 2*SM_CXEDGE would be enough but it
238 // isn't, somehow, and the text control breaks lines differently from
239 // the static one so fudge by adding some extra space
240 const int dw = ::GetSystemMetrics(SM_CXVSCROLL) +
241 4*::GetSystemMetrics(SM_CXEDGE);
242 rc.right += dw;
243
244
245 // chop of the trailing new line(s) from the message box text, they are
246 // ignored by the static control but result in extra lines and hence extra
247 // scrollbar position in the edit one
248 wxString text(wxGetWindowText(hwndStatic));
249 for ( wxString::iterator i = text.end() - 1; i != text.begin(); --i )
250 {
251 if ( *i != '\n' )
252 {
253 text.erase(i + 1, text.end());
254 break;
255 }
256 }
23e00c55 257
4b02d42e
VZ
258 // do create the new control
259 HWND hwndEdit = ::CreateWindow
260 (
9a83f860 261 wxT("EDIT"),
cc5e20f8 262 wxTextBuffer::Translate(text).wx_str(),
4b02d42e
VZ
263 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
264 ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
265 rc.left, rc.top,
266 rc.right - rc.left, rc.bottom - rc.top,
267 GetHwnd(),
268 NULL,
269 wxhInstance,
270 NULL
271 );
272
273 if ( !hwndEdit )
274 {
275 wxLogDebug("Creation of replacement edit control failed in message box");
276 return;
277 }
23e00c55 278
4b02d42e
VZ
279 // copy the font from the original control
280 LRESULT hfont = ::SendMessage(hwndStatic, WM_GETFONT, 0, 0);
281 ::SendMessage(hwndEdit, WM_SETFONT, hfont, 0);
282
283 // and get rid of it
284 ::DestroyWindow(hwndStatic);
23e00c55 285
23e00c55 286
4b02d42e
VZ
287 // shrink and centre the message box vertically and widen it box to account
288 // for the extra scrollbar
289 RECT rcBox = wxGetWindowRect(GetHwnd());
290 const int hMsgBox = rcBox.bottom - rcBox.top - dh;
291 rcBox.top = (rectDisplay.height - hMsgBox)/2;
292 rcBox.bottom = rcBox.top + hMsgBox + (rectDisplay.height - hMsgBox)%2;
293 rcBox.left -= dw/2;
294 rcBox.right += dw - dw/2;
295 SetWindowRect(GetHwnd(), rcBox);
296
297 // and adjust all the buttons positions
298 for ( unsigned n = 0; n < WXSIZEOF(ms_buttons); n++ )
1d89da8a 299 {
4b02d42e
VZ
300 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
301 if ( !hwndBtn )
302 continue; // it's ok, not all buttons are always present
303
304 RECT rc = wxGetWindowRect(hwndBtn);
305 rc.top -= dh;
306 rc.bottom -= dh;
307 rc.left += dw/2;
308 rc.right += dw/2;
309 MoveWindowToScreenRect(hwndBtn, rc);
23e00c55 310 }
4b02d42e 311}
23e00c55 312
4b02d42e
VZ
313void wxMessageDialog::AdjustButtonLabels()
314{
315 // changing the button labels is the easy part but we also need to ensure
316 // that the buttons are big enough for the label strings and increase their
317 // size (and maybe the size of the message box itself) if they are not
1d89da8a 318
4b02d42e 319 // TODO-RTL: check whether this works correctly in RTL
23e00c55
VZ
320
321 // we want to use this font in GetTextExtent() calls below but we don't
322 // want to send WM_SETFONT to the message box, who knows how is it going to
323 // react to it (right now it doesn't seem to do anything but what if this
324 // changes)
4b02d42e
VZ
325 wxWindowBase::SetFont(GetMessageFont());
326
327 // first iteration: find the widest button and update the buttons labels
328 int wBtnOld = 0, // current buttons width
329 wBtnNew = 0; // required new buttons width
330 RECT rcBtn; // stores the button height and y positions
331 unsigned numButtons = 0; // total number of buttons in the message box
332 unsigned n;
333 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
23e00c55 334 {
4b02d42e 335 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
23e00c55
VZ
336 if ( !hwndBtn )
337 continue; // it's ok, not all buttons are always present
338
4b02d42e
VZ
339 numButtons++;
340
341 const wxString label = (this->*ms_buttons[n].getter)();
23e00c55
VZ
342 const wxSize sizeLabel = wxWindowBase::GetTextExtent(label);
343
344 // check if the button is big enough for this label
4b02d42e
VZ
345 const RECT rc = wxGetWindowRect(hwndBtn);
346 if ( !wBtnOld )
347 {
348 // initialize wBtnOld using the first button width, all the other
349 // ones should have the same one
350 wBtnOld = rc.right - rc.left;
351
352 rcBtn = rc; // remember for use below when we reposition the buttons
353 }
354 else
1d89da8a 355 {
4b02d42e
VZ
356 wxASSERT_MSG( wBtnOld == rc.right - rc.left,
357 "all buttons are supposed to be of same width" );
1d89da8a 358 }
23e00c55 359
4b02d42e
VZ
360 const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x;
361 if ( widthNeeded > wBtnNew )
362 wBtnNew = widthNeeded;
363
23e00c55 364 ::SetWindowText(hwndBtn, label.wx_str());
1d89da8a
VZ
365 }
366
4b02d42e
VZ
367 if ( wBtnNew <= wBtnOld )
368 {
369 // all buttons fit, nothing else to do
370 return;
371 }
372
373 // resize the message box to be wider if needed
374 const int wBoxOld = wxGetClientRect(GetHwnd()).right;
375
376 const int CHAR_WIDTH = GetCharWidth();
377 const int MARGIN_OUTER = 2*CHAR_WIDTH; // margin between box and buttons
378 const int MARGIN_INNER = CHAR_WIDTH; // margin between buttons
379
380 RECT rcBox = wxGetWindowRect(GetHwnd());
381
382 const int wAllButtons = numButtons*(wBtnNew + MARGIN_INNER) - MARGIN_INNER;
383 int wBoxNew = 2*MARGIN_OUTER + wAllButtons;
384 if ( wBoxNew > wBoxOld )
385 {
386 const int dw = wBoxNew - wBoxOld;
387 rcBox.left -= dw/2;
388 rcBox.right += dw - dw/2;
389
390 SetWindowRect(GetHwnd(), rcBox);
391
392 // surprisingly, we don't need to resize the static text control, it
393 // seems to adjust itself to the new size, at least under Windows 2003
394 // (TODO: test if this happens on older Windows versions)
395 }
396 else // the current width is big enough
397 {
398 wBoxNew = wBoxOld;
399 }
400
401
402 // finally position all buttons
23e00c55 403
4b02d42e
VZ
404 // notice that we have to take into account the difference between window
405 // and client width
406 rcBtn.left = (rcBox.left + rcBox.right - wxGetClientRect(GetHwnd()).right +
407 wBoxNew - wAllButtons) / 2;
408 rcBtn.right = rcBtn.left + wBtnNew;
23e00c55 409
4b02d42e
VZ
410 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
411 {
412 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
413 if ( !hwndBtn )
414 continue;
415
416 MoveWindowToScreenRect(hwndBtn, rcBtn);
417
418 rcBtn.left += wBtnNew + MARGIN_INNER;
419 rcBtn.right += wBtnNew + MARGIN_INNER;
420 }
1d89da8a
VZ
421}
422
704c499e
VZ
423#endif // wxUSE_MSGBOX_HOOK
424
4b02d42e
VZ
425/* static */
426wxFont wxMessageDialog::GetMessageFont()
427{
428 const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics();
429 return wxNativeFontInfo(ncm.lfMessageFont);
430}
704c499e 431
0d7ea902 432int wxMessageDialog::ShowModal()
2bda0e17 433{
a543e3ce 434 if ( !wxTheApp->GetTopWindow() )
0d7ea902
VZ
435 {
436 // when the message box is shown from wxApp::OnInit() (i.e. before the
437 // message loop is entered), this must be done or the next message box
438 // will never be shown - just try putting 2 calls to wxMessageBox() in
439 // OnInit() to see it
440 while ( wxTheApp->Pending() )
441 wxTheApp->Dispatch();
442 }
93c95e18 443
b8505921 444 // use the top level window as parent if none specified
a543e3ce 445 if ( !m_parent )
8bda0ec6 446 m_parent = GetParentForModalDialog();
a543e3ce 447 HWND hWnd = m_parent ? GetHwndOf(m_parent) : NULL;
b8505921 448
42c097b8
VZ
449#if wxUSE_INTL
450 // native message box always uses the current user locale but the program
451 // may be using a different one and in this case we need to manually
452 // translate the button labels to avoid mismatch between the language of
453 // the message box text and its buttons
454 wxLocale * const loc = wxGetLocale();
455 if ( loc && loc->GetLanguage() != wxLocale::GetSystemLanguage() )
456 {
457 if ( m_dialogStyle & wxYES_NO )
458 {
459 // use the strings with mnemonics here as the native message box
460 // does
461 SetYesNoLabels(_("&Yes"), _("&No"));
462 }
463
464 // we may or not have the Ok/Cancel buttons but either we do have them
465 // or we already made the labels custom because we called
466 // SetYesNoLabels() above so doing this does no harm -- and is
467 // necessary in wxYES_NO | wxCANCEL case
468 //
469 // note that we don't use mnemonics here for consistency with the
470 // native message box (which probably doesn't use them because
471 // Enter/Esc keys can be already used to dismiss the message box
472 // using keyboard)
473 SetOKCancelLabels(_("OK"), _("Cancel"));
474 }
475#endif // wxUSE_INTL
476
b8505921 477 // translate wx style in MSW
f45d6ade 478 unsigned int msStyle;
e5b50758 479 const long wxStyle = GetMessageDialogStyle();
f45d6ade 480 if ( wxStyle & wxYES_NO )
0d7ea902 481 {
3180bc0e 482#if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
e5b50758 483 if (wxStyle & wxCANCEL)
0d7ea902
VZ
484 msStyle = MB_YESNOCANCEL;
485 else
3180bc0e 486#endif // !(__SMARTPHONE__ && __WXWINCE__)
0d7ea902 487 msStyle = MB_YESNO;
93c95e18 488
f45d6ade 489 if ( wxStyle & wxNO_DEFAULT )
0d7ea902 490 msStyle |= MB_DEFBUTTON2;
f45d6ade
VZ
491 else if ( wxStyle & wxCANCEL_DEFAULT )
492 msStyle |= MB_DEFBUTTON3;
0d7ea902 493 }
f45d6ade 494 else // without Yes/No we're going to have an OK button
0d7ea902 495 {
f45d6ade
VZ
496 if ( wxStyle & wxCANCEL )
497 {
0d7ea902 498 msStyle = MB_OKCANCEL;
f45d6ade
VZ
499
500 if ( wxStyle & wxCANCEL_DEFAULT )
501 msStyle |= MB_DEFBUTTON2;
502 }
503 else // just "OK"
504 {
0d7ea902 505 msStyle = MB_OK;
f45d6ade 506 }
0d7ea902 507 }
f45d6ade 508
e5b50758 509 if (wxStyle & wxICON_EXCLAMATION)
0d7ea902 510 msStyle |= MB_ICONEXCLAMATION;
e5b50758 511 else if (wxStyle & wxICON_HAND)
0d7ea902 512 msStyle |= MB_ICONHAND;
e5b50758 513 else if (wxStyle & wxICON_INFORMATION)
0d7ea902 514 msStyle |= MB_ICONINFORMATION;
e5b50758 515 else if (wxStyle & wxICON_QUESTION)
0d7ea902 516 msStyle |= MB_ICONQUESTION;
2bda0e17 517
e5b50758 518 if ( wxStyle & wxSTAY_ON_TOP )
a7fd7c78
VZ
519 msStyle |= MB_TOPMOST;
520
08a58133 521#ifndef __WXWINCE__
978af864
VZ
522 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
523 msStyle |= MB_RTLREADING | MB_RIGHT;
08a58133 524#endif
978af864 525
0d7ea902
VZ
526 if (hWnd)
527 msStyle |= MB_APPLMODAL;
528 else
529 msStyle |= MB_TASKMODAL;
93c95e18 530
12e424d2
VZ
531 // per MSDN documentation for MessageBox() we can prefix the message with 2
532 // right-to-left mark characters to tell the function to use RTL layout
533 // (unfortunately this only works in Unicode builds)
2afb9e16 534 wxString message = GetFullMessage();
12e424d2
VZ
535#if wxUSE_UNICODE
536 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
537 {
538 // NB: not all compilers support \u escapes
539 static const wchar_t wchRLM = 0x200f;
540 message.Prepend(wxString(wchRLM, 2));
541 }
542#endif // wxUSE_UNICODE
543
704c499e 544#if wxUSE_MSGBOX_HOOK
4b02d42e
VZ
545 // install the hook in any case as we don't know in advance if the message
546 // box is not going to be too big (requiring the replacement of the static
547 // control with an edit one)
548 const DWORD tid = ::GetCurrentThreadId();
549 m_hook = ::SetWindowsHookEx(WH_CBT,
550 &wxMessageDialog::HookFunction, NULL, tid);
551 HookMap()[tid] = this;
704c499e 552#endif // wxUSE_MSGBOX_HOOK
1d89da8a 553
b8505921 554 // do show the dialog
e0a050e3 555 int msAns = MessageBox(hWnd, message.wx_str(), m_caption.wx_str(), msStyle);
b8505921 556 int ans;
0d7ea902
VZ
557 switch (msAns)
558 {
b8505921 559 default:
9a83f860 560 wxFAIL_MSG(wxT("unexpected ::MessageBox() return code"));
b8505921
VZ
561 // fall through
562
0d7ea902
VZ
563 case IDCANCEL:
564 ans = wxID_CANCEL;
565 break;
566 case IDOK:
567 ans = wxID_OK;
568 break;
569 case IDYES:
570 ans = wxID_YES;
571 break;
572 case IDNO:
573 ans = wxID_NO;
574 break;
575 }
576 return ans;
2bda0e17 577}
a8ff046b
VZ
578
579#endif // wxUSE_MSGDLG