]>
Commit | Line | Data |
---|---|---|
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 | |
6c9a19aa | 7 | // Copyright: (c) Julian Smart |
65571936 | 8 | // Licence: wxWindows licence |
2bda0e17 KB |
9 | ///////////////////////////////////////////////////////////////////////////// |
10 | ||
2bda0e17 KB |
11 | // For compilers that support precompilation, includes "wx.h". |
12 | #include "wx/wxprec.h" | |
13 | ||
14 | #ifdef __BORLANDC__ | |
7520f3da | 15 | #pragma hdrstop |
2bda0e17 KB |
16 | #endif |
17 | ||
a8ff046b VZ |
18 | #if wxUSE_MSGDLG |
19 | ||
704c499e VZ |
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 | ||
2bda0e17 | 28 | #ifndef WX_PRECOMP |
d2bc8725 | 29 | #include "wx/msgdlg.h" |
35bc781e | 30 | #include "wx/app.h" |
42c097b8 | 31 | #include "wx/intl.h" |
0d7ea902 | 32 | #include "wx/utils.h" |
d2bc8725 | 33 | #include "wx/msw/private.h" |
704c499e VZ |
34 | #if wxUSE_MSGBOX_HOOK |
35 | #include "wx/hashmap.h" | |
36 | #endif | |
2bda0e17 KB |
37 | #endif |
38 | ||
d2bc8725 | 39 | #include "wx/ptr_scpd.h" |
ede7b017 | 40 | #include "wx/dynlib.h" |
23e00c55 VZ |
41 | #include "wx/msw/private/button.h" |
42 | #include "wx/msw/private/metrics.h" | |
ede7b017 | 43 | #include "wx/msw/private/msgdlg.h" |
691745ab | 44 | #include "wx/modalhook.h" |
f592a939 | 45 | #include "wx/fontutil.h" |
23e00c55 VZ |
46 | |
47 | #if wxUSE_MSGBOX_HOOK | |
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 |
63 | using namespace wxMSWMessageDialog; |
64 | ||
2bda0e17 | 65 | IMPLEMENT_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 | |
73 | WX_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 | |
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 | ||
1d89da8a VZ |
87 | namespace |
88 | { | |
89 | ||
90 | wxMessageDialogMap& 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 | |
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 | |
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 | |
128 | void 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 | |
138 | void 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 */ | |
151 | WXLRESULT wxCALLBACK | |
152 | wxMessageDialog::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 | 195 | void 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"), |
017dc06b | 274 | wxTextBuffer::Translate(text).t_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 |
325 | void 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 | ||
017dc06b | 376 | ::SetWindowText(hwndBtn, label.t_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 */ |
438 | wxFont wxMessageDialog::GetMessageFont() | |
439 | { | |
440 | const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics(); | |
441 | return wxNativeFontInfo(ncm.lfMessageFont); | |
442 | } | |
704c499e | 443 | |
ede7b017 | 444 | int 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 |
017dc06b | 587 | int msAns = MessageBox(hWnd, message.t_str(), m_caption.t_str(), msStyle); |
ede7b017 | 588 | |
d9b22d8f VS |
589 | int ret = MSWTranslateReturnCode(msAns); |
590 | SetReturnCode(ret); | |
591 | return ret; | |
ede7b017 VZ |
592 | } |
593 | ||
5b1985a9 | 594 | int wxMessageDialog::ShowModal() |
ede7b017 | 595 | { |
691745ab | 596 | WX_HOOK_MODAL_DIALOG(); |
643e9cf9 | 597 | |
ede7b017 | 598 | #ifdef wxHAS_MSW_TASKDIALOG |
5b1985a9 | 599 | if ( HasNativeTaskDialog() ) |
ede7b017 | 600 | { |
5b1985a9 VZ |
601 | TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc(); |
602 | wxCHECK_MSG( taskDialogIndirect, wxID_CANCEL, wxS("no task dialog?") ); | |
ede7b017 | 603 | |
5b1985a9 VZ |
604 | WinStruct<TASKDIALOGCONFIG> tdc; |
605 | wxMSWTaskDialogConfig wxTdc( *this ); | |
606 | wxTdc.MSWCommonTaskDialogInit( tdc ); | |
ede7b017 | 607 | |
5b1985a9 VZ |
608 | int msAns; |
609 | HRESULT hr = taskDialogIndirect( &tdc, &msAns, NULL, NULL ); | |
610 | if ( FAILED(hr) ) | |
611 | { | |
612 | wxLogApiError( "TaskDialogIndirect", hr ); | |
613 | return wxID_CANCEL; | |
614 | } | |
ede7b017 | 615 | |
dc8c61be DS |
616 | // In case only an "OK" button was specified we actually created a |
617 | // "Cancel" button (see comment in MSWCommonTaskDialogInit). This | |
618 | // results in msAns being IDCANCEL while we want IDOK (just like | |
619 | // how the native MessageBox function does with only an "OK" button). | |
620 | if ( (msAns == IDCANCEL) | |
621 | && !(GetMessageDialogStyle() & (wxYES_NO|wxCANCEL)) ) | |
622 | { | |
623 | msAns = IDOK; | |
624 | } | |
625 | ||
d9b22d8f VS |
626 | int ret = MSWTranslateReturnCode(msAns); |
627 | SetReturnCode(ret); | |
628 | return ret; | |
5b1985a9 VZ |
629 | } |
630 | #endif // wxHAS_MSW_TASKDIALOG | |
ede7b017 VZ |
631 | |
632 | return ShowMessageBox(); | |
633 | } | |
634 | ||
67315c8b VZ |
635 | long wxMessageDialog::GetEffectiveIcon() const |
636 | { | |
637 | // only use the auth needed icon if available, otherwise fallback to the default logic | |
638 | if ( (m_dialogStyle & wxICON_AUTH_NEEDED) && | |
639 | wxMSWMessageDialog::HasNativeTaskDialog() ) | |
640 | { | |
641 | return wxICON_AUTH_NEEDED; | |
642 | } | |
643 | ||
644 | return wxMessageDialogBase::GetEffectiveIcon(); | |
645 | } | |
646 | ||
dd53b347 VZ |
647 | void wxMessageDialog::DoCentre(int dir) |
648 | { | |
649 | #ifdef wxHAS_MSW_TASKDIALOG | |
650 | // Task dialog is always centered on its parent window and trying to center | |
651 | // it manually doesn't work because its HWND is not created yet so don't | |
652 | // even try as this would only result in (debug) error messages. | |
653 | if ( HasNativeTaskDialog() ) | |
654 | return; | |
655 | #endif // wxHAS_MSW_TASKDIALOG | |
656 | ||
00bc0d17 | 657 | wxMessageDialogBase::DoCentre(dir); |
dd53b347 VZ |
658 | } |
659 | ||
ede7b017 VZ |
660 | // ---------------------------------------------------------------------------- |
661 | // Helpers of the wxMSWMessageDialog namespace | |
662 | // ---------------------------------------------------------------------------- | |
663 | ||
664 | #ifdef wxHAS_MSW_TASKDIALOG | |
665 | ||
666 | wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg) | |
7112cdd1 | 667 | : buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS]) |
ede7b017 VZ |
668 | { |
669 | parent = dlg.GetParentForModalDialog(); | |
670 | caption = dlg.GetCaption(); | |
671 | message = dlg.GetMessage(); | |
672 | extendedMessage = dlg.GetExtendedMessage(); | |
aebe0d3b VZ |
673 | |
674 | // Before wxMessageDialog added support for extended message it was common | |
675 | // practice to have long multiline texts in the message box with the first | |
676 | // line playing the role of the main message and the rest of the extended | |
677 | // one. Try to detect such usage automatically here by synthesizing the | |
678 | // extended message on our own if it wasn't given. | |
679 | if ( extendedMessage.empty() ) | |
680 | { | |
681 | // Check if there is a blank separating line after the first line (this | |
682 | // is not the same as searching for "\n\n" as we want the automatically | |
683 | // recognized main message be single line to avoid embarrassing false | |
684 | // positives). | |
685 | const size_t posNL = message.find('\n'); | |
686 | if ( posNL != wxString::npos && | |
687 | posNL < message.length() - 1 && | |
688 | message[posNL + 1 ] == '\n' ) | |
689 | { | |
690 | extendedMessage.assign(message, posNL + 2, wxString::npos); | |
691 | message.erase(posNL); | |
692 | } | |
693 | } | |
694 | ||
ede7b017 VZ |
695 | iconId = dlg.GetEffectiveIcon(); |
696 | style = dlg.GetMessageDialogStyle(); | |
697 | useCustomLabels = dlg.HasCustomLabels(); | |
698 | btnYesLabel = dlg.GetYesLabel(); | |
699 | btnNoLabel = dlg.GetNoLabel(); | |
700 | btnOKLabel = dlg.GetOKLabel(); | |
701 | btnCancelLabel = dlg.GetCancelLabel(); | |
7112cdd1 | 702 | btnHelpLabel = dlg.GetHelpLabel(); |
ede7b017 VZ |
703 | } |
704 | ||
705 | void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc) | |
706 | { | |
5f519da2 VZ |
707 | // Use TDF_SIZE_TO_CONTENT to try to prevent Windows from truncating or |
708 | // ellipsizing the message text. This doesn't always work as Windows will | |
709 | // still do it if the message contains too long "words" (i.e. runs of the | |
710 | // text without spaces) but at least it ensures that the message text is | |
711 | // fully shown for reasonably-sized words whereas without it using almost | |
712 | // any file system path in a message box would result in truncation. | |
713 | tdc.dwFlags = TDF_EXPAND_FOOTER_AREA | | |
714 | TDF_POSITION_RELATIVE_TO_WINDOW | | |
715 | TDF_SIZE_TO_CONTENT; | |
ede7b017 | 716 | tdc.hInstance = wxGetInstance(); |
017dc06b | 717 | tdc.pszWindowTitle = caption.t_str(); |
ede7b017 VZ |
718 | |
719 | // use the top level window as parent if none specified | |
720 | tdc.hwndParent = parent ? GetHwndOf(parent) : NULL; | |
721 | ||
722 | if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) | |
723 | tdc.dwFlags |= TDF_RTL_LAYOUT; | |
aebe0d3b VZ |
724 | |
725 | // If we have both the main and extended messages, just use them as | |
726 | // intended. However if only one message is given we normally use it as the | |
727 | // content and not as the main instruction because the latter is supposed | |
728 | // to stand out compared to the former and doesn't look good if there is | |
729 | // nothing for it to contrast with. Finally, notice that the extended | |
730 | // message we use here might be automatically extracted from the main | |
731 | // message in our ctor, see comment there. | |
732 | if ( !extendedMessage.empty() ) | |
733 | { | |
017dc06b VZ |
734 | tdc.pszMainInstruction = message.t_str(); |
735 | tdc.pszContent = extendedMessage.t_str(); | |
aebe0d3b VZ |
736 | } |
737 | else | |
738 | { | |
017dc06b | 739 | tdc.pszContent = message.t_str(); |
aebe0d3b | 740 | } |
ede7b017 VZ |
741 | |
742 | // set an icon to be used, if possible | |
743 | switch ( iconId ) | |
744 | { | |
745 | case wxICON_ERROR: | |
746 | tdc.pszMainIcon = TD_ERROR_ICON; | |
747 | break; | |
748 | ||
749 | case wxICON_WARNING: | |
750 | tdc.pszMainIcon = TD_WARNING_ICON; | |
751 | break; | |
752 | ||
753 | case wxICON_INFORMATION: | |
754 | tdc.pszMainIcon = TD_INFORMATION_ICON; | |
755 | break; | |
67315c8b VZ |
756 | |
757 | case wxICON_AUTH_NEEDED: | |
758 | tdc.pszMainIcon = TD_SHIELD_ICON; | |
759 | break; | |
ede7b017 VZ |
760 | } |
761 | ||
762 | // custom label button array that can hold all buttons in use | |
763 | tdc.pButtons = buttons.get(); | |
764 | ||
765 | if ( style & wxYES_NO ) | |
766 | { | |
767 | AddTaskDialogButton(tdc, IDYES, TDCBF_YES_BUTTON, btnYesLabel); | |
768 | AddTaskDialogButton(tdc, IDNO, TDCBF_NO_BUTTON, btnNoLabel); | |
769 | ||
770 | if (style & wxCANCEL) | |
771 | AddTaskDialogButton(tdc, IDCANCEL, | |
772 | TDCBF_CANCEL_BUTTON, btnCancelLabel); | |
773 | ||
774 | if ( style & wxNO_DEFAULT ) | |
775 | tdc.nDefaultButton = IDNO; | |
776 | else if ( style & wxCANCEL_DEFAULT ) | |
777 | tdc.nDefaultButton = IDCANCEL; | |
778 | } | |
779 | else // without Yes/No we're going to have an OK button | |
780 | { | |
ede7b017 VZ |
781 | if ( style & wxCANCEL ) |
782 | { | |
18c8dd2b | 783 | AddTaskDialogButton(tdc, IDOK, TDCBF_OK_BUTTON, btnOKLabel); |
ede7b017 VZ |
784 | AddTaskDialogButton(tdc, IDCANCEL, |
785 | TDCBF_CANCEL_BUTTON, btnCancelLabel); | |
786 | ||
787 | if ( style & wxCANCEL_DEFAULT ) | |
788 | tdc.nDefaultButton = IDCANCEL; | |
789 | } | |
18c8dd2b VZ |
790 | else // Only "OK" |
791 | { | |
792 | // We actually create a "Cancel" button instead because we want to | |
793 | // allow closing the dialog box with Escape (and also Alt-F4 or | |
794 | // clicking the close button in the title bar) which wouldn't work | |
795 | // without a Cancel button. | |
796 | if ( !useCustomLabels ) | |
797 | { | |
798 | useCustomLabels = true; | |
799 | btnOKLabel = _("OK"); | |
800 | } | |
801 | ||
802 | AddTaskDialogButton(tdc, IDCANCEL, TDCBF_CANCEL_BUTTON, btnOKLabel); | |
803 | } | |
ede7b017 | 804 | } |
7112cdd1 VZ |
805 | |
806 | if ( style & wxHELP ) | |
807 | { | |
808 | // There is no support for "Help" button in the task dialog, it can | |
809 | // only show "Retry" or "Close" ones. | |
810 | useCustomLabels = true; | |
811 | ||
812 | AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel); | |
813 | } | |
ede7b017 VZ |
814 | } |
815 | ||
816 | void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc, | |
817 | int btnCustomId, | |
818 | int btnCommonId, | |
819 | const wxString& customLabel) | |
820 | { | |
821 | if ( useCustomLabels ) | |
822 | { | |
823 | // use custom buttons to implement custom labels | |
824 | TASKDIALOG_BUTTON &tdBtn = buttons[tdc.cButtons]; | |
825 | ||
826 | tdBtn.nButtonID = btnCustomId; | |
017dc06b | 827 | tdBtn.pszButtonText = customLabel.t_str(); |
ede7b017 | 828 | tdc.cButtons++; |
7112cdd1 VZ |
829 | |
830 | // We should never have more than 4 buttons currently as this is the | |
831 | // maximal number of buttons supported by the message dialog. | |
832 | wxASSERT_MSG( tdc.cButtons <= MAX_BUTTONS, wxT("Too many buttons") ); | |
ede7b017 VZ |
833 | } |
834 | else | |
835 | { | |
836 | tdc.dwCommonButtons |= btnCommonId; | |
837 | } | |
838 | } | |
839 | ||
840 | // Task dialog can be used from different threads (and wxProgressDialog always | |
841 | // uses it from another thread in fact) so protect access to the static | |
842 | // variable below with a critical section. | |
843 | wxCRIT_SECT_DECLARE(gs_csTaskDialogIndirect); | |
844 | ||
845 | TaskDialogIndirect_t wxMSWMessageDialog::GetTaskDialogIndirectFunc() | |
846 | { | |
5b1985a9 VZ |
847 | // Initialize the function pointer to an invalid value different from NULL |
848 | // to avoid reloading comctl32.dll and trying to resolve it every time | |
849 | // we're called if task dialog is not available (notice that this may | |
850 | // happen even under Vista+ if we don't use comctl32.dll v6). | |
851 | static const TaskDialogIndirect_t | |
852 | INVALID_TASKDIALOG_FUNC = reinterpret_cast<TaskDialogIndirect_t>(-1); | |
853 | static TaskDialogIndirect_t s_TaskDialogIndirect = INVALID_TASKDIALOG_FUNC; | |
ede7b017 VZ |
854 | |
855 | wxCRIT_SECT_LOCKER(lock, gs_csTaskDialogIndirect); | |
856 | ||
5b1985a9 | 857 | if ( s_TaskDialogIndirect == INVALID_TASKDIALOG_FUNC ) |
ede7b017 VZ |
858 | { |
859 | wxLoadedDLL dllComCtl32("comctl32.dll"); | |
860 | wxDL_INIT_FUNC(s_, TaskDialogIndirect, dllComCtl32); | |
ede7b017 VZ |
861 | } |
862 | ||
863 | return s_TaskDialogIndirect; | |
864 | } | |
865 | ||
866 | #endif // wxHAS_MSW_TASKDIALOG | |
867 | ||
868 | bool wxMSWMessageDialog::HasNativeTaskDialog() | |
869 | { | |
870 | #ifdef wxHAS_MSW_TASKDIALOG | |
5b1985a9 VZ |
871 | if ( wxGetWinVersion() >= wxWinVersion_6 ) |
872 | { | |
873 | if ( wxMSWMessageDialog::GetTaskDialogIndirectFunc() ) | |
874 | return true; | |
875 | } | |
876 | #endif // wxHAS_MSW_TASKDIALOG | |
877 | ||
ede7b017 | 878 | return false; |
ede7b017 VZ |
879 | } |
880 | ||
881 | int wxMSWMessageDialog::MSWTranslateReturnCode(int msAns) | |
882 | { | |
b8505921 | 883 | int ans; |
0d7ea902 VZ |
884 | switch (msAns) |
885 | { | |
b8505921 | 886 | default: |
ede7b017 | 887 | wxFAIL_MSG(wxT("unexpected return code")); |
b8505921 VZ |
888 | // fall through |
889 | ||
0d7ea902 VZ |
890 | case IDCANCEL: |
891 | ans = wxID_CANCEL; | |
892 | break; | |
893 | case IDOK: | |
894 | ans = wxID_OK; | |
895 | break; | |
896 | case IDYES: | |
897 | ans = wxID_YES; | |
898 | break; | |
899 | case IDNO: | |
900 | ans = wxID_NO; | |
901 | break; | |
7112cdd1 VZ |
902 | case IDHELP: |
903 | ans = wxID_HELP; | |
904 | break; | |
0d7ea902 | 905 | } |
ede7b017 | 906 | |
0d7ea902 | 907 | return ans; |
2bda0e17 | 908 | } |
a8ff046b VZ |
909 | |
910 | #endif // wxUSE_MSGDLG |