]>
git.saurik.com Git - wxWidgets.git/blob - src/msw/msgdlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/msgdlg.cpp
3 // Purpose: wxMessageDialog
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
21 #include "wx/msgdlg.h"
23 // there is no hook support under CE so we can't use the code for message box
26 #define wxUSE_MSGBOX_HOOK 1
28 #define wxUSE_MSGBOX_HOOK 0
35 #include "wx/dialog.h"
37 #include "wx/hashmap.h"
41 #include "wx/msw/private.h"
42 #include "wx/msw/private/button.h"
43 #include "wx/msw/private/metrics.h"
46 #include "wx/fontutil.h"
47 #include "wx/textbuf.h"
48 #include "wx/display.h"
53 #include "wx/msw/wince/missing.h"
56 IMPLEMENT_CLASS(wxMessageDialog
, wxDialog
)
60 // there can potentially be one message box per thread so we use a hash map
61 // with thread ids as keys and (currently shown) message boxes as values
63 // TODO: replace this with wxTLS once it's available
64 WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog
*,
65 wxIntegerHash
, wxIntegerEqual
,
68 // the order in this array is the one in which buttons appear in the
70 const wxMessageDialog::ButtonAccessors
wxMessageDialog::ms_buttons
[] =
72 { IDYES
, &wxMessageDialog::GetYesLabel
},
73 { IDNO
, &wxMessageDialog::GetNoLabel
},
74 { IDOK
, &wxMessageDialog::GetOKLabel
},
75 { IDCANCEL
, &wxMessageDialog::GetCancelLabel
},
81 wxMessageDialogMap
& HookMap()
83 static wxMessageDialogMap s_Map
;
89 All this code is used for adjusting the message box layout when we mess
90 with its contents. It's rather complicated because we try hard to avoid
91 assuming much about the standard layout details and so, instead of just
92 laying out everything ourselves (which would have been so much simpler!)
93 we try to only modify the existing controls positions by offsetting them
94 from their default ones in the hope that this will continue to work with
95 the future Windows versions.
98 // convert the given RECT from screen to client coordinates in place
99 void ScreenRectToClient(HWND hwnd
, RECT
& rc
)
101 // map from desktop (i.e. screen) coordinates to ones of this window
103 // notice that a RECT is laid out as 2 consecutive POINTs so the cast is
105 ::MapWindowPoints(HWND_DESKTOP
, hwnd
, reinterpret_cast<POINT
*>(&rc
), 2);
108 // set window position to the given rect
109 inline void SetWindowRect(HWND hwnd
, const RECT
& rc
)
113 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
117 // set window position expressed in screen coordinates, whether the window is
118 // child or top level
119 void MoveWindowToScreenRect(HWND hwnd
, RECT rc
)
121 ScreenRectToClient(::GetParent(hwnd
), rc
);
123 SetWindowRect(hwnd
, rc
);
126 // helper of AdjustButtonLabels(): move the given window by dx
128 // works for both child and top level windows
129 void OffsetWindow(HWND hwnd
, int dx
)
131 RECT rc
= wxGetWindowRect(hwnd
);
136 MoveWindowToScreenRect(hwnd
, rc
);
139 } // anonymous namespace
143 wxMessageDialog::HookFunction(int code
, WXWPARAM wParam
, WXLPARAM lParam
)
145 // Find the thread-local instance of wxMessageDialog
146 const DWORD tid
= ::GetCurrentThreadId();
147 wxMessageDialogMap::iterator node
= HookMap().find(tid
);
148 wxCHECK_MSG( node
!= HookMap().end(), false,
149 wxT("bogus thread id in wxMessageDialog::Hook") );
151 wxMessageDialog
* const wnd
= node
->second
;
153 const HHOOK hhook
= (HHOOK
)wnd
->m_hook
;
154 const LRESULT rc
= ::CallNextHookEx(hhook
, code
, wParam
, lParam
);
156 if ( code
== HCBT_ACTIVATE
)
158 // we won't need this hook any longer
159 ::UnhookWindowsHookEx(hhook
);
161 HookMap().erase(tid
);
163 wnd
->SetHWND((HWND
)wParam
);
165 // replace the static text with an edit control if the message box is
166 // too big to fit the display
167 wnd
->ReplaceStaticWithEdit();
169 // update the labels if necessary: we need to do it before centering
170 // the dialog as this can change its size
171 if ( wnd
->HasCustomLabels() )
172 wnd
->AdjustButtonLabels();
174 // centre the message box on its parent if requested
175 if ( wnd
->GetMessageDialogStyle() & wxCENTER
)
176 wnd
->Center(); // center on parent
177 //else: default behaviour, center on screen
179 // there seems to be no reason to leave it set
186 void wxMessageDialog::ReplaceStaticWithEdit()
188 // check if the message box fits the display
189 int nDisplay
= wxDisplay::GetFromWindow(this);
190 if ( nDisplay
== wxNOT_FOUND
)
192 const wxRect rectDisplay
= wxDisplay(nDisplay
).GetClientArea();
194 if ( rectDisplay
.Contains(GetRect()) )
201 // find the static control to replace: normally there are two of them, the
202 // icon and the text itself so search for all of them and ignore the icon
204 HWND hwndStatic
= ::FindWindowEx(GetHwnd(), NULL
, wxT("STATIC"), NULL
);
205 if ( ::GetWindowLong(hwndStatic
, GWL_STYLE
) & SS_ICON
)
206 hwndStatic
= ::FindWindowEx(GetHwnd(), hwndStatic
, wxT("STATIC"), NULL
);
210 wxLogDebug("Failed to find the static text control in message box.");
214 // set the right font for GetCharHeight() call below
215 wxWindowBase::SetFont(GetMessageFont());
217 // put the new edit control at the same place
218 RECT rc
= wxGetWindowRect(hwndStatic
);
219 ScreenRectToClient(GetHwnd(), rc
);
221 // but make it less tall so that the message box fits on the screen: we try
222 // to make the message box take no more than 7/8 of the screen to leave
223 // some space above and below it
224 const int hText
= (7*rectDisplay
.height
)/8 -
226 2*::GetSystemMetrics(SM_CYFIXEDFRAME
) +
227 ::GetSystemMetrics(SM_CYCAPTION
) +
228 5*GetCharHeight() // buttons + margins
230 const int dh
= (rc
.bottom
- rc
.top
) - hText
; // vertical space we save
233 // and it also must be wider as it needs a vertical scrollbar (in order
234 // to preserve the word wrap, otherwise the number of lines would change
235 // and we want the control to look as similar as possible to the original)
237 // NB: you would have thought that 2*SM_CXEDGE would be enough but it
238 // isn't, somehow, and the text control breaks lines differently from
239 // the static one so fudge by adding some extra space
240 const int dw
= ::GetSystemMetrics(SM_CXVSCROLL
) +
241 4*::GetSystemMetrics(SM_CXEDGE
);
245 // chop of the trailing new line(s) from the message box text, they are
246 // ignored by the static control but result in extra lines and hence extra
247 // scrollbar position in the edit one
248 wxString
text(wxGetWindowText(hwndStatic
));
249 for ( wxString::iterator i
= text
.end() - 1; i
!= text
.begin(); --i
)
253 text
.erase(i
+ 1, text
.end());
258 // do create the new control
259 HWND hwndEdit
= ::CreateWindow
262 wxTextBuffer::Translate(text
).wx_str(),
263 WS_CHILD
| WS_VSCROLL
| WS_VISIBLE
|
264 ES_MULTILINE
| ES_READONLY
| ES_AUTOVSCROLL
,
266 rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
275 wxLogDebug("Creation of replacement edit control failed in message box");
279 // copy the font from the original control
280 LRESULT hfont
= ::SendMessage(hwndStatic
, WM_GETFONT
, 0, 0);
281 ::SendMessage(hwndEdit
, WM_SETFONT
, hfont
, 0);
284 ::DestroyWindow(hwndStatic
);
287 // shrink and centre the message box vertically and widen it box to account
288 // for the extra scrollbar
289 RECT rcBox
= wxGetWindowRect(GetHwnd());
290 const int hMsgBox
= rcBox
.bottom
- rcBox
.top
- dh
;
291 rcBox
.top
= (rectDisplay
.height
- hMsgBox
)/2;
292 rcBox
.bottom
= rcBox
.top
+ hMsgBox
+ (rectDisplay
.height
- hMsgBox
)%2
;
294 rcBox
.right
+= dw
- dw
/2;
295 SetWindowRect(GetHwnd(), rcBox
);
297 // and adjust all the buttons positions
298 for ( unsigned n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
300 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
302 continue; // it's ok, not all buttons are always present
304 RECT rc
= wxGetWindowRect(hwndBtn
);
309 MoveWindowToScreenRect(hwndBtn
, rc
);
313 void wxMessageDialog::AdjustButtonLabels()
315 // changing the button labels is the easy part but we also need to ensure
316 // that the buttons are big enough for the label strings and increase their
317 // size (and maybe the size of the message box itself) if they are not
319 // TODO-RTL: check whether this works correctly in RTL
321 // we want to use this font in GetTextExtent() calls below but we don't
322 // want to send WM_SETFONT to the message box, who knows how is it going to
323 // react to it (right now it doesn't seem to do anything but what if this
325 wxWindowBase::SetFont(GetMessageFont());
327 // first iteration: find the widest button and update the buttons labels
328 int wBtnOld
= 0, // current buttons width
329 wBtnNew
= 0; // required new buttons width
330 RECT rcBtn
; // stores the button height and y positions
331 unsigned numButtons
= 0; // total number of buttons in the message box
333 for ( n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
335 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
337 continue; // it's ok, not all buttons are always present
341 const wxString label
= (this->*ms_buttons
[n
].getter
)();
342 const wxSize sizeLabel
= wxWindowBase::GetTextExtent(label
);
344 // check if the button is big enough for this label
345 const RECT rc
= wxGetWindowRect(hwndBtn
);
348 // initialize wBtnOld using the first button width, all the other
349 // ones should have the same one
350 wBtnOld
= rc
.right
- rc
.left
;
352 rcBtn
= rc
; // remember for use below when we reposition the buttons
356 wxASSERT_MSG( wBtnOld
== rc
.right
- rc
.left
,
357 "all buttons are supposed to be of same width" );
360 const int widthNeeded
= wxMSWButton::GetFittingSize(this, sizeLabel
).x
;
361 if ( widthNeeded
> wBtnNew
)
362 wBtnNew
= widthNeeded
;
364 ::SetWindowText(hwndBtn
, label
.wx_str());
367 if ( wBtnNew
<= wBtnOld
)
369 // all buttons fit, nothing else to do
373 // resize the message box to be wider if needed
374 const int wBoxOld
= wxGetClientRect(GetHwnd()).right
;
376 const int CHAR_WIDTH
= GetCharWidth();
377 const int MARGIN_OUTER
= 2*CHAR_WIDTH
; // margin between box and buttons
378 const int MARGIN_INNER
= CHAR_WIDTH
; // margin between buttons
380 RECT rcBox
= wxGetWindowRect(GetHwnd());
382 const int wAllButtons
= numButtons
*(wBtnNew
+ MARGIN_INNER
) - MARGIN_INNER
;
383 int wBoxNew
= 2*MARGIN_OUTER
+ wAllButtons
;
384 if ( wBoxNew
> wBoxOld
)
386 const int dw
= wBoxNew
- wBoxOld
;
388 rcBox
.right
+= dw
- dw
/2;
390 SetWindowRect(GetHwnd(), rcBox
);
392 // surprisingly, we don't need to resize the static text control, it
393 // seems to adjust itself to the new size, at least under Windows 2003
394 // (TODO: test if this happens on older Windows versions)
396 else // the current width is big enough
402 // finally position all buttons
404 // notice that we have to take into account the difference between window
406 rcBtn
.left
= (rcBox
.left
+ rcBox
.right
- wxGetClientRect(GetHwnd()).right
+
407 wBoxNew
- wAllButtons
) / 2;
408 rcBtn
.right
= rcBtn
.left
+ wBtnNew
;
410 for ( n
= 0; n
< WXSIZEOF(ms_buttons
); n
++ )
412 const HWND hwndBtn
= ::GetDlgItem(GetHwnd(), ms_buttons
[n
].id
);
416 MoveWindowToScreenRect(hwndBtn
, rcBtn
);
418 rcBtn
.left
+= wBtnNew
+ MARGIN_INNER
;
419 rcBtn
.right
+= wBtnNew
+ MARGIN_INNER
;
423 #endif // wxUSE_MSGBOX_HOOK
426 wxFont
wxMessageDialog::GetMessageFont()
428 const NONCLIENTMETRICS
& ncm
= wxMSWImpl::GetNonClientMetrics();
429 return wxNativeFontInfo(ncm
.lfMessageFont
);
432 int wxMessageDialog::ShowModal()
434 if ( !wxTheApp
->GetTopWindow() )
436 // when the message box is shown from wxApp::OnInit() (i.e. before the
437 // message loop is entered), this must be done or the next message box
438 // will never be shown - just try putting 2 calls to wxMessageBox() in
439 // OnInit() to see it
440 while ( wxTheApp
->Pending() )
441 wxTheApp
->Dispatch();
444 // use the top level window as parent if none specified
446 m_parent
= GetParentForModalDialog();
447 HWND hWnd
= m_parent
? GetHwndOf(m_parent
) : NULL
;
450 // native message box always uses the current user locale but the program
451 // may be using a different one and in this case we need to manually
452 // translate the button labels to avoid mismatch between the language of
453 // the message box text and its buttons
454 wxLocale
* const loc
= wxGetLocale();
455 if ( loc
&& loc
->GetLanguage() != wxLocale::GetSystemLanguage() )
457 if ( m_dialogStyle
& wxYES_NO
)
459 // use the strings with mnemonics here as the native message box
461 SetYesNoLabels(_("&Yes"), _("&No"));
464 // we may or not have the Ok/Cancel buttons but either we do have them
465 // or we already made the labels custom because we called
466 // SetYesNoLabels() above so doing this does no harm -- and is
467 // necessary in wxYES_NO | wxCANCEL case
469 // note that we don't use mnemonics here for consistency with the
470 // native message box (which probably doesn't use them because
471 // Enter/Esc keys can be already used to dismiss the message box
473 SetOKCancelLabels(_("OK"), _("Cancel"));
477 // translate wx style in MSW
478 unsigned int msStyle
;
479 const long wxStyle
= GetMessageDialogStyle();
480 if ( wxStyle
& wxYES_NO
)
482 #if !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
483 if (wxStyle
& wxCANCEL
)
484 msStyle
= MB_YESNOCANCEL
;
486 #endif // !(__SMARTPHONE__ && __WXWINCE__)
489 if ( wxStyle
& wxNO_DEFAULT
)
490 msStyle
|= MB_DEFBUTTON2
;
491 else if ( wxStyle
& wxCANCEL_DEFAULT
)
492 msStyle
|= MB_DEFBUTTON3
;
494 else // without Yes/No we're going to have an OK button
496 if ( wxStyle
& wxCANCEL
)
498 msStyle
= MB_OKCANCEL
;
500 if ( wxStyle
& wxCANCEL_DEFAULT
)
501 msStyle
|= MB_DEFBUTTON2
;
509 if (wxStyle
& wxICON_EXCLAMATION
)
510 msStyle
|= MB_ICONEXCLAMATION
;
511 else if (wxStyle
& wxICON_HAND
)
512 msStyle
|= MB_ICONHAND
;
513 else if (wxStyle
& wxICON_INFORMATION
)
514 msStyle
|= MB_ICONINFORMATION
;
515 else if (wxStyle
& wxICON_QUESTION
)
516 msStyle
|= MB_ICONQUESTION
;
518 if ( wxStyle
& wxSTAY_ON_TOP
)
519 msStyle
|= MB_TOPMOST
;
522 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
523 msStyle
|= MB_RTLREADING
| MB_RIGHT
;
527 msStyle
|= MB_APPLMODAL
;
529 msStyle
|= MB_TASKMODAL
;
531 // per MSDN documentation for MessageBox() we can prefix the message with 2
532 // right-to-left mark characters to tell the function to use RTL layout
533 // (unfortunately this only works in Unicode builds)
534 wxString message
= GetFullMessage();
536 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
538 // NB: not all compilers support \u escapes
539 static const wchar_t wchRLM
= 0x200f;
540 message
.Prepend(wxString(wchRLM
, 2));
542 #endif // wxUSE_UNICODE
544 #if wxUSE_MSGBOX_HOOK
545 // install the hook in any case as we don't know in advance if the message
546 // box is not going to be too big (requiring the replacement of the static
547 // control with an edit one)
548 const DWORD tid
= ::GetCurrentThreadId();
549 m_hook
= ::SetWindowsHookEx(WH_CBT
,
550 &wxMessageDialog::HookFunction
, NULL
, tid
);
551 HookMap()[tid
] = this;
552 #endif // wxUSE_MSGBOX_HOOK
554 // do show the dialog
555 int msAns
= MessageBox(hWnd
, message
.wx_str(), m_caption
.wx_str(), msStyle
);
560 wxFAIL_MSG(wxT("unexpected ::MessageBox() return code"));
579 #endif // wxUSE_MSGDLG