Merge branch 'safe-show-message'

Show message box from wxSafeShowMessage() in the other ports too, if
possible.

Currently this is done using wxMessageBox() if it can be sure that it's
safe to call and only MSW uses native function which is always safe to
call. Ideal would be to also use a native function under Mac, where it
should also be the case, but this doesn't seem to work for whichever
reason.

See https://github.com/wxWidgets/wxWidgets/pull/2270
This commit is contained in:
Vadim Zeitlin
2021-03-23 18:59:51 +01:00
34 changed files with 194 additions and 47 deletions

View File

@@ -622,6 +622,10 @@ public:
// there are none, will return NULL)
virtual wxWindow *GetTopWindow() const;
// convenient helper which is safe to use even if there is no wxApp at
// all, it will just return NULL in this case
static wxWindow *GetMainTopWindow();
// control the exit behaviour: by default, the program will exit the
// main loop (and so, usually, terminate) when the last top-level
// program window is deleted. Beware that if you disable this behaviour

View File

@@ -88,6 +88,16 @@ public:
// return true to suppress subsequent asserts, false to continue as before
virtual bool ShowAssertDialog(const wxString& msg) = 0;
// show the message safely to the user, i.e. show it in a message box if
// possible (even in a console application!) or return false if we can't do
// it (e.g. GUI is not initialized at all)
//
// note that this function can be called even when wxApp doesn't exist, as
// it's supposed to be always safe to call -- hence the name
//
// return true if the message box was shown, false if nothing was done
virtual bool SafeMessageBox(const wxString& text, const wxString& title) = 0;
// return true if fprintf(stderr) goes somewhere, false otherwise
virtual bool HasStderr() = 0;
@@ -208,6 +218,8 @@ public:
virtual bool ShowAssertDialog(const wxString& msg) wxOVERRIDE;
virtual bool HasStderr() wxOVERRIDE;
virtual bool SafeMessageBox(const wxString& text,
const wxString& title) wxOVERRIDE;
// the GetToolkitVersion for console application is always the same
wxPortId GetToolkitVersion(int *verMaj = NULL,
@@ -248,6 +260,13 @@ public:
virtual bool ShowAssertDialog(const wxString& msg) wxOVERRIDE;
virtual bool HasStderr() wxOVERRIDE;
// Win32 has its own implementation using native message box directly in
// the base class, don't override it.
#ifndef __WIN32__
virtual bool SafeMessageBox(const wxString& text,
const wxString& title) wxOVERRIDE;
#endif // !__WIN32__
virtual bool IsUsingUniversalWidgets() const wxOVERRIDE
{
#ifdef __WXUNIVERSAL__

View File

@@ -1459,7 +1459,7 @@ inline void wxLogNop() { }
// wxLogFatalError helper: show the (fatal) error to the user in a safe way,
// i.e. without using wxMessageBox() for example because it could crash
void WXDLLIMPEXP_BASE
bool WXDLLIMPEXP_BASE
wxSafeShowMessage(const wxString& title, const wxString& text);
// ----------------------------------------------------------------------------

View File

@@ -58,6 +58,13 @@ public:
// write text to the console, return true if ok or false on error
virtual bool WriteToStderr(const wxString& text) = 0;
// return the main application window or 0 if none
virtual WXHWND GetMainHWND() const = 0;
// implement this base class function for both console and GUI applications
virtual bool SafeMessageBox(const wxString& text,
const wxString& title) wxOVERRIDE;
protected:
#if wxUSE_THREADS
// implementation of WaitForThread() for the console applications which is

View File

@@ -30,6 +30,7 @@ public:
#endif // wxUSE_THREADS
virtual bool CanUseStderr() wxOVERRIDE { return true; }
virtual bool WriteToStderr(const wxString& text) wxOVERRIDE;
virtual WXHWND GetMainHWND() const wxOVERRIDE { return NULL; }
};
#if wxUSE_GUI
@@ -55,6 +56,7 @@ public:
virtual bool CanUseStderr() wxOVERRIDE;
virtual bool WriteToStderr(const wxString& text) wxOVERRIDE;
virtual WXHWND GetMainHWND() const wxOVERRIDE;
};
#elif defined(__WXGTK__)
@@ -85,6 +87,7 @@ public:
virtual bool CanUseStderr() { return false; }
virtual bool WriteToStderr(const wxString& WXUNUSED(text)) { return false; }
virtual WXHWND GetMainHWND() const wxOVERRIDE { return NULL; }
};
#elif defined(__WXQT__)
@@ -110,6 +113,7 @@ public:
virtual bool CanUseStderr() { return false; }
virtual bool WriteToStderr(const wxString&) { return false; }
virtual WXHWND GetMainHWND() const wxOVERRIDE { return NULL; }
};
#endif

