]> git.saurik.com Git - wxWidgets.git/blame - src/msw/msgdlg.cpp
Update copyright year in the version information resource.
[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
704c499e
VZ
21// there is no hook support under CE so we can't use the code for message box
22// positioning there
23#ifndef __WXWINCE__
24 #define wxUSE_MSGBOX_HOOK 1
25#else
26 #define wxUSE_MSGBOX_HOOK 0
27#endif
28
2bda0e17 29#ifndef WX_PRECOMP
d2bc8725 30 #include "wx/msgdlg.h"
35bc781e 31 #include "wx/app.h"
42c097b8 32 #include "wx/intl.h"
0d7ea902 33 #include "wx/utils.h"
d2bc8725 34 #include "wx/msw/private.h"
704c499e
VZ
35 #if wxUSE_MSGBOX_HOOK
36 #include "wx/hashmap.h"
37 #endif
2bda0e17
KB
38#endif
39
d2bc8725 40#include "wx/ptr_scpd.h"
ede7b017 41#include "wx/dynlib.h"
23e00c55
VZ
42#include "wx/msw/private/button.h"
43#include "wx/msw/private/metrics.h"
ede7b017 44#include "wx/msw/private/msgdlg.h"
691745ab 45#include "wx/modalhook.h"
23e00c55
VZ
46
47#if wxUSE_MSGBOX_HOOK
48 #include "wx/fontutil.h"
4b02d42e
VZ
49 #include "wx/textbuf.h"
50 #include "wx/display.h"
23e00c55 51#endif
2bda0e17 52
676d6550
JS
53// For MB_TASKMODAL
54#ifdef __WXWINCE__
23e00c55 55 #include "wx/msw/wince/missing.h"
676d6550
JS
56#endif
57
5f519da2
VZ
58// Interestingly, this symbol currently seems to be absent from Platform SDK
59// headers but it is documented at MSDN.
60#ifndef TDF_SIZE_TO_CONTENT
61 #define TDF_SIZE_TO_CONTENT 0x1000000
62#endif
63
ede7b017
VZ
64using namespace wxMSWMessageDialog;
65
2bda0e17 66IMPLEMENT_CLASS(wxMessageDialog, wxDialog)
2bda0e17 67
704c499e
VZ
68#if wxUSE_MSGBOX_HOOK
69
1d89da8a
VZ
70// there can potentially be one message box per thread so we use a hash map
71// with thread ids as keys and (currently shown) message boxes as values
72//
73// TODO: replace this with wxTLS once it's available
74WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog *,
75 wxIntegerHash, wxIntegerEqual,
76 wxMessageDialogMap);
77
4b02d42e
VZ
78// the order in this array is the one in which buttons appear in the
79// message box
80const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] =
81{
82 { IDYES, &wxMessageDialog::GetYesLabel },
83 { IDNO, &wxMessageDialog::GetNoLabel },
84 { IDOK, &wxMessageDialog::GetOKLabel },
85 { IDCANCEL, &wxMessageDialog::GetCancelLabel },
86};
87
1d89da8a
VZ
88namespace
89{
90
91wxMessageDialogMap& HookMap()
92{
93 static wxMessageDialogMap s_Map;
94
95 return s_Map;
96}
97
4b02d42e
VZ
98/*
99 All this code is used for adjusting the message box layout when we mess
100 with its contents. It's rather complicated because we try hard to avoid
101 assuming much about the standard layout details and so, instead of just
102 laying out everything ourselves (which would have been so much simpler!)
103 we try to only modify the existing controls positions by offsetting them
104 from their default ones in the hope that this will continue to work with
105 the future Windows versions.
106 */
107
108// convert the given RECT from screen to client coordinates in place
109void ScreenRectToClient(HWND hwnd, RECT& rc)
110{
111 // map from desktop (i.e. screen) coordinates to ones of this window
112 //
113 // notice that a RECT is laid out as 2 consecutive POINTs so the cast is
114 // valid
115 ::MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast<POINT *>(&rc), 2);
116}
117
118// set window position to the given rect
119inline void SetWindowRect(HWND hwnd, const RECT& rc)
120{
121 ::MoveWindow(hwnd,
122 rc.left, rc.top,
123 rc.right - rc.left, rc.bottom - rc.top,
124 FALSE);
125}
126
127// set window position expressed in screen coordinates, whether the window is
128// child or top level
129void MoveWindowToScreenRect(HWND hwnd, RECT rc)
130{
131 ScreenRectToClient(::GetParent(hwnd), rc);
132
133 SetWindowRect(hwnd, rc);
134}
135
136// helper of AdjustButtonLabels(): move the given window by dx
137//
138// works for both child and top level windows
139void OffsetWindow(HWND hwnd, int dx)
140{
141 RECT rc = wxGetWindowRect(hwnd);
142
143 rc.left += dx;
144 rc.right += dx;
145
146 MoveWindowToScreenRect(hwnd, rc);
147}
148
1d89da8a
VZ
149} // anonymous namespace
150
151/* static */
152WXLRESULT wxCALLBACK
153wxMessageDialog::HookFunction(int code, WXWPARAM wParam, WXLPARAM lParam)
154{
155 // Find the thread-local instance of wxMessageDialog
156 const DWORD tid = ::GetCurrentThreadId();
157 wxMessageDialogMap::iterator node = HookMap().find(tid);
158 wxCHECK_MSG( node != HookMap().end(), false,
159 wxT("bogus thread id in wxMessageDialog::Hook") );
160
161 wxMessageDialog * const wnd = node->second;
162
163 const HHOOK hhook = (HHOOK)wnd->m_hook;
164 const LRESULT rc = ::CallNextHookEx(hhook, code, wParam, lParam);
165
23e00c55
VZ
166 if ( code == HCBT_ACTIVATE )
167 {
168 // we won't need this hook any longer
169 ::UnhookWindowsHookEx(hhook);
170 wnd->m_hook = NULL;
171 HookMap().erase(tid);
172
173 wnd->SetHWND((HWND)wParam);
174
4b02d42e
VZ
175 // replace the static text with an edit control if the message box is
176 // too big to fit the display
177 wnd->ReplaceStaticWithEdit();
178
b6bcaaca
VZ
179 // update the labels if necessary: we need to do it before centering
180 // the dialog as this can change its size
181 if ( wnd->HasCustomLabels() )
182 wnd->AdjustButtonLabels();
183
23e00c55
VZ
184 // centre the message box on its parent if requested
185 if ( wnd->GetMessageDialogStyle() & wxCENTER )
186 wnd->Center(); // center on parent
187 //else: default behaviour, center on screen
188
23e00c55
VZ
189 // there seems to be no reason to leave it set
190 wnd->SetHWND(NULL);
191 }
192
193 return rc;
194}
195
4b02d42e 196void wxMessageDialog::ReplaceStaticWithEdit()
23e00c55 197{
4b02d42e
VZ
198 // check if the message box fits the display
199 int nDisplay = wxDisplay::GetFromWindow(this);
200 if ( nDisplay == wxNOT_FOUND )
201 nDisplay = 0;
202 const wxRect rectDisplay = wxDisplay(nDisplay).GetClientArea();
23e00c55 203
4b02d42e 204 if ( rectDisplay.Contains(GetRect()) )
23e00c55 205 {
4b02d42e
VZ
206 // nothing to do
207 return;
23e00c55
VZ
208 }
209
23e00c55 210
4b02d42e
VZ
211 // find the static control to replace: normally there are two of them, the
212 // icon and the text itself so search for all of them and ignore the icon
213 // ones
9a83f860 214 HWND hwndStatic = ::FindWindowEx(GetHwnd(), NULL, wxT("STATIC"), NULL);
4b02d42e 215 if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON )
9a83f860 216 hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), NULL);
23e00c55 217
4b02d42e
VZ
218 if ( !hwndStatic )
219 {
220 wxLogDebug("Failed to find the static text control in message box.");
221 return;
222 }
23e00c55 223
4b02d42e
VZ
224 // set the right font for GetCharHeight() call below
225 wxWindowBase::SetFont(GetMessageFont());
226
227 // put the new edit control at the same place
228 RECT rc = wxGetWindowRect(hwndStatic);
229 ScreenRectToClient(GetHwnd(), rc);
230
231 // but make it less tall so that the message box fits on the screen: we try
232 // to make the message box take no more than 7/8 of the screen to leave
233 // some space above and below it
234 const int hText = (7*rectDisplay.height)/8 -
235 (
236 2*::GetSystemMetrics(SM_CYFIXEDFRAME) +
237 ::GetSystemMetrics(SM_CYCAPTION) +
238 5*GetCharHeight() // buttons + margins
239 );
240 const int dh = (rc.bottom - rc.top) - hText; // vertical space we save
241 rc.bottom -= dh;
242
243 // and it also must be wider as it needs a vertical scrollbar (in order
244 // to preserve the word wrap, otherwise the number of lines would change
245 // and we want the control to look as similar as possible to the original)
246 //
247 // NB: you would have thought that 2*SM_CXEDGE would be enough but it
248 // isn't, somehow, and the text control breaks lines differently from
249 // the static one so fudge by adding some extra space
250 const int dw = ::GetSystemMetrics(SM_CXVSCROLL) +
251 4*::GetSystemMetrics(SM_CXEDGE);
252 rc.right += dw;
253
254
255 // chop of the trailing new line(s) from the message box text, they are
256 // ignored by the static control but result in extra lines and hence extra
257 // scrollbar position in the edit one
258 wxString text(wxGetWindowText(hwndStatic));
d3019e4d 259 for ( wxString::reverse_iterator i = text.rbegin(); i != text.rend(); ++i )
4b02d42e
VZ
260 {
261 if ( *i != '\n' )
262 {
3672f9d0
VZ
263 // found last non-newline char, remove anything after it if
264 // necessary and stop in any case
265 if ( i != text.rbegin() )
266 text.erase(i.base() + 1, text.end());
4b02d42e
VZ
267 break;
268 }
269 }
23e00c55 270
4b02d42e
VZ
271 // do create the new control
272 HWND hwndEdit = ::CreateWindow
273 (
9a83f860 274 wxT("EDIT"),
017dc06b 275 wxTextBuffer::Translate(text).t_str(),
4b02d42e
VZ
276 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
277 ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
278 rc.left, rc.top,
279 rc.right - rc.left, rc.bottom - rc.top,
280 GetHwnd(),
281 NULL,
5431e4a6 282 wxGetInstance(),
4b02d42e
VZ
283 NULL
284 );
285
286 if ( !hwndEdit )
287 {
288 wxLogDebug("Creation of replacement edit control failed in message box");
289 return;
290 }
23e00c55 291
4b02d42e
VZ
292 // copy the font from the original control
293 LRESULT hfont = ::SendMessage(hwndStatic, WM_GETFONT, 0, 0);
294 ::SendMessage(hwndEdit, WM_SETFONT, hfont, 0);
295
296 // and get rid of it
297 ::DestroyWindow(hwndStatic);
23e00c55 298
23e00c55 299
4b02d42e
VZ
300 // shrink and centre the message box vertically and widen it box to account
301 // for the extra scrollbar
302 RECT rcBox = wxGetWindowRect(GetHwnd());
303 const int hMsgBox = rcBox.bottom - rcBox.top - dh;
304 rcBox.top = (rectDisplay.height - hMsgBox)/2;
305 rcBox.bottom = rcBox.top + hMsgBox + (rectDisplay.height - hMsgBox)%2;
306 rcBox.left -= dw/2;
307 rcBox.right += dw - dw/2;
308 SetWindowRect(GetHwnd(), rcBox);
309
310 // and adjust all the buttons positions
311 for ( unsigned n = 0; n < WXSIZEOF(ms_buttons); n++ )
1d89da8a 312 {
4b02d42e
VZ
313 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
314 if ( !hwndBtn )
315 continue; // it's ok, not all buttons are always present
316
317 RECT rc = wxGetWindowRect(hwndBtn);
318 rc.top -= dh;
319 rc.bottom -= dh;
320 rc.left += dw/2;
321 rc.right += dw/2;
322 MoveWindowToScreenRect(hwndBtn, rc);
23e00c55 323 }
4b02d42e 324}
23e00c55 325
4b02d42e
VZ
326void wxMessageDialog::AdjustButtonLabels()
327{
328 // changing the button labels is the easy part but we also need to ensure
329 // that the buttons are big enough for the label strings and increase their
330 // size (and maybe the size of the message box itself) if they are not
1d89da8a 331
4b02d42e 332 // TODO-RTL: check whether this works correctly in RTL
23e00c55
VZ
333
334 // we want to use this font in GetTextExtent() calls below but we don't
335 // want to send WM_SETFONT to the message box, who knows how is it going to
336 // react to it (right now it doesn't seem to do anything but what if this
337 // changes)
4b02d42e
VZ
338 wxWindowBase::SetFont(GetMessageFont());
339
340 // first iteration: find the widest button and update the buttons labels
341 int wBtnOld = 0, // current buttons width
342 wBtnNew = 0; // required new buttons width
343 RECT rcBtn; // stores the button height and y positions
344 unsigned numButtons = 0; // total number of buttons in the message box
345 unsigned n;
346 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
23e00c55 347 {
4b02d42e 348 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
23e00c55
VZ
349 if ( !hwndBtn )
350 continue; // it's ok, not all buttons are always present
351
4b02d42e
VZ
352 numButtons++;
353
354 const wxString label = (this->*ms_buttons[n].getter)();
23e00c55
VZ
355 const wxSize sizeLabel = wxWindowBase::GetTextExtent(label);
356
357 // check if the button is big enough for this label
4b02d42e
VZ
358 const RECT rc = wxGetWindowRect(hwndBtn);
359 if ( !wBtnOld )
360 {
361 // initialize wBtnOld using the first button width, all the other
362 // ones should have the same one
363 wBtnOld = rc.right - rc.left;
364
365 rcBtn = rc; // remember for use below when we reposition the buttons
366 }
367 else
1d89da8a 368 {
4b02d42e
VZ
369 wxASSERT_MSG( wBtnOld == rc.right - rc.left,
370 "all buttons are supposed to be of same width" );
1d89da8a 371 }
23e00c55 372
4b02d42e
VZ
373 const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x;
374 if ( widthNeeded > wBtnNew )
375 wBtnNew = widthNeeded;
376
017dc06b 377 ::SetWindowText(hwndBtn, label.t_str());
1d89da8a
VZ
378 }
379
4b02d42e
VZ
380 if ( wBtnNew <= wBtnOld )
381 {
382 // all buttons fit, nothing else to do
383 return;
384 }
385
386 // resize the message box to be wider if needed
387 const int wBoxOld = wxGetClientRect(GetHwnd()).right;
388
389 const int CHAR_WIDTH = GetCharWidth();
390 const int MARGIN_OUTER = 2*CHAR_WIDTH; // margin between box and buttons
391 const int MARGIN_INNER = CHAR_WIDTH; // margin between buttons
392
393 RECT rcBox = wxGetWindowRect(GetHwnd());
394
395 const int wAllButtons = numButtons*(wBtnNew + MARGIN_INNER) - MARGIN_INNER;
396 int wBoxNew = 2*MARGIN_OUTER + wAllButtons;
397 if ( wBoxNew > wBoxOld )
398 {
399 const int dw = wBoxNew - wBoxOld;
400 rcBox.left -= dw/2;
401 rcBox.right += dw - dw/2;
402
403 SetWindowRect(GetHwnd(), rcBox);
404
405 // surprisingly, we don't need to resize the static text control, it
406 // seems to adjust itself to the new size, at least under Windows 2003
407 // (TODO: test if this happens on older Windows versions)
408 }
409 else // the current width is big enough
410 {
411 wBoxNew = wBoxOld;
412 }
413
414
415 // finally position all buttons
23e00c55 416
4b02d42e
VZ
417 // notice that we have to take into account the difference between window
418 // and client width
419 rcBtn.left = (rcBox.left + rcBox.right - wxGetClientRect(GetHwnd()).right +
420 wBoxNew - wAllButtons) / 2;
421 rcBtn.right = rcBtn.left + wBtnNew;
23e00c55 422
4b02d42e
VZ
423 for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
424 {
425 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
426 if ( !hwndBtn )
427 continue;
428
429 MoveWindowToScreenRect(hwndBtn, rcBtn);
430
431 rcBtn.left += wBtnNew + MARGIN_INNER;
432 rcBtn.right += wBtnNew + MARGIN_INNER;
433 }
1d89da8a
VZ
434}
435
704c499e
VZ
436#endif // wxUSE_MSGBOX_HOOK
437
4b02d42e
VZ
438/* static */
439wxFont wxMessageDialog::GetMessageFont()
440{
441 const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics();
442 return wxNativeFontInfo(ncm.lfMessageFont);
443}
704c499e 444
ede7b017 445int wxMessageDialog::ShowMessageBox()
2bda0e17 446{
a543e3ce 447 if ( !wxTheApp->GetTopWindow() )
0d7ea902
VZ
448 {
449 // when the message box is shown from wxApp::OnInit() (i.e. before the
450 // message loop is entered), this must be done or the next message box
451 // will never be shown - just try putting 2 calls to wxMessageBox() in
452 // OnInit() to see it
453 while ( wxTheApp->Pending() )
454 wxTheApp->Dispatch();
455 }
93c95e18 456
b8505921 457 // use the top level window as parent if none specified
cdc48273 458 m_parent = GetParentForModalDialog();
a543e3ce 459 HWND hWnd = m_parent ? GetHwndOf(m_parent) : NULL;
b8505921 460
42c097b8
VZ
461#if wxUSE_INTL
462 // native message box always uses the current user locale but the program
463 // may be using a different one and in this case we need to manually
c22ce321
VZ
464 // translate the default button labels (if they're non default we have no
465 // way to translate them and so we must assume they were already
466 // translated) to avoid mismatch between the language of the message box
467 // text and its buttons
42c097b8
VZ
468 wxLocale * const loc = wxGetLocale();
469 if ( loc && loc->GetLanguage() != wxLocale::GetSystemLanguage() )
470 {
c22ce321
VZ
471 if ( m_dialogStyle & wxYES_NO &&
472 (GetCustomYesLabel().empty() && GetCustomNoLabel().empty()) )
473
42c097b8
VZ
474 {
475 // use the strings with mnemonics here as the native message box
476 // does
477 SetYesNoLabels(_("&Yes"), _("&No"));
478 }
479
480 // we may or not have the Ok/Cancel buttons but either we do have them
481 // or we already made the labels custom because we called
482 // SetYesNoLabels() above so doing this does no harm -- and is
483 // necessary in wxYES_NO | wxCANCEL case
484 //
485 // note that we don't use mnemonics here for consistency with the
486 // native message box (which probably doesn't use them because
487 // Enter/Esc keys can be already used to dismiss the message box
488 // using keyboard)
c22ce321
VZ
489 if ( GetCustomOKLabel().empty() && GetCustomCancelLabel().empty() )
490 SetOKCancelLabels(_("OK"), _("Cancel"));
42c097b8
VZ
491 }
492#endif // wxUSE_INTL
493
b8505921 494 // translate wx style in MSW
f45d6ade 495 unsigned int msStyle;
e5b50758 496 const long wxStyle = GetMessageDialogStyle();
f45d6ade 497 if ( wxStyle & wxYES_NO )
0d7ea902 498 {
3180bc0e 499#if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
e5b50758 500 if (wxStyle & wxCANCEL)
0d7ea902
VZ
501 msStyle = MB_YESNOCANCEL;
502 else
3180bc0e 503#endif // !(__SMARTPHONE__ && __WXWINCE__)
0d7ea902 504 msStyle = MB_YESNO;
93c95e18 505
f45d6ade 506 if ( wxStyle & wxNO_DEFAULT )
0d7ea902 507 msStyle |= MB_DEFBUTTON2;
f45d6ade
VZ
508 else if ( wxStyle & wxCANCEL_DEFAULT )
509 msStyle |= MB_DEFBUTTON3;
0d7ea902 510 }
f45d6ade 511 else // without Yes/No we're going to have an OK button
0d7ea902 512 {
f45d6ade
VZ
513 if ( wxStyle & wxCANCEL )
514 {
0d7ea902 515 msStyle = MB_OKCANCEL;
f45d6ade
VZ
516
517 if ( wxStyle & wxCANCEL_DEFAULT )
518 msStyle |= MB_DEFBUTTON2;
519 }
520 else // just "OK"
521 {
0d7ea902 522 msStyle = MB_OK;
f45d6ade 523 }
0d7ea902 524 }
f45d6ade 525
7112cdd1
VZ
526 if ( wxStyle & wxHELP )
527 {
528 msStyle |= MB_HELP;
529 }
530
b76b3d28 531 // set the icon style
a4578b0c
VZ
532 switch ( GetEffectiveIcon() )
533 {
534 case wxICON_ERROR:
535 msStyle |= MB_ICONHAND;
536 break;
537
538 case wxICON_WARNING:
539 msStyle |= MB_ICONEXCLAMATION;
540 break;
541
542 case wxICON_QUESTION:
543 msStyle |= MB_ICONQUESTION;
544 break;
545
546 case wxICON_INFORMATION:
547 msStyle |= MB_ICONINFORMATION;
548 break;
549 }
2bda0e17 550
e5b50758 551 if ( wxStyle & wxSTAY_ON_TOP )
a7fd7c78
VZ
552 msStyle |= MB_TOPMOST;
553
08a58133 554#ifndef __WXWINCE__
978af864
VZ
555 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
556 msStyle |= MB_RTLREADING | MB_RIGHT;
08a58133 557#endif
978af864 558
0d7ea902
VZ
559 if (hWnd)
560 msStyle |= MB_APPLMODAL;
561 else
562 msStyle |= MB_TASKMODAL;
93c95e18 563
12e424d2
VZ
564 // per MSDN documentation for MessageBox() we can prefix the message with 2
565 // right-to-left mark characters to tell the function to use RTL layout
566 // (unfortunately this only works in Unicode builds)
2afb9e16 567 wxString message = GetFullMessage();
12e424d2
VZ
568#if wxUSE_UNICODE
569 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
570 {
571 // NB: not all compilers support \u escapes
572 static const wchar_t wchRLM = 0x200f;
573 message.Prepend(wxString(wchRLM, 2));
574 }
575#endif // wxUSE_UNICODE
576
704c499e 577#if wxUSE_MSGBOX_HOOK
4b02d42e
VZ
578 // install the hook in any case as we don't know in advance if the message
579 // box is not going to be too big (requiring the replacement of the static
580 // control with an edit one)
581 const DWORD tid = ::GetCurrentThreadId();
582 m_hook = ::SetWindowsHookEx(WH_CBT,
583 &wxMessageDialog::HookFunction, NULL, tid);
584 HookMap()[tid] = this;
704c499e 585#endif // wxUSE_MSGBOX_HOOK
1d89da8a 586
b8505921 587 // do show the dialog
017dc06b 588 int msAns = MessageBox(hWnd, message.t_str(), m_caption.t_str(), msStyle);
ede7b017
VZ
589
590 return MSWTranslateReturnCode(msAns);
591}
592
5b1985a9 593int wxMessageDialog::ShowModal()
ede7b017 594{
691745ab 595 WX_HOOK_MODAL_DIALOG();
643e9cf9 596
ede7b017 597#ifdef wxHAS_MSW_TASKDIALOG
5b1985a9 598 if ( HasNativeTaskDialog() )
ede7b017 599 {
5b1985a9
VZ
600 TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
601 wxCHECK_MSG( taskDialogIndirect, wxID_CANCEL, wxS("no task dialog?") );
ede7b017 602
5b1985a9
VZ
603 WinStruct<TASKDIALOGCONFIG> tdc;
604 wxMSWTaskDialogConfig wxTdc( *this );
605 wxTdc.MSWCommonTaskDialogInit( tdc );
ede7b017 606
5b1985a9
VZ
607 int msAns;
608 HRESULT hr = taskDialogIndirect( &tdc, &msAns, NULL, NULL );
609 if ( FAILED(hr) )
610 {
611 wxLogApiError( "TaskDialogIndirect", hr );
612 return wxID_CANCEL;
613 }
ede7b017 614
dc8c61be
DS
615 // In case only an "OK" button was specified we actually created a
616 // "Cancel" button (see comment in MSWCommonTaskDialogInit). This
617 // results in msAns being IDCANCEL while we want IDOK (just like
618 // how the native MessageBox function does with only an "OK" button).
619 if ( (msAns == IDCANCEL)
620 && !(GetMessageDialogStyle() & (wxYES_NO|wxCANCEL)) )
621 {
622 msAns = IDOK;
623 }
624
5b1985a9
VZ
625 return MSWTranslateReturnCode( msAns );
626 }
627#endif // wxHAS_MSW_TASKDIALOG
ede7b017
VZ
628
629 return ShowMessageBox();
630}
631
67315c8b
VZ
632long wxMessageDialog::GetEffectiveIcon() const
633{
634 // only use the auth needed icon if available, otherwise fallback to the default logic
635 if ( (m_dialogStyle & wxICON_AUTH_NEEDED) &&
636 wxMSWMessageDialog::HasNativeTaskDialog() )
637 {
638 return wxICON_AUTH_NEEDED;
639 }
640
641 return wxMessageDialogBase::GetEffectiveIcon();
642}
643
dd53b347
VZ
644void wxMessageDialog::DoCentre(int dir)
645{
646#ifdef wxHAS_MSW_TASKDIALOG
647 // Task dialog is always centered on its parent window and trying to center
648 // it manually doesn't work because its HWND is not created yet so don't
649 // even try as this would only result in (debug) error messages.
650 if ( HasNativeTaskDialog() )
651 return;
652#endif // wxHAS_MSW_TASKDIALOG
653
00bc0d17 654 wxMessageDialogBase::DoCentre(dir);
dd53b347
VZ
655}
656
ede7b017
VZ
657// ----------------------------------------------------------------------------
658// Helpers of the wxMSWMessageDialog namespace
659// ----------------------------------------------------------------------------
660
661#ifdef wxHAS_MSW_TASKDIALOG
662
663wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg)
7112cdd1 664 : buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS])
ede7b017
VZ
665{
666 parent = dlg.GetParentForModalDialog();
667 caption = dlg.GetCaption();
668 message = dlg.GetMessage();
669 extendedMessage = dlg.GetExtendedMessage();
aebe0d3b
VZ
670
671 // Before wxMessageDialog added support for extended message it was common
672 // practice to have long multiline texts in the message box with the first
673 // line playing the role of the main message and the rest of the extended
674 // one. Try to detect such usage automatically here by synthesizing the
675 // extended message on our own if it wasn't given.
676 if ( extendedMessage.empty() )
677 {
678 // Check if there is a blank separating line after the first line (this
679 // is not the same as searching for "\n\n" as we want the automatically
680 // recognized main message be single line to avoid embarrassing false
681 // positives).
682 const size_t posNL = message.find('\n');
683 if ( posNL != wxString::npos &&
684 posNL < message.length() - 1 &&
685 message[posNL + 1 ] == '\n' )
686 {
687 extendedMessage.assign(message, posNL + 2, wxString::npos);
688 message.erase(posNL);
689 }
690 }
691
ede7b017
VZ
692 iconId = dlg.GetEffectiveIcon();
693 style = dlg.GetMessageDialogStyle();
694 useCustomLabels = dlg.HasCustomLabels();
695 btnYesLabel = dlg.GetYesLabel();
696 btnNoLabel = dlg.GetNoLabel();
697 btnOKLabel = dlg.GetOKLabel();
698 btnCancelLabel = dlg.GetCancelLabel();
7112cdd1 699 btnHelpLabel = dlg.GetHelpLabel();
ede7b017
VZ
700}
701
702void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc)
703{
5f519da2
VZ
704 // Use TDF_SIZE_TO_CONTENT to try to prevent Windows from truncating or
705 // ellipsizing the message text. This doesn't always work as Windows will
706 // still do it if the message contains too long "words" (i.e. runs of the
707 // text without spaces) but at least it ensures that the message text is
708 // fully shown for reasonably-sized words whereas without it using almost
709 // any file system path in a message box would result in truncation.
710 tdc.dwFlags = TDF_EXPAND_FOOTER_AREA |
711 TDF_POSITION_RELATIVE_TO_WINDOW |
712 TDF_SIZE_TO_CONTENT;
ede7b017 713 tdc.hInstance = wxGetInstance();
017dc06b 714 tdc.pszWindowTitle = caption.t_str();
ede7b017
VZ
715
716 // use the top level window as parent if none specified
717 tdc.hwndParent = parent ? GetHwndOf(parent) : NULL;
718
719 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
720 tdc.dwFlags |= TDF_RTL_LAYOUT;
aebe0d3b
VZ
721
722 // If we have both the main and extended messages, just use them as
723 // intended. However if only one message is given we normally use it as the
724 // content and not as the main instruction because the latter is supposed
725 // to stand out compared to the former and doesn't look good if there is
726 // nothing for it to contrast with. Finally, notice that the extended
727 // message we use here might be automatically extracted from the main
728 // message in our ctor, see comment there.
729 if ( !extendedMessage.empty() )
730 {
017dc06b
VZ
731 tdc.pszMainInstruction = message.t_str();
732 tdc.pszContent = extendedMessage.t_str();
aebe0d3b
VZ
733 }
734 else
735 {
017dc06b 736 tdc.pszContent = message.t_str();
aebe0d3b 737 }
ede7b017
VZ
738
739 // set an icon to be used, if possible
740 switch ( iconId )
741 {
742 case wxICON_ERROR:
743 tdc.pszMainIcon = TD_ERROR_ICON;
744 break;
745
746 case wxICON_WARNING:
747 tdc.pszMainIcon = TD_WARNING_ICON;
748 break;
749
750 case wxICON_INFORMATION:
751 tdc.pszMainIcon = TD_INFORMATION_ICON;
752 break;
67315c8b
VZ
753
754 case wxICON_AUTH_NEEDED:
755 tdc.pszMainIcon = TD_SHIELD_ICON;
756 break;
ede7b017
VZ
757 }
758
759 // custom label button array that can hold all buttons in use
760 tdc.pButtons = buttons.get();
761
762 if ( style & wxYES_NO )
763 {
764 AddTaskDialogButton(tdc, IDYES, TDCBF_YES_BUTTON, btnYesLabel);
765 AddTaskDialogButton(tdc, IDNO, TDCBF_NO_BUTTON, btnNoLabel);
766
767 if (style & wxCANCEL)
768 AddTaskDialogButton(tdc, IDCANCEL,
769 TDCBF_CANCEL_BUTTON, btnCancelLabel);
770
771 if ( style & wxNO_DEFAULT )
772 tdc.nDefaultButton = IDNO;
773 else if ( style & wxCANCEL_DEFAULT )
774 tdc.nDefaultButton = IDCANCEL;
775 }
776 else // without Yes/No we're going to have an OK button
777 {
ede7b017
VZ
778 if ( style & wxCANCEL )
779 {
18c8dd2b 780 AddTaskDialogButton(tdc, IDOK, TDCBF_OK_BUTTON, btnOKLabel);
ede7b017
VZ
781 AddTaskDialogButton(tdc, IDCANCEL,
782 TDCBF_CANCEL_BUTTON, btnCancelLabel);
783
784 if ( style & wxCANCEL_DEFAULT )
785 tdc.nDefaultButton = IDCANCEL;
786 }
18c8dd2b
VZ
787 else // Only "OK"
788 {
789 // We actually create a "Cancel" button instead because we want to
790 // allow closing the dialog box with Escape (and also Alt-F4 or
791 // clicking the close button in the title bar) which wouldn't work
792 // without a Cancel button.
793 if ( !useCustomLabels )
794 {
795 useCustomLabels = true;
796 btnOKLabel = _("OK");
797 }
798
799 AddTaskDialogButton(tdc, IDCANCEL, TDCBF_CANCEL_BUTTON, btnOKLabel);
800 }
ede7b017 801 }
7112cdd1
VZ
802
803 if ( style & wxHELP )
804 {
805 // There is no support for "Help" button in the task dialog, it can
806 // only show "Retry" or "Close" ones.
807 useCustomLabels = true;
808
809 AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel);
810 }
ede7b017
VZ
811}
812
813void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc,
814 int btnCustomId,
815 int btnCommonId,
816 const wxString& customLabel)
817{
818 if ( useCustomLabels )
819 {
820 // use custom buttons to implement custom labels
821 TASKDIALOG_BUTTON &tdBtn = buttons[tdc.cButtons];
822
823 tdBtn.nButtonID = btnCustomId;
017dc06b 824 tdBtn.pszButtonText = customLabel.t_str();
ede7b017 825 tdc.cButtons++;
7112cdd1
VZ
826
827 // We should never have more than 4 buttons currently as this is the
828 // maximal number of buttons supported by the message dialog.
829 wxASSERT_MSG( tdc.cButtons <= MAX_BUTTONS, wxT("Too many buttons") );
ede7b017
VZ
830 }
831 else
832 {
833 tdc.dwCommonButtons |= btnCommonId;
834 }
835}
836
837// Task dialog can be used from different threads (and wxProgressDialog always
838// uses it from another thread in fact) so protect access to the static
839// variable below with a critical section.
840wxCRIT_SECT_DECLARE(gs_csTaskDialogIndirect);
841
842TaskDialogIndirect_t wxMSWMessageDialog::GetTaskDialogIndirectFunc()
843{
5b1985a9
VZ
844 // Initialize the function pointer to an invalid value different from NULL
845 // to avoid reloading comctl32.dll and trying to resolve it every time
846 // we're called if task dialog is not available (notice that this may
847 // happen even under Vista+ if we don't use comctl32.dll v6).
848 static const TaskDialogIndirect_t
849 INVALID_TASKDIALOG_FUNC = reinterpret_cast<TaskDialogIndirect_t>(-1);
850 static TaskDialogIndirect_t s_TaskDialogIndirect = INVALID_TASKDIALOG_FUNC;
ede7b017
VZ
851
852 wxCRIT_SECT_LOCKER(lock, gs_csTaskDialogIndirect);
853
5b1985a9 854 if ( s_TaskDialogIndirect == INVALID_TASKDIALOG_FUNC )
ede7b017
VZ
855 {
856 wxLoadedDLL dllComCtl32("comctl32.dll");
857 wxDL_INIT_FUNC(s_, TaskDialogIndirect, dllComCtl32);
ede7b017
VZ
858 }
859
860 return s_TaskDialogIndirect;
861}
862
863#endif // wxHAS_MSW_TASKDIALOG
864
865bool wxMSWMessageDialog::HasNativeTaskDialog()
866{
867#ifdef wxHAS_MSW_TASKDIALOG
5b1985a9
VZ
868 if ( wxGetWinVersion() >= wxWinVersion_6 )
869 {
870 if ( wxMSWMessageDialog::GetTaskDialogIndirectFunc() )
871 return true;
872 }
873#endif // wxHAS_MSW_TASKDIALOG
874
ede7b017 875 return false;
ede7b017
VZ
876}
877
878int wxMSWMessageDialog::MSWTranslateReturnCode(int msAns)
879{
b8505921 880 int ans;
0d7ea902
VZ
881 switch (msAns)
882 {
b8505921 883 default:
ede7b017 884 wxFAIL_MSG(wxT("unexpected return code"));
b8505921
VZ
885 // fall through
886
0d7ea902
VZ
887 case IDCANCEL:
888 ans = wxID_CANCEL;
889 break;
890 case IDOK:
891 ans = wxID_OK;
892 break;
893 case IDYES:
894 ans = wxID_YES;
895 break;
896 case IDNO:
897 ans = wxID_NO;
898 break;
7112cdd1
VZ
899 case IDHELP:
900 ans = wxID_HELP;
901 break;
0d7ea902 902 }
ede7b017 903
0d7ea902 904 return ans;
2bda0e17 905}
a8ff046b
VZ
906
907#endif // wxUSE_MSGDLG