improve wxMessageOutputBest console output under Windows (closes 9146)

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@53730 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2008-05-23 23:28:13 +00:00
parent e9863f4ea1
commit 784ee7d511
7 changed files with 363 additions and 59 deletions

View File

@ -100,21 +100,6 @@ private:
#pragma warning (default:4275)
#endif
// ----------------------------------------------------------------------------
// implementation showing the message to the user in "best" possible way: uses
// native message box if available (currently only under Windows) and stderr
// otherwise; unlike wxMessageOutputMessageBox this class is always safe to use
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_BASE wxMessageOutputBest : public wxMessageOutput
{
public:
wxMessageOutputBest() { }
protected:
virtual void Output(const wxString& str);
};
// ----------------------------------------------------------------------------
// implementation which sends output to stderr
// ----------------------------------------------------------------------------
@ -126,6 +111,34 @@ public:
protected:
virtual void Output(const wxString& str);
// return the string with "\n" appended if it doesn't already terminate
// with it (in which case it's returned unchanged)
wxString AppendLineFeedIfNeeded(const wxString& str);
};
// ----------------------------------------------------------------------------
// implementation showing the message to the user in "best" possible way:
// uses stderr or message box if available according to the flag given to ctor.
// ----------------------------------------------------------------------------
enum wxMessageOutputFlags
{
wxMSGOUT_PREFER_STDERR = 0, // use stderr if available (this is the default)
wxMSGOUT_PREFER_MSGBOX = 1 // always use message box if available
};
class WXDLLIMPEXP_BASE wxMessageOutputBest : public wxMessageOutputStderr
{
public:
wxMessageOutputBest(wxMessageOutputFlags flags = wxMSGOUT_PREFER_STDERR)
: m_flags(flags) { }
protected:
virtual void Output(const wxString& str);
private:
wxMessageOutputFlags m_flags;
};
// ----------------------------------------------------------------------------
@ -149,7 +162,7 @@ protected:
// implementation using the native way of outputting debug messages
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_BASE wxMessageOutputDebug : public wxMessageOutput
class WXDLLIMPEXP_BASE wxMessageOutputDebug : public wxMessageOutputStderr
{
public:
wxMessageOutputDebug() { }
@ -171,5 +184,4 @@ protected:
virtual void Output(const wxString& str);
};
#endif
// _WX_MSGOUT_H_
#endif // _WX_MSGOUT_H_

View File

@ -65,6 +65,23 @@ public:
virtual GSocketManager *GetSocketManager() { return ms_manager; }
#endif // wxUSE_SOCKETS
#ifndef __WXWINCE__
// console helpers
// ---------------
// this method can be overridden by a derived class to always return true
// or false to force [not] using the console for output to stderr
//
// by default console applications always return true from here while the
// GUI ones only return true if they're being run from console and there is
// no other activity happening in this console
virtual bool CanUseStderr() = 0;
// write text to the console, return true if ok or false on error
virtual bool WriteToStderr(const wxString& text) = 0;
#endif // !__WXWINCE__
protected:
// implementation of WaitForThread() for the console applications which is
// also used by the GUI code if it doesn't [yet|already} dispatch events

View File

@ -28,6 +28,10 @@ public:
#endif
virtual bool DoMessageFromThreadWait();
virtual WXDWORD WaitForThread(WXHANDLE hThread);
#ifndef __WXWINCE__
virtual bool CanUseStderr() { return true; }
virtual bool WriteToStderr(const wxString& text);
#endif // !__WXWINCE__
};
#if wxUSE_GUI
@ -45,9 +49,13 @@ public:
virtual bool DoMessageFromThreadWait();
virtual wxPortId GetToolkitVersion(int *majVer = NULL, int *minVer = NULL) const;
virtual WXDWORD WaitForThread(WXHANDLE hThread);
#ifndef __WXWINCE__
virtual bool CanUseStderr();
virtual bool WriteToStderr(const wxString& text);
#endif // !__WXWINCE__
};
#endif // wxUSE_GUI
#endif // _WX_MSW_APPTRAIT_H_

View File

