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