X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/14f355c2b5c71fc7c3d680aea366582d2ac60f7b..ccc040255c07064f2ed96f1a345d21860b202cb6:/src/msw/msgdlg.cpp diff --git a/src/msw/msgdlg.cpp b/src/msw/msgdlg.cpp index c51438155d..d2e06d4b79 100644 --- a/src/msw/msgdlg.cpp +++ b/src/msw/msgdlg.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: msgdlg.cpp +// Name: src/msw/msgdlg.cpp // Purpose: wxMessageDialog // Author: Julian Smart // Modified by: @@ -9,51 +9,439 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma implementation "msgdlg.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ -#pragma hdrstop + #pragma hdrstop +#endif + +#if wxUSE_MSGDLG + +// there is no hook support under CE so we can't use the code for message box +// positioning there +#ifndef __WXWINCE__ + #define wxUSE_MSGBOX_HOOK 1 +#else + #define wxUSE_MSGBOX_HOOK 0 #endif #ifndef WX_PRECOMP + #include "wx/msgdlg.h" #include "wx/app.h" - #include "wx/defs.h" + #include "wx/intl.h" #include "wx/utils.h" - #include "wx/dialog.h" - #include "wx/msgdlg.h" + #include "wx/msw/private.h" + #if wxUSE_MSGBOX_HOOK + #include "wx/hashmap.h" + #endif #endif -#include "wx/msw/private.h" +#include "wx/ptr_scpd.h" +#include "wx/dynlib.h" +#include "wx/msw/private/button.h" +#include "wx/msw/private/metrics.h" +#include "wx/msw/private/msgdlg.h" + +#if wxUSE_MSGBOX_HOOK + #include "wx/fontutil.h" + #include "wx/textbuf.h" + #include "wx/display.h" +#endif + +// For MB_TASKMODAL +#ifdef __WXWINCE__ + #include "wx/msw/wince/missing.h" +#endif + +// Interestingly, this symbol currently seems to be absent from Platform SDK +// headers but it is documented at MSDN. +#ifndef TDF_SIZE_TO_CONTENT + #define TDF_SIZE_TO_CONTENT 0x1000000 +#endif + +using namespace wxMSWMessageDialog; IMPLEMENT_CLASS(wxMessageDialog, wxDialog) -wxMessageDialog::wxMessageDialog(wxWindow *parent, - const wxString& message, - const wxString& caption, - long style, - const wxPoint& WXUNUSED(pos)) +#if wxUSE_MSGBOX_HOOK + +// there can potentially be one message box per thread so we use a hash map +// with thread ids as keys and (currently shown) message boxes as values +// +// TODO: replace this with wxTLS once it's available +WX_DECLARE_HASH_MAP(unsigned long, wxMessageDialog *, + wxIntegerHash, wxIntegerEqual, + wxMessageDialogMap); + +// the order in this array is the one in which buttons appear in the +// message box +const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] = +{ + { IDYES, &wxMessageDialog::GetYesLabel }, + { IDNO, &wxMessageDialog::GetNoLabel }, + { IDOK, &wxMessageDialog::GetOKLabel }, + { IDCANCEL, &wxMessageDialog::GetCancelLabel }, +}; + +namespace +{ + +wxMessageDialogMap& HookMap() { -#ifdef __WXDEBUG__ - // check for common programming errors - if ( (style & wxID_OK) == wxID_OK ) + static wxMessageDialogMap s_Map; + + return s_Map; +} + +/* + All this code is used for adjusting the message box layout when we mess + with its contents. It's rather complicated because we try hard to avoid + assuming much about the standard layout details and so, instead of just + laying out everything ourselves (which would have been so much simpler!) + we try to only modify the existing controls positions by offsetting them + from their default ones in the hope that this will continue to work with + the future Windows versions. + */ + +// convert the given RECT from screen to client coordinates in place +void ScreenRectToClient(HWND hwnd, RECT& rc) +{ + // map from desktop (i.e. screen) coordinates to ones of this window + // + // notice that a RECT is laid out as 2 consecutive POINTs so the cast is + // valid + ::MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast(&rc), 2); +} + +// set window position to the given rect +inline void SetWindowRect(HWND hwnd, const RECT& rc) +{ + ::MoveWindow(hwnd, + rc.left, rc.top, + rc.right - rc.left, rc.bottom - rc.top, + FALSE); +} + +// set window position expressed in screen coordinates, whether the window is +// child or top level +void MoveWindowToScreenRect(HWND hwnd, RECT rc) +{ + ScreenRectToClient(::GetParent(hwnd), rc); + + SetWindowRect(hwnd, rc); +} + +// helper of AdjustButtonLabels(): move the given window by dx +// +// works for both child and top level windows +void OffsetWindow(HWND hwnd, int dx) +{ + RECT rc = wxGetWindowRect(hwnd); + + rc.left += dx; + rc.right += dx; + + MoveWindowToScreenRect(hwnd, rc); +} + +} // anonymous namespace + +/* static */ +WXLRESULT wxCALLBACK +wxMessageDialog::HookFunction(int code, WXWPARAM wParam, WXLPARAM lParam) +{ + // Find the thread-local instance of wxMessageDialog + const DWORD tid = ::GetCurrentThreadId(); + wxMessageDialogMap::iterator node = HookMap().find(tid); + wxCHECK_MSG( node != HookMap().end(), false, + wxT("bogus thread id in wxMessageDialog::Hook") ); + + wxMessageDialog * const wnd = node->second; + + const HHOOK hhook = (HHOOK)wnd->m_hook; + const LRESULT rc = ::CallNextHookEx(hhook, code, wParam, lParam); + + if ( code == HCBT_ACTIVATE ) { - // programmer probably confused wxID_OK with wxOK. Correct one is wxOK. - wxFAIL_MSG( _T("wxMessageBox: Did you mean wxOK (and not wxID_OK)?") ); + // we won't need this hook any longer + ::UnhookWindowsHookEx(hhook); + wnd->m_hook = NULL; + HookMap().erase(tid); + + wnd->SetHWND((HWND)wParam); + + // replace the static text with an edit control if the message box is + // too big to fit the display + wnd->ReplaceStaticWithEdit(); + + // update the labels if necessary: we need to do it before centering + // the dialog as this can change its size + if ( wnd->HasCustomLabels() ) + wnd->AdjustButtonLabels(); + + // centre the message box on its parent if requested + if ( wnd->GetMessageDialogStyle() & wxCENTER ) + wnd->Center(); // center on parent + //else: default behaviour, center on screen + + // there seems to be no reason to leave it set + wnd->SetHWND(NULL); } -#endif // __WXDEBUG__ - m_caption = caption; - m_message = message; - m_dialogStyle = style; - m_parent = parent; + return rc; } -int wxMessageDialog::ShowModal() +void wxMessageDialog::ReplaceStaticWithEdit() +{ + // check if the message box fits the display + int nDisplay = wxDisplay::GetFromWindow(this); + if ( nDisplay == wxNOT_FOUND ) + nDisplay = 0; + const wxRect rectDisplay = wxDisplay(nDisplay).GetClientArea(); + + if ( rectDisplay.Contains(GetRect()) ) + { + // nothing to do + return; + } + + + // find the static control to replace: normally there are two of them, the + // icon and the text itself so search for all of them and ignore the icon + // ones + HWND hwndStatic = ::FindWindowEx(GetHwnd(), NULL, wxT("STATIC"), NULL); + if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON ) + hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), NULL); + + if ( !hwndStatic ) + { + wxLogDebug("Failed to find the static text control in message box."); + return; + } + + // set the right font for GetCharHeight() call below + wxWindowBase::SetFont(GetMessageFont()); + + // put the new edit control at the same place + RECT rc = wxGetWindowRect(hwndStatic); + ScreenRectToClient(GetHwnd(), rc); + + // but make it less tall so that the message box fits on the screen: we try + // to make the message box take no more than 7/8 of the screen to leave + // some space above and below it + const int hText = (7*rectDisplay.height)/8 - + ( + 2*::GetSystemMetrics(SM_CYFIXEDFRAME) + + ::GetSystemMetrics(SM_CYCAPTION) + + 5*GetCharHeight() // buttons + margins + ); + const int dh = (rc.bottom - rc.top) - hText; // vertical space we save + rc.bottom -= dh; + + // and it also must be wider as it needs a vertical scrollbar (in order + // to preserve the word wrap, otherwise the number of lines would change + // and we want the control to look as similar as possible to the original) + // + // NB: you would have thought that 2*SM_CXEDGE would be enough but it + // isn't, somehow, and the text control breaks lines differently from + // the static one so fudge by adding some extra space + const int dw = ::GetSystemMetrics(SM_CXVSCROLL) + + 4*::GetSystemMetrics(SM_CXEDGE); + rc.right += dw; + + + // chop of the trailing new line(s) from the message box text, they are + // ignored by the static control but result in extra lines and hence extra + // scrollbar position in the edit one + wxString text(wxGetWindowText(hwndStatic)); + for ( wxString::reverse_iterator i = text.rbegin(); i != text.rend(); ++i ) + { + if ( *i != '\n' ) + { + // found last non-newline char, remove anything after it if + // necessary and stop in any case + if ( i != text.rbegin() ) + text.erase(i.base() + 1, text.end()); + break; + } + } + + // do create the new control + HWND hwndEdit = ::CreateWindow + ( + wxT("EDIT"), + wxTextBuffer::Translate(text).t_str(), + WS_CHILD | WS_VSCROLL | WS_VISIBLE | + ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL, + rc.left, rc.top, + rc.right - rc.left, rc.bottom - rc.top, + GetHwnd(), + NULL, + wxGetInstance(), + NULL + ); + + if ( !hwndEdit ) + { + wxLogDebug("Creation of replacement edit control failed in message box"); + return; + } + + // copy the font from the original control + LRESULT hfont = ::SendMessage(hwndStatic, WM_GETFONT, 0, 0); + ::SendMessage(hwndEdit, WM_SETFONT, hfont, 0); + + // and get rid of it + ::DestroyWindow(hwndStatic); + + + // shrink and centre the message box vertically and widen it box to account + // for the extra scrollbar + RECT rcBox = wxGetWindowRect(GetHwnd()); + const int hMsgBox = rcBox.bottom - rcBox.top - dh; + rcBox.top = (rectDisplay.height - hMsgBox)/2; + rcBox.bottom = rcBox.top + hMsgBox + (rectDisplay.height - hMsgBox)%2; + rcBox.left -= dw/2; + rcBox.right += dw - dw/2; + SetWindowRect(GetHwnd(), rcBox); + + // and adjust all the buttons positions + for ( unsigned n = 0; n < WXSIZEOF(ms_buttons); n++ ) + { + const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id); + if ( !hwndBtn ) + continue; // it's ok, not all buttons are always present + + RECT rc = wxGetWindowRect(hwndBtn); + rc.top -= dh; + rc.bottom -= dh; + rc.left += dw/2; + rc.right += dw/2; + MoveWindowToScreenRect(hwndBtn, rc); + } +} + +void wxMessageDialog::AdjustButtonLabels() +{ + // changing the button labels is the easy part but we also need to ensure + // that the buttons are big enough for the label strings and increase their + // size (and maybe the size of the message box itself) if they are not + + // TODO-RTL: check whether this works correctly in RTL + + // we want to use this font in GetTextExtent() calls below but we don't + // want to send WM_SETFONT to the message box, who knows how is it going to + // react to it (right now it doesn't seem to do anything but what if this + // changes) + wxWindowBase::SetFont(GetMessageFont()); + + // first iteration: find the widest button and update the buttons labels + int wBtnOld = 0, // current buttons width + wBtnNew = 0; // required new buttons width + RECT rcBtn; // stores the button height and y positions + unsigned numButtons = 0; // total number of buttons in the message box + unsigned n; + for ( n = 0; n < WXSIZEOF(ms_buttons); n++ ) + { + const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id); + if ( !hwndBtn ) + continue; // it's ok, not all buttons are always present + + numButtons++; + + const wxString label = (this->*ms_buttons[n].getter)(); + const wxSize sizeLabel = wxWindowBase::GetTextExtent(label); + + // check if the button is big enough for this label + const RECT rc = wxGetWindowRect(hwndBtn); + if ( !wBtnOld ) + { + // initialize wBtnOld using the first button width, all the other + // ones should have the same one + wBtnOld = rc.right - rc.left; + + rcBtn = rc; // remember for use below when we reposition the buttons + } + else + { + wxASSERT_MSG( wBtnOld == rc.right - rc.left, + "all buttons are supposed to be of same width" ); + } + + const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x; + if ( widthNeeded > wBtnNew ) + wBtnNew = widthNeeded; + + ::SetWindowText(hwndBtn, label.t_str()); + } + + if ( wBtnNew <= wBtnOld ) + { + // all buttons fit, nothing else to do + return; + } + + // resize the message box to be wider if needed + const int wBoxOld = wxGetClientRect(GetHwnd()).right; + + const int CHAR_WIDTH = GetCharWidth(); + const int MARGIN_OUTER = 2*CHAR_WIDTH; // margin between box and buttons + const int MARGIN_INNER = CHAR_WIDTH; // margin between buttons + + RECT rcBox = wxGetWindowRect(GetHwnd()); + + const int wAllButtons = numButtons*(wBtnNew + MARGIN_INNER) - MARGIN_INNER; + int wBoxNew = 2*MARGIN_OUTER + wAllButtons; + if ( wBoxNew > wBoxOld ) + { + const int dw = wBoxNew - wBoxOld; + rcBox.left -= dw/2; + rcBox.right += dw - dw/2; + + SetWindowRect(GetHwnd(), rcBox); + + // surprisingly, we don't need to resize the static text control, it + // seems to adjust itself to the new size, at least under Windows 2003 + // (TODO: test if this happens on older Windows versions) + } + else // the current width is big enough + { + wBoxNew = wBoxOld; + } + + + // finally position all buttons + + // notice that we have to take into account the difference between window + // and client width + rcBtn.left = (rcBox.left + rcBox.right - wxGetClientRect(GetHwnd()).right + + wBoxNew - wAllButtons) / 2; + rcBtn.right = rcBtn.left + wBtnNew; + + for ( n = 0; n < WXSIZEOF(ms_buttons); n++ ) + { + const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id); + if ( !hwndBtn ) + continue; + + MoveWindowToScreenRect(hwndBtn, rcBtn); + + rcBtn.left += wBtnNew + MARGIN_INNER; + rcBtn.right += wBtnNew + MARGIN_INNER; + } +} + +#endif // wxUSE_MSGBOX_HOOK + +/* static */ +wxFont wxMessageDialog::GetMessageFont() +{ + const NONCLIENTMETRICS& ncm = wxMSWImpl::GetNonClientMetrics(); + return wxNativeFontInfo(ncm.lfMessageFont); +} + +int wxMessageDialog::ShowMessageBox() { if ( !wxTheApp->GetTopWindow() ) { @@ -66,56 +454,415 @@ int wxMessageDialog::ShowModal() } // use the top level window as parent if none specified - if ( !m_parent ) - m_parent = FindSuitableParent(); + m_parent = GetParentForModalDialog(); HWND hWnd = m_parent ? GetHwndOf(m_parent) : NULL; +#if wxUSE_INTL + // native message box always uses the current user locale but the program + // may be using a different one and in this case we need to manually + // translate the default button labels (if they're non default we have no + // way to translate them and so we must assume they were already + // translated) to avoid mismatch between the language of the message box + // text and its buttons + wxLocale * const loc = wxGetLocale(); + if ( loc && loc->GetLanguage() != wxLocale::GetSystemLanguage() ) + { + if ( m_dialogStyle & wxYES_NO && + (GetCustomYesLabel().empty() && GetCustomNoLabel().empty()) ) + + { + // use the strings with mnemonics here as the native message box + // does + SetYesNoLabels(_("&Yes"), _("&No")); + } + + // we may or not have the Ok/Cancel buttons but either we do have them + // or we already made the labels custom because we called + // SetYesNoLabels() above so doing this does no harm -- and is + // necessary in wxYES_NO | wxCANCEL case + // + // note that we don't use mnemonics here for consistency with the + // native message box (which probably doesn't use them because + // Enter/Esc keys can be already used to dismiss the message box + // using keyboard) + if ( GetCustomOKLabel().empty() && GetCustomCancelLabel().empty() ) + SetOKCancelLabels(_("OK"), _("Cancel")); + } +#endif // wxUSE_INTL + // translate wx style in MSW - unsigned int msStyle = MB_OK; - if (m_dialogStyle & wxYES_NO) + unsigned int msStyle; + const long wxStyle = GetMessageDialogStyle(); + if ( wxStyle & wxYES_NO ) { - if (m_dialogStyle & wxCANCEL) +#if !(defined(__SMARTPHONE__) && defined(__WXWINCE__)) + if (wxStyle & wxCANCEL) msStyle = MB_YESNOCANCEL; else +#endif // !(__SMARTPHONE__ && __WXWINCE__) msStyle = MB_YESNO; - if (m_dialogStyle & wxNO_DEFAULT) + if ( wxStyle & wxNO_DEFAULT ) msStyle |= MB_DEFBUTTON2; + else if ( wxStyle & wxCANCEL_DEFAULT ) + msStyle |= MB_DEFBUTTON3; } - - if (m_dialogStyle & wxOK) + else // without Yes/No we're going to have an OK button { - if (m_dialogStyle & wxCANCEL) + if ( wxStyle & wxCANCEL ) + { msStyle = MB_OKCANCEL; - else + + if ( wxStyle & wxCANCEL_DEFAULT ) + msStyle |= MB_DEFBUTTON2; + } + else // just "OK" + { msStyle = MB_OK; + } + } + + if ( wxStyle & wxHELP ) + { + msStyle |= MB_HELP; } - if (m_dialogStyle & wxICON_EXCLAMATION) - msStyle |= MB_ICONEXCLAMATION; - else if (m_dialogStyle & wxICON_HAND) - msStyle |= MB_ICONHAND; - else if (m_dialogStyle & wxICON_INFORMATION) - msStyle |= MB_ICONINFORMATION; - else if (m_dialogStyle & wxICON_QUESTION) - msStyle |= MB_ICONQUESTION; - if ( m_dialogStyle & wxSTAY_ON_TOP ) + // set the icon style + switch ( GetEffectiveIcon() ) + { + case wxICON_ERROR: + msStyle |= MB_ICONHAND; + break; + + case wxICON_WARNING: + msStyle |= MB_ICONEXCLAMATION; + break; + + case wxICON_QUESTION: + msStyle |= MB_ICONQUESTION; + break; + + case wxICON_INFORMATION: + msStyle |= MB_ICONINFORMATION; + break; + } + + if ( wxStyle & wxSTAY_ON_TOP ) msStyle |= MB_TOPMOST; +#ifndef __WXWINCE__ + if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) + msStyle |= MB_RTLREADING | MB_RIGHT; +#endif + if (hWnd) msStyle |= MB_APPLMODAL; -#ifndef __WXWINCE__ else msStyle |= MB_TASKMODAL; -#endif + + // per MSDN documentation for MessageBox() we can prefix the message with 2 + // right-to-left mark characters to tell the function to use RTL layout + // (unfortunately this only works in Unicode builds) + wxString message = GetFullMessage(); +#if wxUSE_UNICODE + if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) + { + // NB: not all compilers support \u escapes + static const wchar_t wchRLM = 0x200f; + message.Prepend(wxString(wchRLM, 2)); + } +#endif // wxUSE_UNICODE + +#if wxUSE_MSGBOX_HOOK + // install the hook in any case as we don't know in advance if the message + // box is not going to be too big (requiring the replacement of the static + // control with an edit one) + const DWORD tid = ::GetCurrentThreadId(); + m_hook = ::SetWindowsHookEx(WH_CBT, + &wxMessageDialog::HookFunction, NULL, tid); + HookMap()[tid] = this; +#endif // wxUSE_MSGBOX_HOOK // do show the dialog - int msAns = MessageBox(hWnd, m_message.c_str(), m_caption.c_str(), msStyle); + int msAns = MessageBox(hWnd, message.t_str(), m_caption.t_str(), msStyle); + + return MSWTranslateReturnCode(msAns); +} + +int wxMessageDialog::ShowModal() +{ +#ifdef wxHAS_MSW_TASKDIALOG + if ( HasNativeTaskDialog() ) + { + TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc(); + wxCHECK_MSG( taskDialogIndirect, wxID_CANCEL, wxS("no task dialog?") ); + + WinStruct tdc; + wxMSWTaskDialogConfig wxTdc( *this ); + wxTdc.MSWCommonTaskDialogInit( tdc ); + + int msAns; + HRESULT hr = taskDialogIndirect( &tdc, &msAns, NULL, NULL ); + if ( FAILED(hr) ) + { + wxLogApiError( "TaskDialogIndirect", hr ); + return wxID_CANCEL; + } + + // In case only an "OK" button was specified we actually created a + // "Cancel" button (see comment in MSWCommonTaskDialogInit). This + // results in msAns being IDCANCEL while we want IDOK (just like + // how the native MessageBox function does with only an "OK" button). + if ( (msAns == IDCANCEL) + && !(GetMessageDialogStyle() & (wxYES_NO|wxCANCEL)) ) + { + msAns = IDOK; + } + + return MSWTranslateReturnCode( msAns ); + } +#endif // wxHAS_MSW_TASKDIALOG + + return ShowMessageBox(); +} + +void wxMessageDialog::DoCentre(int dir) +{ +#ifdef wxHAS_MSW_TASKDIALOG + // Task dialog is always centered on its parent window and trying to center + // it manually doesn't work because its HWND is not created yet so don't + // even try as this would only result in (debug) error messages. + if ( HasNativeTaskDialog() ) + return; +#endif // wxHAS_MSW_TASKDIALOG + + wxMessageDialogBase::DoCentre(dir); +} + +// ---------------------------------------------------------------------------- +// Helpers of the wxMSWMessageDialog namespace +// ---------------------------------------------------------------------------- + +#ifdef wxHAS_MSW_TASKDIALOG + +wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg) + : buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS]) +{ + parent = dlg.GetParentForModalDialog(); + caption = dlg.GetCaption(); + message = dlg.GetMessage(); + extendedMessage = dlg.GetExtendedMessage(); + + // Before wxMessageDialog added support for extended message it was common + // practice to have long multiline texts in the message box with the first + // line playing the role of the main message and the rest of the extended + // one. Try to detect such usage automatically here by synthesizing the + // extended message on our own if it wasn't given. + if ( extendedMessage.empty() ) + { + // Check if there is a blank separating line after the first line (this + // is not the same as searching for "\n\n" as we want the automatically + // recognized main message be single line to avoid embarrassing false + // positives). + const size_t posNL = message.find('\n'); + if ( posNL != wxString::npos && + posNL < message.length() - 1 && + message[posNL + 1 ] == '\n' ) + { + extendedMessage.assign(message, posNL + 2, wxString::npos); + message.erase(posNL); + } + } + + iconId = dlg.GetEffectiveIcon(); + style = dlg.GetMessageDialogStyle(); + useCustomLabels = dlg.HasCustomLabels(); + btnYesLabel = dlg.GetYesLabel(); + btnNoLabel = dlg.GetNoLabel(); + btnOKLabel = dlg.GetOKLabel(); + btnCancelLabel = dlg.GetCancelLabel(); + btnHelpLabel = dlg.GetHelpLabel(); +} + +void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc) +{ + // Use TDF_SIZE_TO_CONTENT to try to prevent Windows from truncating or + // ellipsizing the message text. This doesn't always work as Windows will + // still do it if the message contains too long "words" (i.e. runs of the + // text without spaces) but at least it ensures that the message text is + // fully shown for reasonably-sized words whereas without it using almost + // any file system path in a message box would result in truncation. + tdc.dwFlags = TDF_EXPAND_FOOTER_AREA | + TDF_POSITION_RELATIVE_TO_WINDOW | + TDF_SIZE_TO_CONTENT; + tdc.hInstance = wxGetInstance(); + tdc.pszWindowTitle = caption.t_str(); + + // use the top level window as parent if none specified + tdc.hwndParent = parent ? GetHwndOf(parent) : NULL; + + if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) + tdc.dwFlags |= TDF_RTL_LAYOUT; + + // If we have both the main and extended messages, just use them as + // intended. However if only one message is given we normally use it as the + // content and not as the main instruction because the latter is supposed + // to stand out compared to the former and doesn't look good if there is + // nothing for it to contrast with. Finally, notice that the extended + // message we use here might be automatically extracted from the main + // message in our ctor, see comment there. + if ( !extendedMessage.empty() ) + { + tdc.pszMainInstruction = message.t_str(); + tdc.pszContent = extendedMessage.t_str(); + } + else + { + tdc.pszContent = message.t_str(); + } + + // set an icon to be used, if possible + switch ( iconId ) + { + case wxICON_ERROR: + tdc.pszMainIcon = TD_ERROR_ICON; + break; + + case wxICON_WARNING: + tdc.pszMainIcon = TD_WARNING_ICON; + break; + + case wxICON_INFORMATION: + tdc.pszMainIcon = TD_INFORMATION_ICON; + break; + } + + // custom label button array that can hold all buttons in use + tdc.pButtons = buttons.get(); + + if ( style & wxYES_NO ) + { + AddTaskDialogButton(tdc, IDYES, TDCBF_YES_BUTTON, btnYesLabel); + AddTaskDialogButton(tdc, IDNO, TDCBF_NO_BUTTON, btnNoLabel); + + if (style & wxCANCEL) + AddTaskDialogButton(tdc, IDCANCEL, + TDCBF_CANCEL_BUTTON, btnCancelLabel); + + if ( style & wxNO_DEFAULT ) + tdc.nDefaultButton = IDNO; + else if ( style & wxCANCEL_DEFAULT ) + tdc.nDefaultButton = IDCANCEL; + } + else // without Yes/No we're going to have an OK button + { + if ( style & wxCANCEL ) + { + AddTaskDialogButton(tdc, IDOK, TDCBF_OK_BUTTON, btnOKLabel); + AddTaskDialogButton(tdc, IDCANCEL, + TDCBF_CANCEL_BUTTON, btnCancelLabel); + + if ( style & wxCANCEL_DEFAULT ) + tdc.nDefaultButton = IDCANCEL; + } + else // Only "OK" + { + // We actually create a "Cancel" button instead because we want to + // allow closing the dialog box with Escape (and also Alt-F4 or + // clicking the close button in the title bar) which wouldn't work + // without a Cancel button. + if ( !useCustomLabels ) + { + useCustomLabels = true; + btnOKLabel = _("OK"); + } + + AddTaskDialogButton(tdc, IDCANCEL, TDCBF_CANCEL_BUTTON, btnOKLabel); + } + } + + if ( style & wxHELP ) + { + // There is no support for "Help" button in the task dialog, it can + // only show "Retry" or "Close" ones. + useCustomLabels = true; + + AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel); + } +} + +void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc, + int btnCustomId, + int btnCommonId, + const wxString& customLabel) +{ + if ( useCustomLabels ) + { + // use custom buttons to implement custom labels + TASKDIALOG_BUTTON &tdBtn = buttons[tdc.cButtons]; + + tdBtn.nButtonID = btnCustomId; + tdBtn.pszButtonText = customLabel.t_str(); + tdc.cButtons++; + + // We should never have more than 4 buttons currently as this is the + // maximal number of buttons supported by the message dialog. + wxASSERT_MSG( tdc.cButtons <= MAX_BUTTONS, wxT("Too many buttons") ); + } + else + { + tdc.dwCommonButtons |= btnCommonId; + } +} + +// Task dialog can be used from different threads (and wxProgressDialog always +// uses it from another thread in fact) so protect access to the static +// variable below with a critical section. +wxCRIT_SECT_DECLARE(gs_csTaskDialogIndirect); + +TaskDialogIndirect_t wxMSWMessageDialog::GetTaskDialogIndirectFunc() +{ + // Initialize the function pointer to an invalid value different from NULL + // to avoid reloading comctl32.dll and trying to resolve it every time + // we're called if task dialog is not available (notice that this may + // happen even under Vista+ if we don't use comctl32.dll v6). + static const TaskDialogIndirect_t + INVALID_TASKDIALOG_FUNC = reinterpret_cast(-1); + static TaskDialogIndirect_t s_TaskDialogIndirect = INVALID_TASKDIALOG_FUNC; + + wxCRIT_SECT_LOCKER(lock, gs_csTaskDialogIndirect); + + if ( s_TaskDialogIndirect == INVALID_TASKDIALOG_FUNC ) + { + wxLoadedDLL dllComCtl32("comctl32.dll"); + wxDL_INIT_FUNC(s_, TaskDialogIndirect, dllComCtl32); + } + + return s_TaskDialogIndirect; +} + +#endif // wxHAS_MSW_TASKDIALOG + +bool wxMSWMessageDialog::HasNativeTaskDialog() +{ +#ifdef wxHAS_MSW_TASKDIALOG + if ( wxGetWinVersion() >= wxWinVersion_6 ) + { + if ( wxMSWMessageDialog::GetTaskDialogIndirectFunc() ) + return true; + } +#endif // wxHAS_MSW_TASKDIALOG + + return false; +} + +int wxMSWMessageDialog::MSWTranslateReturnCode(int msAns) +{ int ans; switch (msAns) { default: - wxFAIL_MSG(_T("unexpected ::MessageBox() return code")); + wxFAIL_MSG(wxT("unexpected return code")); // fall through case IDCANCEL: @@ -130,7 +877,12 @@ int wxMessageDialog::ShowModal() case IDNO: ans = wxID_NO; break; + case IDHELP: + ans = wxID_HELP; + break; } + return ans; } +#endif // wxUSE_MSGDLG