Merge in from trunk r64802 - r68625
[wxWidgets.git] / src / msw / msgdlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/msgdlg.cpp
3 // Purpose: wxMessageDialog
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_MSGDLG
20
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
29 #ifndef WX_PRECOMP
30 #include "wx/msgdlg.h"
31 #include "wx/app.h"
32 #include "wx/intl.h"
33 #include "wx/utils.h"
34 #include "wx/msw/private.h"
35 #if wxUSE_MSGBOX_HOOK
36 #include "wx/hashmap.h"
37 #endif
38 #endif
39
40 #include "wx/ptr_scpd.h"
41 #include "wx/dynlib.h"
42 #include "wx/msw/private/button.h"
43 #include "wx/msw/private/metrics.h"
44 #include "wx/msw/private/msgdlg.h"
45
46 #if wxUSE_MSGBOX_HOOK
47 #include "wx/fontutil.h"
48 #include "wx/textbuf.h"
49 #include "wx/display.h"
50 #endif
51
52 // For MB_TASKMODAL
53 #ifdef __WXWINCE__
54 #include "wx/msw/wince/missing.h"
55 #endif
56
57 using namespace wxMSWMessageDialog;
58
59 IMPLEMENT_CLASS(wxMessageDialog, wxDialog)
60
61 #if wxUSE_MSGBOX_HOOK
62
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
67 WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog *,
68 wxIntegerHash, wxIntegerEqual,
69 wxMessageDialogMap);
70
71 // the order in this array is the one in which buttons appear in the
72 // message box
73 const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] =
74 {
75 { IDYES, &wxMessageDialog::GetYesLabel },
76 { IDNO, &wxMessageDialog::GetNoLabel },
77 { IDOK, &wxMessageDialog::GetOKLabel },
78 { IDCANCEL, &wxMessageDialog::GetCancelLabel },
79 };
80
81 namespace
82 {
83
84 wxMessageDialogMap& HookMap()
85 {
86 static wxMessageDialogMap s_Map;
87
88 return s_Map;
89 }
90
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
102 void 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
112 inline 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
122 void 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
132 void 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
142 } // anonymous namespace
143
144 /* static */
145 WXLRESULT wxCALLBACK
146 wxMessageDialog::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
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
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
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
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
182 // there seems to be no reason to leave it set
183 wnd->SetHWND(NULL);
184 }
185
186 return rc;
187 }
188
189 void wxMessageDialog::ReplaceStaticWithEdit()
190 {
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();
196
197 if ( rectDisplay.Contains(GetRect()) )
198 {
199 // nothing to do
200 return;
201 }
202
203
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
207 HWND hwndStatic = ::FindWindowEx(GetHwnd(), NULL, wxT("STATIC"), NULL);
208 if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON )
209 hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), NULL);
210
211 if ( !hwndStatic )
212 {
213 wxLogDebug("Failed to find the static text control in message box.");
214 return;
215 }
216
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));
252 for ( wxString::reverse_iterator i = text.rbegin(); i != text.rend(); ++i )
253 {
254 if ( *i != '\n' )
255 {
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());
260 break;
261 }
262 }
263
264 // do create the new control
265 HWND hwndEdit = ::CreateWindow
266 (
267 wxT("EDIT"),
268 wxTextBuffer::Translate(text).wx_str(),
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,
275 wxGetInstance(),
276 NULL
277 );
278
279 if ( !hwndEdit )
280 {
281 wxLogDebug("Creation of replacement edit control failed in message box");
282 return;
283 }
284
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);
291
292
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++ )
305 {
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);
316 }
317 }
318
319 void 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
324
325 // TODO-RTL: check whether this works correctly in RTL
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)
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++ )
340 {
341 const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
342 if ( !hwndBtn )
343 continue; // it's ok, not all buttons are always present
344
345 numButtons++;
346
347 const wxString label = (this->*ms_buttons[n].getter)();
348 const wxSize sizeLabel = wxWindowBase::GetTextExtent(label);
349
350 // check if the button is big enough for this label
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
361 {
362 wxASSERT_MSG( wBtnOld == rc.right - rc.left,
363 "all buttons are supposed to be of same width" );
364 }
365
366 const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x;
367 if ( widthNeeded > wBtnNew )
368 wBtnNew = widthNeeded;
369
370 ::SetWindowText(hwndBtn, label.wx_str());
371 }
372
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
409
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;
415
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 }
427 }
428
429 #endif // wxUSE_MSGBOX_HOOK
430
431 /* static */
432 wxFont wxMessageDialog::GetMessageFont()
433 {
434 const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics();
435 return wxNativeFontInfo(ncm.lfMessageFont);
436 }
437
438 int wxMessageDialog::ShowMessageBox()
439 {
440 if ( !wxTheApp->GetTopWindow() )
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 }
449
450 // use the top level window as parent if none specified
451 m_parent = GetParentForModalDialog();
452 HWND hWnd = m_parent ? GetHwndOf(m_parent) : NULL;
453
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
457 // translate the button labels to avoid mismatch between the language of
458 // the message box text and its buttons
459 wxLocale * const loc = wxGetLocale();
460 if ( loc && loc->GetLanguage() != wxLocale::GetSystemLanguage() )
461 {
462 if ( m_dialogStyle & wxYES_NO )
463 {
464 // use the strings with mnemonics here as the native message box
465 // does
466 SetYesNoLabels(_("&Yes"), _("&No"));
467 }
468
469 // we may or not have the Ok/Cancel buttons but either we do have them
470 // or we already made the labels custom because we called
471 // SetYesNoLabels() above so doing this does no harm -- and is
472 // necessary in wxYES_NO | wxCANCEL case
473 //
474 // note that we don't use mnemonics here for consistency with the
475 // native message box (which probably doesn't use them because
476 // Enter/Esc keys can be already used to dismiss the message box
477 // using keyboard)
478 SetOKCancelLabels(_("OK"), _("Cancel"));
479 }
480 #endif // wxUSE_INTL
481
482 // translate wx style in MSW
483 unsigned int msStyle;
484 const long wxStyle = GetMessageDialogStyle();
485 if ( wxStyle & wxYES_NO )
486 {
487 #if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
488 if (wxStyle & wxCANCEL)
489 msStyle = MB_YESNOCANCEL;
490 else
491 #endif // !(__SMARTPHONE__ && __WXWINCE__)
492 msStyle = MB_YESNO;
493
494 if ( wxStyle & wxNO_DEFAULT )
495 msStyle |= MB_DEFBUTTON2;
496 else if ( wxStyle & wxCANCEL_DEFAULT )
497 msStyle |= MB_DEFBUTTON3;
498 }
499 else // without Yes/No we're going to have an OK button
500 {
501 if ( wxStyle & wxCANCEL )
502 {
503 msStyle = MB_OKCANCEL;
504
505 if ( wxStyle & wxCANCEL_DEFAULT )
506 msStyle |= MB_DEFBUTTON2;
507 }
508 else // just "OK"
509 {
510 msStyle = MB_OK;
511 }
512 }
513
514 if ( wxStyle & wxHELP )
515 {
516 msStyle |= MB_HELP;
517 }
518
519 // set the icon style
520 switch ( GetEffectiveIcon() )
521 {
522 case wxICON_ERROR:
523 msStyle |= MB_ICONHAND;
524 break;
525
526 case wxICON_WARNING:
527 msStyle |= MB_ICONEXCLAMATION;
528 break;
529
530 case wxICON_QUESTION:
531 msStyle |= MB_ICONQUESTION;
532 break;
533
534 case wxICON_INFORMATION:
535 msStyle |= MB_ICONINFORMATION;
536 break;
537 }
538
539 if ( wxStyle & wxSTAY_ON_TOP )
540 msStyle |= MB_TOPMOST;
541
542 #ifndef __WXWINCE__
543 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
544 msStyle |= MB_RTLREADING | MB_RIGHT;
545 #endif
546
547 if (hWnd)
548 msStyle |= MB_APPLMODAL;
549 else
550 msStyle |= MB_TASKMODAL;
551
552 // per MSDN documentation for MessageBox() we can prefix the message with 2
553 // right-to-left mark characters to tell the function to use RTL layout
554 // (unfortunately this only works in Unicode builds)
555 wxString message = GetFullMessage();
556 #if wxUSE_UNICODE
557 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
558 {
559 // NB: not all compilers support \u escapes
560 static const wchar_t wchRLM = 0x200f;
561 message.Prepend(wxString(wchRLM, 2));
562 }
563 #endif // wxUSE_UNICODE
564
565 #if wxUSE_MSGBOX_HOOK
566 // install the hook in any case as we don't know in advance if the message
567 // box is not going to be too big (requiring the replacement of the static
568 // control with an edit one)
569 const DWORD tid = ::GetCurrentThreadId();
570 m_hook = ::SetWindowsHookEx(WH_CBT,
571 &wxMessageDialog::HookFunction, NULL, tid);
572 HookMap()[tid] = this;
573 #endif // wxUSE_MSGBOX_HOOK
574
575 // do show the dialog
576 int msAns = MessageBox(hWnd, message.wx_str(), m_caption.wx_str(), msStyle);
577
578 return MSWTranslateReturnCode(msAns);
579 }
580
581 int wxMessageDialog::ShowModal()
582 {
583 #ifdef wxHAS_MSW_TASKDIALOG
584 if ( HasNativeTaskDialog() )
585 {
586 TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
587 wxCHECK_MSG( taskDialogIndirect, wxID_CANCEL, wxS("no task dialog?") );
588
589 WinStruct<TASKDIALOGCONFIG> tdc;
590 wxMSWTaskDialogConfig wxTdc( *this );
591 wxTdc.MSWCommonTaskDialogInit( tdc );
592
593 int msAns;
594 HRESULT hr = taskDialogIndirect( &tdc, &msAns, NULL, NULL );
595 if ( FAILED(hr) )
596 {
597 wxLogApiError( "TaskDialogIndirect", hr );
598 return wxID_CANCEL;
599 }
600
601 // In case only an "OK" button was specified we actually created a
602 // "Cancel" button (see comment in MSWCommonTaskDialogInit). This
603 // results in msAns being IDCANCEL while we want IDOK (just like
604 // how the native MessageBox function does with only an "OK" button).
605 if ( (msAns == IDCANCEL)
606 && !(GetMessageDialogStyle() & (wxYES_NO|wxCANCEL)) )
607 {
608 msAns = IDOK;
609 }
610
611 return MSWTranslateReturnCode( msAns );
612 }
613 #endif // wxHAS_MSW_TASKDIALOG
614
615 return ShowMessageBox();
616 }
617
618 void wxMessageDialog::DoCentre(int dir)
619 {
620 #ifdef wxHAS_MSW_TASKDIALOG
621 // Task dialog is always centered on its parent window and trying to center
622 // it manually doesn't work because its HWND is not created yet so don't
623 // even try as this would only result in (debug) error messages.
624 if ( HasNativeTaskDialog() )
625 return;
626 #endif // wxHAS_MSW_TASKDIALOG
627
628 wxMessageDialogBase::DoCentre(dir);
629 }
630
631 // ----------------------------------------------------------------------------
632 // Helpers of the wxMSWMessageDialog namespace
633 // ----------------------------------------------------------------------------
634
635 #ifdef wxHAS_MSW_TASKDIALOG
636
637 wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg)
638 : buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS])
639 {
640 parent = dlg.GetParentForModalDialog();
641 caption = dlg.GetCaption();
642 message = dlg.GetMessage();
643 extendedMessage = dlg.GetExtendedMessage();
644
645 // Before wxMessageDialog added support for extended message it was common
646 // practice to have long multiline texts in the message box with the first
647 // line playing the role of the main message and the rest of the extended
648 // one. Try to detect such usage automatically here by synthesizing the
649 // extended message on our own if it wasn't given.
650 if ( extendedMessage.empty() )
651 {
652 // Check if there is a blank separating line after the first line (this
653 // is not the same as searching for "\n\n" as we want the automatically
654 // recognized main message be single line to avoid embarrassing false
655 // positives).
656 const size_t posNL = message.find('\n');
657 if ( posNL != wxString::npos &&
658 posNL < message.length() - 1 &&
659 message[posNL + 1 ] == '\n' )
660 {
661 extendedMessage.assign(message, posNL + 2, wxString::npos);
662 message.erase(posNL);
663 }
664 }
665
666 iconId = dlg.GetEffectiveIcon();
667 style = dlg.GetMessageDialogStyle();
668 useCustomLabels = dlg.HasCustomLabels();
669 btnYesLabel = dlg.GetYesLabel();
670 btnNoLabel = dlg.GetNoLabel();
671 btnOKLabel = dlg.GetOKLabel();
672 btnCancelLabel = dlg.GetCancelLabel();
673 btnHelpLabel = dlg.GetHelpLabel();
674 }
675
676 void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc)
677 {
678 tdc.dwFlags = TDF_EXPAND_FOOTER_AREA | TDF_POSITION_RELATIVE_TO_WINDOW;
679 tdc.hInstance = wxGetInstance();
680 tdc.pszWindowTitle = caption.wx_str();
681
682 // use the top level window as parent if none specified
683 tdc.hwndParent = parent ? GetHwndOf(parent) : NULL;
684
685 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
686 tdc.dwFlags |= TDF_RTL_LAYOUT;
687
688 // If we have both the main and extended messages, just use them as
689 // intended. However if only one message is given we normally use it as the
690 // content and not as the main instruction because the latter is supposed
691 // to stand out compared to the former and doesn't look good if there is
692 // nothing for it to contrast with. Finally, notice that the extended
693 // message we use here might be automatically extracted from the main
694 // message in our ctor, see comment there.
695 if ( !extendedMessage.empty() )
696 {
697 tdc.pszMainInstruction = message.wx_str();
698 tdc.pszContent = extendedMessage.wx_str();
699 }
700 else
701 {
702 tdc.pszContent = message.wx_str();
703 }
704
705 // set an icon to be used, if possible
706 switch ( iconId )
707 {
708 case wxICON_ERROR:
709 tdc.pszMainIcon = TD_ERROR_ICON;
710 break;
711
712 case wxICON_WARNING:
713 tdc.pszMainIcon = TD_WARNING_ICON;
714 break;
715
716 case wxICON_INFORMATION:
717 tdc.pszMainIcon = TD_INFORMATION_ICON;
718 break;
719 }
720
721 // custom label button array that can hold all buttons in use
722 tdc.pButtons = buttons.get();
723
724 if ( style & wxYES_NO )
725 {
726 AddTaskDialogButton(tdc, IDYES, TDCBF_YES_BUTTON, btnYesLabel);
727 AddTaskDialogButton(tdc, IDNO, TDCBF_NO_BUTTON, btnNoLabel);
728
729 if (style & wxCANCEL)
730 AddTaskDialogButton(tdc, IDCANCEL,
731 TDCBF_CANCEL_BUTTON, btnCancelLabel);
732
733 if ( style & wxNO_DEFAULT )
734 tdc.nDefaultButton = IDNO;
735 else if ( style & wxCANCEL_DEFAULT )
736 tdc.nDefaultButton = IDCANCEL;
737 }
738 else // without Yes/No we're going to have an OK button
739 {
740 if ( style & wxCANCEL )
741 {
742 AddTaskDialogButton(tdc, IDOK, TDCBF_OK_BUTTON, btnOKLabel);
743 AddTaskDialogButton(tdc, IDCANCEL,
744 TDCBF_CANCEL_BUTTON, btnCancelLabel);
745
746 if ( style & wxCANCEL_DEFAULT )
747 tdc.nDefaultButton = IDCANCEL;
748 }
749 else // Only "OK"
750 {
751 // We actually create a "Cancel" button instead because we want to
752 // allow closing the dialog box with Escape (and also Alt-F4 or
753 // clicking the close button in the title bar) which wouldn't work
754 // without a Cancel button.
755 if ( !useCustomLabels )
756 {
757 useCustomLabels = true;
758 btnOKLabel = _("OK");
759 }
760
761 AddTaskDialogButton(tdc, IDCANCEL, TDCBF_CANCEL_BUTTON, btnOKLabel);
762 }
763 }
764
765 if ( style & wxHELP )
766 {
767 // There is no support for "Help" button in the task dialog, it can
768 // only show "Retry" or "Close" ones.
769 useCustomLabels = true;
770
771 AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel);
772 }
773 }
774
775 void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc,
776 int btnCustomId,
777 int btnCommonId,
778 const wxString& customLabel)
779 {
780 if ( useCustomLabels )
781 {
782 // use custom buttons to implement custom labels
783 TASKDIALOG_BUTTON &tdBtn = buttons[tdc.cButtons];
784
785 tdBtn.nButtonID = btnCustomId;
786 tdBtn.pszButtonText = customLabel.wx_str();
787 tdc.cButtons++;
788
789 // We should never have more than 4 buttons currently as this is the
790 // maximal number of buttons supported by the message dialog.
791 wxASSERT_MSG( tdc.cButtons <= MAX_BUTTONS, wxT("Too many buttons") );
792 }
793 else
794 {
795 tdc.dwCommonButtons |= btnCommonId;
796 }
797 }
798
799 // Task dialog can be used from different threads (and wxProgressDialog always
800 // uses it from another thread in fact) so protect access to the static
801 // variable below with a critical section.
802 wxCRIT_SECT_DECLARE(gs_csTaskDialogIndirect);
803
804 TaskDialogIndirect_t wxMSWMessageDialog::GetTaskDialogIndirectFunc()
805 {
806 // Initialize the function pointer to an invalid value different from NULL
807 // to avoid reloading comctl32.dll and trying to resolve it every time
808 // we're called if task dialog is not available (notice that this may
809 // happen even under Vista+ if we don't use comctl32.dll v6).
810 static const TaskDialogIndirect_t
811 INVALID_TASKDIALOG_FUNC = reinterpret_cast<TaskDialogIndirect_t>(-1);
812 static TaskDialogIndirect_t s_TaskDialogIndirect = INVALID_TASKDIALOG_FUNC;
813
814 wxCRIT_SECT_LOCKER(lock, gs_csTaskDialogIndirect);
815
816 if ( s_TaskDialogIndirect == INVALID_TASKDIALOG_FUNC )
817 {
818 wxLoadedDLL dllComCtl32("comctl32.dll");
819 wxDL_INIT_FUNC(s_, TaskDialogIndirect, dllComCtl32);
820 }
821
822 return s_TaskDialogIndirect;
823 }
824
825 #endif // wxHAS_MSW_TASKDIALOG
826
827 bool wxMSWMessageDialog::HasNativeTaskDialog()
828 {
829 #ifdef wxHAS_MSW_TASKDIALOG
830 if ( wxGetWinVersion() >= wxWinVersion_6 )
831 {
832 if ( wxMSWMessageDialog::GetTaskDialogIndirectFunc() )
833 return true;
834 }
835 #endif // wxHAS_MSW_TASKDIALOG
836
837 return false;
838 }
839
840 int wxMSWMessageDialog::MSWTranslateReturnCode(int msAns)
841 {
842 int ans;
843 switch (msAns)
844 {
845 default:
846 wxFAIL_MSG(wxT("unexpected return code"));
847 // fall through
848
849 case IDCANCEL:
850 ans = wxID_CANCEL;
851 break;
852 case IDOK:
853 ans = wxID_OK;
854 break;
855 case IDYES:
856 ans = wxID_YES;
857 break;
858 case IDNO:
859 ans = wxID_NO;
860 break;
861 case IDHELP:
862 ans = wxID_HELP;
863 break;
864 }
865
866 return ans;
867 }
868
869 #endif // wxUSE_MSGDLG