@ -437,7 +437,8 @@ wxMessageOutput *wxGUIAppTraitsBase::CreateMessageOutput()
// is (according to common practice):
// - console apps: to stderr (on any platform)
// - GUI apps: stderr on Unix platforms (!)
// message box under Windows and others
// stderr if available and message box otherwise on others
// (currently stderr only Windows if app running from console)
#ifdef __UNIX__
return new wxMessageOutputStderr;
#else // !__UNIX__
@ -445,7 +446,7 @@ wxMessageOutput *wxGUIAppTraitsBase::CreateMessageOutput()
#ifdef __WXMOTIF__
return new wxMessageOutputLog;
#elif wxUSE_MSGDLG
return new wxMessageOutputMessageBox;
return new wxMessageOutputBest(wxMSGOUT_PREFER_STDERR);
#else
return new wxMessageOutputStderr;
#endif

View File

@ -105,48 +105,42 @@ void wxMessageOutput::DoPrintfUtf8(const char *format, ...)
// wxMessageOutputBest
// ----------------------------------------------------------------------------
#ifdef __WINDOWS__
// check if we're running in a console under Windows
static inline bool IsInConsole()
{
#ifdef __WXWINCE__
return false;
#else // !__WXWINCE__
HANDLE hStdErr = ::GetStdHandle(STD_ERROR_HANDLE);
return hStdErr && hStdErr != INVALID_HANDLE_VALUE;
#endif // __WXWINCE__/!__WXWINCE__
}
#endif // __WINDOWS__
void wxMessageOutputBest::Output(const wxString& str)
{
#ifdef __WINDOWS__
if ( !IsInConsole() )
{
::MessageBox(NULL, str.wx_str(), _T("wxWidgets"),
MB_ICONINFORMATION | MB_OK);
}
else
#endif // __WINDOWS__/!__WINDOWS__
{
const wxWX2MBbuf buf = str.mb_str();
// decide whether to use console output or not
wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
const bool hasStderr = traits ? traits->CanUseStderr() : false;
if ( buf )
fprintf(stderr, "%s", (const char*) buf);
else // print at least something
fprintf(stderr, "%s", (const char*) str.ToAscii());
if ( !(m_flags & wxMSGOUT_PREFER_MSGBOX) )
{
if ( hasStderr && traits->WriteToStderr(AppendLineFeedIfNeeded(str)) )
return;
}
::MessageBox(NULL, str.wx_str(), NULL, MB_ICONINFORMATION | MB_OK);
#else // !__WINDOWS__
// TODO: use the native message box for the other ports too
wxMessageOutputStderr::Output(str);
#endif // __WINDOWS__/!__WINDOWS__
}
// ----------------------------------------------------------------------------
// wxMessageOutputStderr
// ----------------------------------------------------------------------------
wxString wxMessageOutputStderr::AppendLineFeedIfNeeded(const wxString& str)
{
wxString strLF(str);
if ( strLF.empty() || *strLF.rbegin() != '\n' )
strLF += '\n';
return strLF;
}
void wxMessageOutputStderr::Output(const wxString& str)
{
const wxWX2MBbuf buf = str.mb_str();
const wxWX2MBbuf buf = AppendLineFeedIfNeeded(str).mb_str();
if ( buf )
fprintf(stderr, "%s", (const char*) buf);
@ -160,17 +154,14 @@ void wxMessageOutputStderr::Output(const wxString& str)
void wxMessageOutputDebug::Output(const wxString& str)
{
wxString out(str);
#if defined(__WXMSW__) && !defined(__WXMICROWIN__)
wxString out(AppendLineFeedIfNeeded(str));
out.Replace(wxT("\t"), wxT(" "));
out.Replace(wxT("\n"), wxT("\r\n"));
::OutputDebugString(out.wx_str());
#else
wxFputs( out , stderr ) ;
if ( out.Right(1) != wxT("\n") )
wxFputs( wxT("\n") , stderr ) ;
fflush( stderr ) ;
// TODO: use native debug output function for the other ports too
wxMessageOutputStderr::Output(str);
#endif // platform
}
@ -204,9 +195,7 @@ void wxMessageOutputMessageBox::Output(const wxString& str)
out.Replace(wxT("\t"), wxT(" "));
#endif
wxString title;
if ( wxTheApp )
title.Printf(_("%s message"), wxTheApp->GetAppDisplayName().c_str());
wxString title = wxTheApp ? wxTheApp->GetAppDisplayName() : wxT("wxWidgets");
::wxMessageBox(out, title);
}

View File

@ -282,6 +282,279 @@ wxEventLoopBase* wxGUIAppTraits::CreateEventLoop()
return new wxEventLoop;
}
// ---------------------------------------------------------------------------
// Stuff for using console from the GUI applications
// ---------------------------------------------------------------------------
#ifndef __WXWINCE__
#include <wx/dynlib.h>
namespace
{
/*
Helper class to manipulate console from a GUI app.
Notice that console output is available in the GUI app only if:
- AttachConsole() returns TRUE (which means it never works under pre-XP)
- we have a valid STD_ERROR_HANDLE
- command history hasn't been changed since our startup
To check if all these conditions are verified, you need to simple call
IsOkToUse(). It will check the first two conditions above the first time it
is called (and if this fails, the subsequent calls will return immediately)
and also recheck the last one every time it is called.
*/
class wxConsoleStderr
{
public:
// default ctor does nothing, call Init() before using this class
wxConsoleStderr()
{
m_hStderr = INVALID_HANDLE_VALUE;
m_historyLen =
m_dataLen =
m_dataLine = 0;
m_ok = -1;
}
~wxConsoleStderr()
{
if ( m_hStderr != INVALID_HANDLE_VALUE )
{
if ( !::FreeConsole() )
{
wxLogLastError(_T("FreeConsole"));
}
}
}
// return true if we were successfully initialized and there had been no
// console activity which would interfere with our output since then
bool IsOkToUse() const
{
if ( m_ok == -1 )
{
wxConsoleStderr * const self = wx_const_cast(wxConsoleStderr *, this);
self->m_ok = self->DoInit();
// no need to call IsHistoryUnchanged() as we just initialized
// m_history anyhow
return m_ok == 1;
}
return m_ok && IsHistoryUnchanged();
}
// output the provided text on the console, return true if ok
bool Write(const wxString& text);
private:
// called by Init() once only to do the real initialization
bool DoInit();
// retrieve the command line history into the provided buffer and return
// its length
int GetCommandHistory(wxWxCharBuffer& buf) const;
// check if the console history has changed
bool IsHistoryUnchanged() const;
int m_ok; // initially -1, set to true or false by Init()
wxDynamicLibrary m_dllKernel32;
HANDLE m_hStderr; // console handle, if it's valid we must call
// FreeConsole() (even if m_ok != 1)
wxWxCharBuffer m_history; // command history on startup
int m_historyLen; // length command history buffer
wxCharBuffer m_data; // data between empty line and cursor position
int m_dataLen; // length data buffer
int m_dataLine; // line offset
typedef DWORD (WINAPI *GetConsoleCommandHistory_t)(LPTSTR sCommands,
DWORD nBufferLength,
LPCTSTR sExeName);
typedef DWORD (WINAPI *GetConsoleCommandHistoryLength_t)(LPCTSTR sExeName);
GetConsoleCommandHistory_t m_pfnGetConsoleCommandHistory;
GetConsoleCommandHistoryLength_t m_pfnGetConsoleCommandHistoryLength;
DECLARE_NO_COPY_CLASS(wxConsoleStderr)
};
bool wxConsoleStderr::DoInit()
{
HANDLE hStderr = ::GetStdHandle(STD_ERROR_HANDLE);
if ( hStderr == INVALID_HANDLE_VALUE || !hStderr )
return false;
if ( !m_dllKernel32.Load(_T("kernel32.dll")) )
return false;
typedef BOOL (WINAPI *AttachConsole_t)(DWORD dwProcessId);
AttachConsole_t wxDL_INIT_FUNC(pfn, AttachConsole, m_dllKernel32);
if ( !pfnAttachConsole || !pfnAttachConsole(ATTACH_PARENT_PROCESS) )
return false;
// console attached, set m_hStderr now to ensure that we free it in the
// dtor
m_hStderr = hStderr;
wxDL_INIT_FUNC_AW(m_pfn, GetConsoleCommandHistory, m_dllKernel32);
if ( !m_pfnGetConsoleCommandHistory )
return false;
wxDL_INIT_FUNC_AW(m_pfn, GetConsoleCommandHistoryLength, m_dllKernel32);
if ( !m_pfnGetConsoleCommandHistoryLength )
return false;
// remember the current command history to be able to compare with it later
// in IsHistoryUnchanged()
m_historyLen = GetCommandHistory(m_history);
if ( !m_history )
return false;
// now find the first blank line above the current position
CONSOLE_SCREEN_BUFFER_INFO csbi;
if ( !::GetConsoleScreenBufferInfo(m_hStderr, &csbi) )
{
wxLogLastError(_T("GetConsoleScreenBufferInfo"));
return false;
}
COORD pos;
pos.X = 0;
pos.Y = csbi.dwCursorPosition.Y + 1;
// we decide that a line is empty if first 4 characters are spaces
DWORD ret;
char buf[4];
do
{
pos.Y--;
if ( !::ReadConsoleOutputCharacterA(m_hStderr, buf, WXSIZEOF(buf),
pos, &ret) )
{
wxLogLastError(_T("ReadConsoleOutputCharacterA"));
return false;
}
} while ( wxStrncmp(" ", buf, WXSIZEOF(buf)) != 0 );
// calculate line offset and length of data
m_dataLine = csbi.dwCursorPosition.Y - pos.Y;
m_dataLen = m_dataLine*csbi.dwMaximumWindowSize.X + csbi.dwCursorPosition.X;
if ( m_dataLen > 0 )
{
m_data.extend(m_dataLen);
if ( !::ReadConsoleOutputCharacterA(m_hStderr, m_data.data(), m_dataLen,
pos, &ret) )
{
wxLogLastError(_T("ReadConsoleOutputCharacterA"));
return false;
}
}
return true;
}
int wxConsoleStderr::GetCommandHistory(wxWxCharBuffer& buf) const
{
// these functions are internal and may only be called by cmd.exe
static const wxChar *CMD_EXE = _T("cmd.exe");
const int len = m_pfnGetConsoleCommandHistoryLength(CMD_EXE);
if ( len )
{
buf.extend(len);
const int len2 = m_pfnGetConsoleCommandHistory(buf.data(), len, CMD_EXE);
wxASSERT_MSG( len2 == len, _T("failed getting history?") );
}
return len;
}
bool wxConsoleStderr::IsHistoryUnchanged() const
{
wxASSERT_MSG( m_ok == 1, _T("shouldn't be called if not initialized") );
// get (possibly changed) command history
wxWxCharBuffer history;
const int historyLen = GetCommandHistory(history);
// and compare it with the original one
return historyLen == m_historyLen && history &&
memcmp(m_history, history, historyLen) == 0;
}
bool wxConsoleStderr::Write(const wxString& text)
{
wxASSERT_MSG( m_hStderr != INVALID_HANDLE_VALUE,
_T("should only be called if Init() returned true") );
// get current position
CONSOLE_SCREEN_BUFFER_INFO csbi;
if ( !::GetConsoleScreenBufferInfo(m_hStderr, &csbi) )
{
wxLogLastError(_T("GetConsoleScreenBufferInfo"));
return false;
}
// and calculate new position (where is empty line)
csbi.dwCursorPosition.X = 0;
csbi.dwCursorPosition.Y -= m_dataLine;
if ( !::SetConsoleCursorPosition(m_hStderr, csbi.dwCursorPosition) )
{
wxLogLastError(_T("SetConsoleCursorPosition"));
return false;
}
DWORD ret;
if ( !::FillConsoleOutputCharacter(m_hStderr, _T(' '), m_dataLen,
csbi.dwCursorPosition, &ret) )
{
wxLogLastError(_T("FillConsoleOutputCharacter"));
return false;
}
if ( !::WriteConsole(m_hStderr, text.wx_str(), text.length(), &ret, NULL) )
{
wxLogLastError(_T("WriteConsole"));
return false;
}
WriteConsoleA(m_hStderr, m_data, m_dataLen, &ret, 0);
return true;
}
wxConsoleStderr s_consoleStderr;
} // anonymous namespace
bool wxGUIAppTraits::CanUseStderr()
{
return s_consoleStderr.IsOkToUse();
}
bool wxGUIAppTraits::WriteToStderr(const wxString& text)
{
return s_consoleStderr.IsOkToUse() && s_consoleStderr.Write(text);
}
#endif // !__WXWINCE__
// ===========================================================================
// wxApp implementation
// ===========================================================================

View File

@ -108,3 +108,7 @@ WXDWORD wxConsoleAppTraits::WaitForThread(WXHANDLE hThread)
return DoSimpleWaitForThread(hThread);
}
bool wxConsoleAppTraits::WriteToStderr(const wxString& text)
{
return wxFprintf(stderr, "%s", text) != -1;
}