#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__
#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/modalhook.h"
#if wxUSE_MSGBOX_HOOK
#include "wx/fontutil.h"
#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
// 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, _T("STATIC"), NULL);
+ HWND hwndStatic = ::FindWindowEx(GetHwnd(), NULL, wxT("STATIC"), NULL);
if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON )
- hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, _T("STATIC"), NULL);
+ hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), NULL);
if ( !hwndStatic )
{
// 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::iterator i = text.end() - 1; i != text.begin(); --i )
+ for ( wxString::reverse_iterator i = text.rbegin(); i != text.rend(); ++i )
{
if ( *i != '\n' )
{
- text.erase(i + 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;
}
}
// do create the new control
HWND hwndEdit = ::CreateWindow
(
- _T("EDIT"),
- wxTextBuffer::Translate(text).wx_str(),
+ 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,
- wxhInstance,
+ wxGetInstance(),
NULL
);
if ( widthNeeded > wBtnNew )
wBtnNew = widthNeeded;
- ::SetWindowText(hwndBtn, label.wx_str());
+ ::SetWindowText(hwndBtn, label.t_str());
}
if ( wBtnNew <= wBtnOld )
return wxNativeFontInfo(ncm.lfMessageFont);
}
-int wxMessageDialog::ShowModal()
+int wxMessageDialog::ShowMessageBox()
{
if ( !wxTheApp->GetTopWindow() )
{
}
// 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;
const long wxStyle = GetMessageDialogStyle();
}
}
- 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;
+ if ( wxStyle & wxHELP )
+ {
+ msStyle |= MB_HELP;
+ }
+
+ // 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;
#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_HOOK_MODAL_DIALOG();
+
+#ifdef wxHAS_MSW_TASKDIALOG
+ if ( HasNativeTaskDialog() )
+ {
+ TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
+ wxCHECK_MSG( taskDialogIndirect, wxID_CANCEL, wxS("no task dialog?") );
+
+ WinStruct<TASKDIALOGCONFIG> 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();
+}
+
+long wxMessageDialog::GetEffectiveIcon() const
+{
+ // only use the auth needed icon if available, otherwise fallback to the default logic
+ if ( (m_dialogStyle & wxICON_AUTH_NEEDED) &&
+ wxMSWMessageDialog::HasNativeTaskDialog() )
+ {
+ return wxICON_AUTH_NEEDED;
+ }
+
+ return wxMessageDialogBase::GetEffectiveIcon();
+}
+
+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;
+
+ case wxICON_AUTH_NEEDED:
+ tdc.pszMainIcon = TD_SHIELD_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<TaskDialogIndirect_t>(-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:
case IDNO:
ans = wxID_NO;
break;
+ case IDHELP:
+ ans = wxID_HELP;
+ break;
}
+
return ans;
}