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