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