X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/b76b3d2854fae7c61c2472db88cb65aea9b7a08d..07aaf32633ecf18ec3edfbb41793a112914792d0:/src/msw/msgdlg.cpp diff --git a/src/msw/msgdlg.cpp b/src/msw/msgdlg.cpp index a4c79cc53a..edff0a0f5a 100644 --- a/src/msw/msgdlg.cpp +++ b/src/msw/msgdlg.cpp @@ -18,8 +18,6 @@ #if wxUSE_MSGDLG -#include "wx/msgdlg.h" - // there is no hook support under CE so we can't use the code for message box // positioning there #ifndef __WXWINCE__ @@ -29,18 +27,22 @@ #endif #ifndef WX_PRECOMP + #include "wx/msgdlg.h" #include "wx/app.h" #include "wx/intl.h" #include "wx/utils.h" - #include "wx/dialog.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" +#include "wx/testing.h" #if wxUSE_MSGBOX_HOOK #include "wx/fontutil.h" @@ -53,6 +55,14 @@ #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) #if wxUSE_MSGBOX_HOOK @@ -250,8 +260,10 @@ void wxMessageDialog::ReplaceStaticWithEdit() { if ( *i != '\n' ) { - // found last non-newline char, remove everything after it and stop - text.erase(i.base() + 1, text.end()); + // 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; } } @@ -260,7 +272,7 @@ void wxMessageDialog::ReplaceStaticWithEdit() HWND hwndEdit = ::CreateWindow ( wxT("EDIT"), - wxTextBuffer::Translate(text).wx_str(), + wxTextBuffer::Translate(text).t_str(), WS_CHILD | WS_VSCROLL | WS_VISIBLE | ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL, rc.left, rc.top, @@ -362,7 +374,7 @@ void wxMessageDialog::AdjustButtonLabels() if ( widthNeeded > wBtnNew ) wBtnNew = widthNeeded; - ::SetWindowText(hwndBtn, label.wx_str()); + ::SetWindowText(hwndBtn, label.t_str()); } if ( wBtnNew <= wBtnOld ) @@ -430,7 +442,7 @@ wxFont wxMessageDialog::GetMessageFont() return wxNativeFontInfo(ncm.lfMessageFont); } -int wxMessageDialog::ShowModal() +int wxMessageDialog::ShowMessageBox() { if ( !wxTheApp->GetTopWindow() ) { @@ -443,19 +455,22 @@ int wxMessageDialog::ShowModal() } // use the top level window as parent if none specified - if ( !m_parent ) - m_parent = GetParentForModalDialog(); + 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 button labels to avoid mismatch between the language of - // the message box text and its buttons + // 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 ) + if ( m_dialogStyle & wxYES_NO && + (GetCustomYesLabel().empty() && GetCustomNoLabel().empty()) ) + { // use the strings with mnemonics here as the native message box // does @@ -471,7 +486,8 @@ int wxMessageDialog::ShowModal() // native message box (which probably doesn't use them because // Enter/Esc keys can be already used to dismiss the message box // using keyboard) - SetOKCancelLabels(_("OK"), _("Cancel")); + if ( GetCustomOKLabel().empty() && GetCustomCancelLabel().empty() ) + SetOKCancelLabels(_("OK"), _("Cancel")); } #endif // wxUSE_INTL @@ -507,17 +523,30 @@ int wxMessageDialog::ShowModal() } } + if ( wxStyle & wxHELP ) + { + msStyle |= MB_HELP; + } + // set the icon style - if (wxStyle & wxICON_EXCLAMATION) - msStyle |= MB_ICONEXCLAMATION; - else if (wxStyle & wxICON_HAND) - msStyle |= MB_ICONHAND; - else if (wxStyle & wxICON_INFORMATION) - msStyle |= MB_ICONINFORMATION; - else if (wxStyle & wxICON_QUESTION) - msStyle |= MB_ICONQUESTION; - else if (!(wxStyle & wxICON_NONE)) - msStyle |= wxStyle & wxYES ? MB_ICONQUESTION : MB_ICONINFORMATION; + 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; @@ -556,12 +585,287 @@ int wxMessageDialog::ShowModal() #endif // wxUSE_MSGBOX_HOOK // do show the dialog - int msAns = MessageBox(hWnd, message.wx_str(), m_caption.wx_str(), msStyle); + int msAns = MessageBox(hWnd, message.t_str(), m_caption.t_str(), msStyle); + + return MSWTranslateReturnCode(msAns); +} + +int wxMessageDialog::ShowModal() +{ + WX_TESTING_SHOW_MODAL_HOOK(); + +#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(wxT("unexpected ::MessageBox() return code")); + wxFAIL_MSG(wxT("unexpected return code")); // fall through case IDCANCEL: @@ -576,7 +880,11 @@ int wxMessageDialog::ShowModal() case IDNO: ans = wxID_NO; break; + case IDHELP: + ans = wxID_HELP; + break; } + return ans; }