View File

@@ -859,6 +859,18 @@ public:
*/
bool GetUseBestVisual() const;
/**
Returns a pointer to the top application window if any.
This function is safe to call even before creating, or after
destroying, the application object, as it simply returns @NULL if it
doesn't exist. Otherwise it's equivalent to calling
@c wxTheApp->GetTopWindow().
@since 3.1.5
*/
static wxWindow* GetMainTopWindow();
/**
Returns a pointer to the top window.

View File

@@ -133,5 +133,28 @@ public:
Returns @true to suppress subsequent asserts, @false to continue as before.
*/
virtual bool ShowAssertDialog(const wxString& msg) = 0;
/**
Shows a message box with the given text and title if possible.
In some ports, e.g. wxMSW, a message box will always be shown, while in
the other ones it will be only done if the GUI is available (e.g. X11
display was successfully opened for X11-based ports) and the function
simply returns @false without doing anything otherwise.
This function is safe in the sense that it can always be called, even
before creating wxApp, similarly to wxSafeShowMessage() which is
implemented by calling this function and then logging the message to
standard error stream if it returned @false.
@since 3.1.5
@param text
The text to show to the user.
@param title
The title of the message box shown to the user.
@return @true if the message box was shown or @false otherwise.
*/
virtual bool SafeMessageBox(const wxString& text, const wxString& title) = 0;
};

View File

