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