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