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