]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/msgdlg.cpp
Ensure there is valid context for DrawRectangle
[wxWidgets.git] / src / msw / msgdlg.cpp
index e5e4eb07bbf91efd1b000ad5dc0e8140841275b7..edff0a0f5a55c7523c79fb55989e8e5014a2b2b3 100644 (file)
@@ -1,5 +1,5 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        msgdlg.cpp
+// Name:        src/msw/msgdlg.cpp
 // Purpose:     wxMessageDialog
 // Author:      Julian Smart
 // Modified by:
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
-#ifdef __GNUG__
-#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"
+#include "wx/testing.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()
+{
+    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<POINT *>(&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)
 {
-#ifdef __WXDEBUG__
-    // check for common programming errors
-    if ( (style & wxID_OK) == wxID_OK )
+    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,54 +455,417 @@ 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;
+    }
+
+    // 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 (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 )
+    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;
     else
         msStyle |= MB_TASKMODAL;
 
+    // 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()
+{
+    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<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();
+}
+
+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<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:
@@ -128,7 +880,12 @@ int wxMessageDialog::ShowModal()
         case IDNO:
             ans = wxID_NO;
             break;
+        case IDHELP:
+            ans = wxID_HELP;
+            break;
     }
+
     return ans;
 }
 
+#endif // wxUSE_MSGDLG