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