]> git.saurik.com Git - wxWidgets.git/blame - src/msw/msgdlg.cpp
Eliminated WX_PG_DECLARE/IMPLEMENT_DERIVED_PROPERTY_CLASS
[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"
0d7ea902
VZ
33 #include "wx/utils.h"
34 #include "wx/dialog.h"
704c499e
VZ
35 #if wxUSE_MSGBOX_HOOK
36 #include "wx/hashmap.h"
37 #endif
2bda0e17
KB
38#endif
39
40#include "wx/msw/private.h"
23e00c55
VZ
41#include "wx/msw/private/button.h"
42#include "wx/msw/private/metrics.h"
43
44#if wxUSE_MSGBOX_HOOK
45 #include "wx/fontutil.h"
4b02d42e
VZ
46 #include "wx/textbuf.h"
47 #include "wx/display.h"
23e00c55 48#endif
2bda0e17 49
676d6550
JS
50// For MB_TASKMODAL
51#ifdef __WXWINCE__
23e00c55 52 #include "wx/msw/wince/missing.h"
676d6550
JS
53#endif
54
2bda0e17 55IMPLEMENT_CLASS(wxMessageDialog, wxDialog)
2bda0e17 56
704c499e
VZ
57#if wxUSE_MSGBOX_HOOK
58
1d89da8a
VZ
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
61//
62// TODO: replace this with wxTLS once it's available
63WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog *,
64 wxIntegerHash, wxIntegerEqual,
65 wxMessageDialogMap);
66
4b02d42e
VZ
67// the order in this array is the one in which buttons appear in the
68// message box
69const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] =
70{
71 { IDYES, &wxMessageDialog::GetYesLabel },
72 { IDNO, &wxMessageDialog::GetNoLabel },
73 { IDOK, &wxMessageDialog::GetOKLabel },
74 { IDCANCEL, &wxMessageDialog::GetCancelLabel },
75};
76
1d89da8a
VZ
77namespace
78{
79
80wxMessageDialogMap& HookMap()
81{
82 static wxMessageDialogMap s_Map;
83
84 return s_Map;
85}
86
4b02d42e
VZ
87/*
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.
95 */
96
97// convert the given RECT from screen to client coordinates in place
98void ScreenRectToClient(HWND hwnd, RECT& rc)
99{
100 // map from desktop (i.e. screen) coordinates to ones of this window
101 //
102 // notice that a RECT is laid out as 2 consecutive POINTs so the cast is
103 // valid
104 ::MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast<POINT *>(&rc), 2);
105}
106
107// set window position to the given rect
108inline void SetWindowRect(HWND hwnd, const RECT& rc)
109{
110 ::MoveWindow(hwnd,
111 rc.left, rc.top,
112 rc.right - rc.left, rc.bottom - rc.top,
113 FALSE);
114}
115
116// set window position expressed in screen coordinates, whether the window is
117// child or top level
118void MoveWindowToScreenRect(HWND hwnd, RECT rc)
119{
120 ScreenRectToClient(::GetParent(hwnd), rc);
121
122 SetWindowRect(hwnd, rc);
123}
124
125// helper of AdjustButtonLabels(): move the given window by dx
126//
127// works for both child and top level windows
128void OffsetWindow(HWND hwnd, int dx)
129{
130 RECT rc = wxGetWindowRect(hwnd);
131
132 rc.left += dx;
133 rc.right += dx;
134
135 MoveWindowToScreenRect(hwnd, rc);
136}
137
1d89da8a
VZ
138} // anonymous namespace
139
140/* static */
141WXLRESULT wxCALLBACK
142wxMessageDialog::HookFunction(int code, WXWPARAM wParam, WXLPARAM lParam)
143{
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") );
149
150 wxMessageDialog * const wnd = node->second;
151
152 const HHOOK hhook = (HHOOK)wnd->m_hook;
153 const LRESULT rc = ::CallNextHookEx(hhook, code, wParam, lParam);
154
23e00c55
VZ
155 if ( code == HCBT_ACTIVATE )
156 {
157 // we won't need this hook any longer
158 ::UnhookWindowsHookEx(hhook);
159 wnd->m_hook = NULL;
160 HookMap().erase(tid);
161
162 wnd->SetHWND((HWND)wParam);
163
4b02d42e
VZ
164 // replace the static text with an edit control if the message box is
165 // too big to fit the display
166 wnd->ReplaceStaticWithEdit();
167
b6bcaaca
VZ
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();
172
23e00c55
VZ
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
177
23e00c55
VZ
178 // there seems to be no reason to leave it set
179 wnd->SetHWND(NULL);
180 }
181
182 return rc;
183}
184
4b02d42e 185void wxMessageDialog::ReplaceStaticWithEdit()
23e00c55 186{
4b02d42e
VZ
187 // check if the message box fits the display
188 int nDisplay = wxDisplay::GetFromWindow(this);
189 if ( nDisplay == wxNOT_FOUND )
190 nDisplay = 0;
191 const wxRect rectDisplay = wxDisplay(nDisplay).GetClientArea();
23e00c55 192
4b02d42e 193 if ( rectDisplay.Contains(GetRect()) )
23e00c55 194 {
4b02d42e
VZ
195 // nothing to do
196 return;
23e00c55
VZ
197 }
198
23e00c55 199
4b02d42e
VZ
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
202 // ones
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);
23e00c55 206
4b02d42e
VZ
207 if ( !hwndStatic )
208 {
209 wxLogDebug("Failed to find the static text control in message box.");
210 return;
211 }
23e00c55 212
4b02d42e
VZ
213 // set the right font for GetCharHeight() call below
214 wxWindowBase::SetFont(GetMessageFont());
215
216 // put the new edit control at the same place
217 RECT rc = wxGetWindowRect(hwndStatic);
218 ScreenRectToClient(GetHwnd(), rc);
219
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 -
224 (
225 2*::GetSystemMetrics(SM_CYFIXEDFRAME) +
226 ::GetSystemMetrics(SM_CYCAPTION) +
227 5*GetCharHeight() // buttons + margins
228 );
229 const int dh = (rc.bottom - rc.top) - hText; // vertical space we save
230 rc.bottom -= dh;
231
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)
235 //
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);
241 rc.right += dw;
242
243
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 )
249 {
250 if ( *i != '\n' )
251 {
252 text.erase(i + 1, text.end());
253 break;
254 }
255 }
23e00c55 256
4b02d42e
VZ
257 // do create the new control
258 HWND hwndEdit = ::CreateWindow
259 (
260 _T("EDIT"),
cc5e20f8 261 wxTextBuffer::Translate(text).wx_str(),
4b02d42e
VZ
262 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
263 ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
264 rc.left, rc.top,
265 rc.right - rc.left, rc.bottom - rc.top,
266 GetHwnd(),
267 NULL,
268 wxhInstance,
269 NULL
270 );
271
272 if ( !hwndEdit )
273 {
274 wxLogDebug("Creation of replacement edit control failed in message box");
275 return;
276 }
23e00c55 277
4b02d42e
VZ
278 // copy the font from the original control
279 LRESULT hfont = ::SendMessage(hwndStatic, WM_GETFONT, 0, 0);
280 ::SendMessage(hwndEdit, WM_SETFONT, hfont, 0);
281
282 // and get rid of it
283 ::DestroyWindow(hwndStatic);
23e00c55 284
23e00c55 285
4b02d42e
VZ
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;
292 rcBox.left -= dw/2;
293 rcBox.right += dw - dw/2;
294 SetWindowRect(GetHwnd(), rcBox);
295
296 // and adjust all the buttons positions
297 for ( unsigned n = 0; n < WXSIZEOF(ms_buttons); n++ )
1d89da8a 298 {
4b02d42e
VZ
299 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
300 if ( !hwndBtn )
301 continue; // it's ok, not all buttons are always present
302
303 RECT rc = wxGetWindowRect(hwndBtn);
304 rc.top -= dh;
305 rc.bottom -= dh;
306 rc.left += dw/2;
307 rc.right += dw/2;
308 MoveWindowToScreenRect(hwndBtn, rc);
23e00c55 309 }
4b02d42e 310}
23e00c55 311
4b02d42e
VZ
312void wxMessageDialog::AdjustButtonLabels()
313{
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
1d89da8a 317
4b02d42e 318 // TODO-RTL: check whether this works correctly in RTL
23e00c55
VZ
319
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
323 // changes)
4b02d42e
VZ
324 wxWindowBase::SetFont(GetMessageFont());
325
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
331 unsigned n;
332 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
23e00c55 333 {
4b02d42e 334 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
23e00c55
VZ
335 if ( !hwndBtn )
336 continue; // it's ok, not all buttons are always present
337
4b02d42e
VZ
338 numButtons++;
339
340 const wxString label = (this->*ms_buttons[n].getter)();
23e00c55
VZ
341 const wxSize sizeLabel = wxWindowBase::GetTextExtent(label);
342
343 // check if the button is big enough for this label
4b02d42e
VZ
344 const RECT rc = wxGetWindowRect(hwndBtn);
345 if ( !wBtnOld )
346 {
347 // initialize wBtnOld using the first button width, all the other
348 // ones should have the same one
349 wBtnOld = rc.right - rc.left;
350
351 rcBtn = rc; // remember for use below when we reposition the buttons
352 }
353 else
1d89da8a 354 {
4b02d42e
VZ
355 wxASSERT_MSG( wBtnOld == rc.right - rc.left,
356 "all buttons are supposed to be of same width" );
1d89da8a 357 }
23e00c55 358
4b02d42e
VZ
359 const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x;
360 if ( widthNeeded > wBtnNew )
361 wBtnNew = widthNeeded;
362
23e00c55 363 ::SetWindowText(hwndBtn, label.wx_str());
1d89da8a
VZ
364 }
365
4b02d42e
VZ
366 if ( wBtnNew <= wBtnOld )
367 {
368 // all buttons fit, nothing else to do
369 return;
370 }
371
372 // resize the message box to be wider if needed
373 const int wBoxOld = wxGetClientRect(GetHwnd()).right;
374
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
378
379 RECT rcBox = wxGetWindowRect(GetHwnd());
380
381 const int wAllButtons = numButtons*(wBtnNew + MARGIN_INNER) - MARGIN_INNER;
382 int wBoxNew = 2*MARGIN_OUTER + wAllButtons;
383 if ( wBoxNew > wBoxOld )
384 {
385 const int dw = wBoxNew - wBoxOld;
386 rcBox.left -= dw/2;
387 rcBox.right += dw - dw/2;
388
389 SetWindowRect(GetHwnd(), rcBox);
390
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)
394 }
395 else // the current width is big enough
396 {
397 wBoxNew = wBoxOld;
398 }
399
400
401 // finally position all buttons
23e00c55 402
4b02d42e
VZ
403 // notice that we have to take into account the difference between window
404 // and client width
405 rcBtn.left = (rcBox.left + rcBox.right - wxGetClientRect(GetHwnd()).right +
406 wBoxNew - wAllButtons) / 2;
407 rcBtn.right = rcBtn.left + wBtnNew;
23e00c55 408
4b02d42e
VZ
409 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
410 {
411 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
412 if ( !hwndBtn )
413 continue;
414
415 MoveWindowToScreenRect(hwndBtn, rcBtn);
416
417 rcBtn.left += wBtnNew + MARGIN_INNER;
418 rcBtn.right += wBtnNew + MARGIN_INNER;
419 }
1d89da8a
VZ
420}
421
704c499e
VZ
422#endif // wxUSE_MSGBOX_HOOK
423
4b02d42e
VZ
424/* static */
425wxFont wxMessageDialog::GetMessageFont()
426{
427 const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics();
428 return wxNativeFontInfo(ncm.lfMessageFont);
429}
704c499e 430
0d7ea902 431int wxMessageDialog::ShowModal()
2bda0e17 432{
a543e3ce 433 if ( !wxTheApp->GetTopWindow() )
0d7ea902
VZ
434 {
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();
441 }
93c95e18 442
b8505921 443 // use the top level window as parent if none specified
a543e3ce
VZ
444 if ( !m_parent )
445 m_parent = FindSuitableParent();
446 HWND hWnd = m_parent ? GetHwndOf(m_parent) : NULL;
b8505921
VZ
447
448 // translate wx style in MSW
f45d6ade 449 unsigned int msStyle;
e5b50758 450 const long wxStyle = GetMessageDialogStyle();
f45d6ade 451 if ( wxStyle & wxYES_NO )
0d7ea902 452 {
3180bc0e 453#if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
e5b50758 454 if (wxStyle & wxCANCEL)
0d7ea902
VZ
455 msStyle = MB_YESNOCANCEL;
456 else
3180bc0e 457#endif // !(__SMARTPHONE__ && __WXWINCE__)
0d7ea902 458 msStyle = MB_YESNO;
93c95e18 459
f45d6ade 460 if ( wxStyle & wxNO_DEFAULT )
0d7ea902 461 msStyle |= MB_DEFBUTTON2;
f45d6ade
VZ
462 else if ( wxStyle & wxCANCEL_DEFAULT )
463 msStyle |= MB_DEFBUTTON3;
0d7ea902 464 }
f45d6ade 465 else // without Yes/No we're going to have an OK button
0d7ea902 466 {
f45d6ade
VZ
467 if ( wxStyle & wxCANCEL )
468 {
0d7ea902 469 msStyle = MB_OKCANCEL;
f45d6ade
VZ
470
471 if ( wxStyle & wxCANCEL_DEFAULT )
472 msStyle |= MB_DEFBUTTON2;
473 }
474 else // just "OK"
475 {
0d7ea902 476 msStyle = MB_OK;
f45d6ade 477 }
0d7ea902 478 }
f45d6ade 479
e5b50758 480 if (wxStyle & wxICON_EXCLAMATION)
0d7ea902 481 msStyle |= MB_ICONEXCLAMATION;
e5b50758 482 else if (wxStyle & wxICON_HAND)
0d7ea902 483 msStyle |= MB_ICONHAND;
e5b50758 484 else if (wxStyle & wxICON_INFORMATION)
0d7ea902 485 msStyle |= MB_ICONINFORMATION;
e5b50758 486 else if (wxStyle & wxICON_QUESTION)
0d7ea902 487 msStyle |= MB_ICONQUESTION;
2bda0e17 488
e5b50758 489 if ( wxStyle & wxSTAY_ON_TOP )
a7fd7c78
VZ
490 msStyle |= MB_TOPMOST;
491
08a58133 492#ifndef __WXWINCE__
978af864
VZ
493 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
494 msStyle |= MB_RTLREADING | MB_RIGHT;
08a58133 495#endif
978af864 496
0d7ea902
VZ
497 if (hWnd)
498 msStyle |= MB_APPLMODAL;
499 else
500 msStyle |= MB_TASKMODAL;
93c95e18 501
12e424d2
VZ
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)
2afb9e16 505 wxString message = GetFullMessage();
12e424d2
VZ
506#if wxUSE_UNICODE
507 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
508 {
509 // NB: not all compilers support \u escapes
510 static const wchar_t wchRLM = 0x200f;
511 message.Prepend(wxString(wchRLM, 2));
512 }
513#endif // wxUSE_UNICODE
514
704c499e 515#if wxUSE_MSGBOX_HOOK
4b02d42e
VZ
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;
704c499e 523#endif // wxUSE_MSGBOX_HOOK
1d89da8a 524
b8505921 525 // do show the dialog
e0a050e3 526 int msAns = MessageBox(hWnd, message.wx_str(), m_caption.wx_str(), msStyle);
b8505921 527 int ans;
0d7ea902
VZ
528 switch (msAns)
529 {
b8505921
VZ
530 default:
531 wxFAIL_MSG(_T("unexpected ::MessageBox() return code"));
532 // fall through
533
0d7ea902
VZ
534 case IDCANCEL:
535 ans = wxID_CANCEL;
536 break;
537 case IDOK:
538 ans = wxID_OK;
539 break;
540 case IDYES:
541 ans = wxID_YES;
542 break;
543 case IDNO:
544 ans = wxID_NO;
545 break;
546 }
547 return ans;
2bda0e17 548}
a8ff046b
VZ
549
550#endif // wxUSE_MSGDLG