@@ -1268,12 +1268,17 @@ public:
message string.
@param text
The text to show to the user.
@return
@true If a message box was actually shown or @false if the message was
logged to the console because there is no safe to show it currently
(the return value is only available since wxWidgets 3.1.5, the function
doesn't return anything in the previous versions).
@see wxLogFatalError()
@header{wx/log.h}
*/
void wxSafeShowMessage(const wxString& title, const wxString& text);
bool wxSafeShowMessage(const wxString& title, const wxString& text);
/**
Returns the error code from the last system call. This function uses

View File

@@ -358,7 +358,7 @@ wxAppTraits *wxAppConsoleBase::GetTraitsIfExists()
wxAppTraits& wxAppConsoleBase::GetValidTraits()
{
static wxConsoleAppTraits s_traitsConsole;
wxAppTraits* const traits = (wxTheApp ? wxTheApp->GetTraits() : NULL);
wxAppTraits* const traits = GetTraitsIfExists();
return *(traits ? traits : &s_traitsConsole);
}
@@ -944,6 +944,14 @@ bool wxConsoleAppTraitsBase::HasStderr()
return true;
}
bool wxConsoleAppTraitsBase::SafeMessageBox(const wxString& WXUNUSED(text),
const wxString& WXUNUSED(title))
{
// console applications don't show message boxes by default, although this
// can be done in platform-specific cases
return false;
}
// ----------------------------------------------------------------------------
// wxAppTraits
// ----------------------------------------------------------------------------

View File

@@ -25,6 +25,7 @@
#include "wx/window.h"
#include "wx/bitmap.h"
#include "wx/log.h"
#include "wx/module.h"
#include "wx/msgdlg.h"
#include "wx/confbase.h"
#include "wx/utils.h"
@@ -189,6 +190,14 @@ wxWindow* wxAppBase::GetTopWindow() const
return window;
}
/* static */
wxWindow* wxAppBase::GetMainTopWindow()
{
const wxAppBase* const app = static_cast<wxAppBase*>(GetInstance());
return app ? app->GetTopWindow() : NULL;
}
wxVideoMode wxAppBase::GetDisplayMode() const
{
return wxVideoMode();
@@ -576,3 +585,21 @@ bool wxGUIAppTraitsBase::HasStderr()
#endif
}
#ifndef __WIN32__
bool wxGUIAppTraitsBase::SafeMessageBox(const wxString& text,
const wxString& title)
{
// The modules are initialized only after a successful call to
// wxApp::Initialize() in wxEntryStart, so it can be used as a proxy for
// GUI availability (note that the mere existence of wxTheApp is not enough
// for this).
if ( !wxModule::AreInitialized() )
return false;
wxMessageBox(text, title, wxOK | wxICON_ERROR);
return true;
}
#endif // !__WIN32__

View File

@@ -1430,9 +1430,8 @@ wxString wxCmdLineParser::GetUsageString() const
count = namesOptions.size();
// get option names & descriptions for standard options, if any:
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxString stdDesc;
if ( traits )
if ( wxAppTraits *traits = wxApp::GetTraitsIfExists() )
stdDesc = traits->GetStandardCmdLineOptions(namesOptions, descOptions);
// now construct the detailed help message

View File

@@ -98,7 +98,7 @@ wxConfigBase *wxConfigBase::Set(wxConfigBase *pConfig)
wxConfigBase *wxConfigBase::Create()
{
if ( ms_bAutoCreate && ms_pConfig == NULL ) {
wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits * const traits = wxApp::GetTraitsIfExists();
wxCHECK_MSG( traits, NULL, wxT("create wxApp before calling this") );
ms_pConfig = traits->CreateConfig();

View File

@@ -183,8 +183,8 @@ wxDialogBase::GetParentForModalDialog(wxWindow *parent, long style) const
wxGetTopLevelParent(wxGetActiveWindow()));
// and finally the application main window
if ( !parent && wxTheApp )
parent = CheckIfCanBeUsedAsParent(wxTheApp->GetTopWindow());
if ( !parent )
parent = CheckIfCanBeUsedAsParent(wxApp::GetMainTopWindow());
return parent;
}

View File

@@ -413,7 +413,7 @@ wxFontMapperBase *wxFontMapperBase::Get()
{
if ( !sm_instance )
{
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits *traits = wxApp::GetTraitsIfExists();
if ( traits )
{
sm_instance = traits->CreateFontMapper();

View File

@@ -264,7 +264,7 @@ wxIcon wxIconBundle::GetIcon(const wxSize& size, int flags) const
sysY = 0;
if ( flags & FALLBACK_SYSTEM )
{
wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
wxWindow* win = wxApp::GetMainTopWindow();
sysX = wxSystemSettings::GetMetric(wxSYS_ICON_X, win);
sysY = wxSystemSettings::GetMetric(wxSYS_ICON_Y, win);
}

View File

@@ -180,14 +180,17 @@ private:
// helper global functions
// ----------------------------------------------------------------------------
void wxSafeShowMessage(const wxString& title, const wxString& text)
bool wxSafeShowMessage(const wxString& title, const wxString& text)
{
#ifdef __WINDOWS__
::MessageBox(NULL, text.t_str(), title.t_str(), MB_OK | MB_ICONSTOP);
#else
wxFprintf(stderr, wxS("%s: %s\n"), title.c_str(), text.c_str());
fflush(stderr);
#endif
if ( !wxApp::GetValidTraits().SafeMessageBox(text, title) )
{
wxFprintf(stderr, wxS("%s: %s\n"), title.c_str(), text.c_str());
fflush(stderr);
return false;
}
// Message box actually shown.
return true;
}
// ----------------------------------------------------------------------------
@@ -892,7 +895,7 @@ void wxLogStderr::DoLogText(const wxString& msg)
// simply lost
if ( m_fp == stderr )
{
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits *traits = wxApp::GetTraitsIfExists();
if ( traits && !traits->HasStderr() )
{
wxMessageOutputDebug().Output(msg + wxS('\n'));

View File

@@ -105,7 +105,7 @@ void wxMessageOutputBest::Output(const wxString& str)
{
#ifdef __WINDOWS__
// decide whether to use console output or not
wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits * const traits = wxApp::GetTraitsIfExists();
const bool hasStderr = traits ? traits->CanUseStderr() : false;
if ( !(m_flags & wxMSGOUT_PREFER_MSGBOX) )

View File

@@ -174,7 +174,7 @@ void wxPlatformInfo::InitForCurrentPlatform()
m_initializedForCurrentPlatform = true;
// autodetect all informations
const wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
const wxAppTraits * const traits = wxApp::GetTraitsIfExists();
if ( !traits )
{
wxFAIL_MSG( wxT("failed to initialize wxPlatformInfo") );

View File

@@ -65,8 +65,7 @@ private:
void DoInit()
{
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
if ( traits )
if ( wxAppTraits *traits = wxApp::GetTraitsIfExists() )
{
// ask the traits object to create a renderer for us
reset(traits->CreateRenderer());

View File

@@ -104,7 +104,7 @@ float wxSizerFlags::DoGetDefaultBorderInPx()
// We also have to use the DPI for the monitor showing the top window here
// as we don't have any associated window -- but, again, without changes
// in the API, there is nothing we can do about this.
const wxWindow* const win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* const win = wxApp::GetMainTopWindow();
static wxPrivate::DpiDependentValue<float> s_defaultBorderInPx;
if ( s_defaultBorderInPx.HasChanged(win) )
{

View File

@@ -55,7 +55,7 @@ static wxStandardPathsDefault gs_stdPaths;
/* static */
wxStandardPaths& wxStandardPathsBase::Get()
{
wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits * const traits = wxApp::GetTraitsIfExists();
wxCHECK_MSG( traits, gs_stdPaths, wxT("create wxApp before calling this") );
return traits->GetStandardPaths();

View File

@@ -54,7 +54,7 @@ wxTimer::~wxTimer()
void wxTimer::Init()
{
wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits * const traits = wxApp::GetTraitsIfExists();
m_impl = traits ? traits->CreateTimerImpl(this) : NULL;
if ( !m_impl )
{

View File

@@ -81,10 +81,10 @@ wxString wxAboutDialogInfo::GetDescriptionAndCredits() const
wxIcon wxAboutDialogInfo::GetIcon() const
{
wxIcon icon = m_icon;
if ( !icon.IsOk() && wxTheApp )
if ( !icon.IsOk() )
{
const wxTopLevelWindow * const
tlw = wxDynamicCast(wxTheApp->GetTopWindow(), wxTopLevelWindow);
tlw = wxDynamicCast(wxApp::GetMainTopWindow(), wxTopLevelWindow);
if ( tlw )
icon = tlw->GetIcon();
}

View File

@@ -691,8 +691,8 @@ TAG_HANDLER_BEGIN(IMG, "IMG,MAP,AREA")
#if defined(__WXOSX_COCOA__)
// Try to find a 2x resolution image with @2x appended before the file extension.
wxWindow* win = m_WParser->GetWindowInterface() ? m_WParser->GetWindowInterface()->GetHTMLWindow() : NULL;
if (!win && wxTheApp)
win = wxTheApp->GetTopWindow();
if (!win)
win = wxApp::GetMainTopWindow();
if (win && win->GetContentScaleFactor() > 1.0)
{
if (tmp.Find('.') != wxNOT_FOUND)

View File

@@ -603,6 +603,12 @@ bool wxGUIAppTraits::WriteToStderr(const wxString& WXUNUSED(text))
#endif // wxUSE_DYNLIB_CLASS/!wxUSE_DYNLIB_CLASS
WXHWND wxGUIAppTraits::GetMainHWND() const
{
const wxWindow* const w = wxApp::GetMainTopWindow();
return w ? w->GetHWND() : NULL;
}
// ===========================================================================
// wxApp implementation
// ===========================================================================

View File

@@ -326,7 +326,7 @@ wxBitmap wxWindowsArtProvider::CreateBitmap(const wxArtID& id,
/*static*/
wxSize wxArtProvider::GetNativeSizeHint(const wxArtClient& client)
{
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
if ( client == wxART_TOOLBAR )
{
return wxWindow::FromDIP(wxSize(24, 24), win);

View File

@@ -39,6 +39,35 @@
// wxAppTraits implementation
// ============================================================================
bool wxAppTraits::SafeMessageBox(const wxString& text,
const wxString& title)
{
const HWND hwndParent = GetMainHWND();
int flags = MB_OK | MB_ICONSTOP;
// Using MB_TASKMODAL with valid parent doesn't work well because it
// prevents the typical behaviour of modal message boxes, e.g. the message
// box doesn't come up to front when the parent is clicked. But if we don't
// have any parent anyhow, we can just as well use it, as we don't lose
// anything and it has a useful side effect of disabling any existing TLWs
// if there are any.
//
// Note that we also might have chosen to always use MB_TASKMODAL and NULL
// parent. This would have the advantage of always disabling all the window
// which, but at the cost of the behaviour mentioned above and other
// related problems, e.g. showing ugly default icon in Alt-Tab list and an
// extra taskbar button for the message box, so we don't do this, although
// perhaps we still should, at least in case when there is more than one
// TLW (but we can't check for this easily as this is non-GUI code and
// wxTopLevelWindows is not accessible from it).
if ( !hwndParent )
flags |= MB_TASKMODAL;
::MessageBox(hwndParent, text.t_str(), title.t_str(), flags);
return true;
}
#if wxUSE_THREADS
WXDWORD wxAppTraits::DoSimpleWaitForThread(WXHANDLE hThread)
{

View File

@@ -109,13 +109,13 @@ public:
wxCoord wxCursorRefData::GetStandardWidth()
{
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
return wxSystemSettings::GetMetric(wxSYS_CURSOR_X, win);
}
wxCoord wxCursorRefData::GetStandardHeight()
{
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
return wxSystemSettings::GetMetric(wxSYS_CURSOR_Y, win);
}

View File

@@ -471,7 +471,7 @@ bool wxICOFileHandler::LoadIcon(wxIcon *icon,
else
#endif
// were we asked for a large icon?
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
if ( desiredWidth == wxGetSystemMetrics(SM_CXICON, win) &&
desiredHeight == wxGetSystemMetrics(SM_CYICON, win) )
{
@@ -666,7 +666,7 @@ wxSize wxGetHiconSize(HICON hicon)
if ( !size.x )
{
// use default icon size on this hardware
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
size.x = wxGetSystemMetrics(SM_CXICON, win);
size.y = wxGetSystemMetrics(SM_CYICON, win);
}

View File

@@ -69,8 +69,8 @@ HTMLHELP GetHtmlHelpFunction()
// fall back to the top level app window and then the desktop if it's NULL
static HWND GetSuitableHWND(wxWindow *win)
{
if ( !win && wxTheApp )
win = wxTheApp->GetTopWindow();
if ( !win )
win = wxApp::GetMainTopWindow();
return win ? GetHwndOf(win) : ::GetDesktopWindow();
}

View File

@@ -393,7 +393,7 @@ void wxMessageDialog::AdjustButtonLabels()
/* static */
wxFont wxMessageDialog::GetMessageFont()
{
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
const wxNativeFontInfo
info(wxMSWImpl::GetNonClientMetrics(win).lfMessageFont, win);

View File

@@ -179,7 +179,7 @@ wxFont wxSystemSettingsNative::GetFont(wxSystemFont index)
// for most (simple) controls, e.g. buttons and such but other
// controls may prefer to use lfStatusFont or lfCaptionFont if it
// is more appropriate for them
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
const wxNativeFontInfo
info(wxMSWImpl::GetNonClientMetrics(win).lfMessageFont, win);
@@ -337,7 +337,7 @@ extern wxFont wxGetCCDefaultFont()
// font which is also used for the icon titles and not the stock default
// GUI font
LOGFONT lf;
const wxWindow* win = wxTheApp ? wxTheApp->GetTopWindow() : NULL;
const wxWindow* win = wxApp::GetMainTopWindow();
if ( wxSystemParametersInfo
(
SPI_GETICONTITLELOGFONT,

View File

@@ -1018,7 +1018,7 @@ long wxExecute(const wxString& cmd, int flags, wxProcess *handler,
return pi.dwProcessId;
}
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
wxAppTraits *traits = wxApp::GetTraitsIfExists();
wxCHECK_MSG( traits, -1, wxT("no wxAppTraits in wxExecute()?") );
void *cookie = NULL;

View File

@@ -4236,7 +4236,7 @@ bool wxWindowMSW::HandleEndSession(bool endSession, long logOff)
return false;
// only send once
if ( (this != wxTheApp->GetTopWindow()) )
if ( this != wxApp::GetMainTopWindow() )
return false;
wxCloseEvent event(wxEVT_END_SESSION, wxID_ANY);
@@ -4773,10 +4773,11 @@ static wxSize GetWindowDPI(HWND hwnd)
}
/*extern*/
int wxGetSystemMetrics(int nIndex, const wxWindow* win)
int wxGetSystemMetrics(int nIndex, const wxWindow* window)
{
#if wxUSE_DYNLIB_CLASS
const wxWindow* window = (!win && wxTheApp) ? wxTheApp->GetTopWindow() : win;
if ( !window )
window = wxApp::GetMainTopWindow();
if ( window )
{
@@ -4798,14 +4799,14 @@ int wxGetSystemMetrics(int nIndex, const wxWindow* win)
}
}
#else
wxUnusedVar(win);
wxUnusedVar(window);
#endif // wxUSE_DYNLIB_CLASS
return ::GetSystemMetrics(nIndex);
}
/*extern*/
bool wxSystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, const wxWindow* win)
bool wxSystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, const wxWindow* window)
{
// Note that we can't use SystemParametersInfoForDpi() in non-Unicode build
// because it always works with wide strings and we'd have to check for all
@@ -4813,7 +4814,8 @@ bool wxSystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWi
// for them, and convert the returned value to ANSI after the call. Instead
// of doing all this, just don't use it at all in the deprecated ANSI build.
#if wxUSE_DYNLIB_CLASS && wxUSE_UNICODE
const wxWindow* window = (!win && wxTheApp) ? wxTheApp->GetTopWindow() : win;
if ( !window )
window = wxApp::GetMainTopWindow();
if ( window )
{
@@ -4838,7 +4840,7 @@ bool wxSystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWi
}
}
#else
wxUnusedVar(win);
wxUnusedVar(window);
#endif // wxUSE_DYNLIB_CLASS
return ::SystemParametersInfo(uiAction, uiParam, pvParam, fWinIni) == TRUE;