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