1
0
Fork 0

Message box support.

This allows us to display a platform-native dialog for problems
which occur early in startup (before we can show a PUI/Canvas dialog).

In particular this improves feedback where FG_HOME, FG_DATA or
aircraft selection is wrong, all of which happen very early in startup.
This commit is contained in:
James Turner 2013-11-06 15:49:58 -08:00
parent 7e90c8aa2b
commit 136cd6ac51
10 changed files with 341 additions and 75 deletions

View file

@ -21,6 +21,7 @@ set(SOURCES
FileDialog.cxx
PUIFileDialog.cxx
MouseCursor.cxx
MessageBox.cxx
)
set(HEADERS
@ -41,6 +42,7 @@ set(HEADERS
FileDialog.hxx
PUIFileDialog.hxx
MouseCursor.hxx
MessageBox.hxx
)
if(WIN32)
@ -51,8 +53,14 @@ if(WIN32)
endif()
if (APPLE)
list(APPEND HEADERS FGCocoaMenuBar.hxx CocoaFileDialog.hxx CocoaMouseCursor.hxx)
list(APPEND SOURCES FGCocoaMenuBar.mm CocoaFileDialog.mm CocoaMouseCursor.mm)
list(APPEND HEADERS FGCocoaMenuBar.hxx
CocoaFileDialog.hxx
CocoaMouseCursor.hxx
CocoaAutoreleasePool.hxx)
list(APPEND SOURCES FGCocoaMenuBar.mm
CocoaFileDialog.mm
CocoaMouseCursor.mm
CocoaMessageBox.mm)
endif()
flightgear_component(GUI "${SOURCES}" "${HEADERS}")

View file

@ -0,0 +1,24 @@
#ifndef FG_GUI_COCOA_AUTORELEASE_POOL_HXX
#define FG_GUI_COCOA_AUTORELEASE_POOL_HXX
#include <Foundation/NSAutoreleasePool.h>
class CocoaAutoreleasePool
{
public:
CocoaAutoreleasePool()
{
pool = [[NSAutoreleasePool alloc] init];
}
~CocoaAutoreleasePool()
{
[pool release];
}
private:
NSAutoreleasePool* pool;
};
#endif // of FG_GUI_COCOA_AUTORELEASE_POOL_HXX

View file

@ -0,0 +1,42 @@
#include <string>
#include <Cocoa/Cocoa.h>
#include <GUI/CocoaAutoreleasePool.hxx>
#include <GUI/MessageBox.hxx>
static NSString* stdStringToCocoa(const std::string& s)
{
return [NSString stringWithUTF8String:s.c_str()];
}
flightgear::MessageBoxResult cocoaMessageBox(const std::string& msg,
const std::string& text)
{
CocoaAutoreleasePool pool;
NSAlert* alert = [NSAlert alertWithMessageText:stdStringToCocoa(msg)
defaultButton:nil /* localized 'ok' */
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"%@",stdStringToCocoa(text)];
[[alert retain] autorelease];
[alert runModal];
return flightgear::MSG_BOX_OK;
}
flightgear::MessageBoxResult cocoaFatalMessage(const std::string& msg,
const std::string& text)
{
CocoaAutoreleasePool pool;
NSAlert* alert = [NSAlert alertWithMessageText:stdStringToCocoa(msg)
defaultButton:@"Quit FlightGear"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"%@", stdStringToCocoa(text)];
[[alert retain] autorelease];
[alert runModal];
return flightgear::MSG_BOX_OK;
}

View file

@ -23,6 +23,7 @@
#include <map>
#include <Main/globals.hxx>
#include <GUI/CocoaAutoreleasePool.hxx>
class CocoaMouseCursor::CocoaMouseCursorPrivate
{
@ -84,7 +85,8 @@ void CocoaMouseCursor::setCursor(Cursor aCursor)
if (aCursor == d->activeCursorKey) {
return;
}
CocoaAutoreleasePool pool;
d->activeCursorKey = aCursor;
if (d->cursors.find(aCursor) == d->cursors.end()) {
d->cursors[aCursor] = cocoaCursorForKey(aCursor);
@ -96,6 +98,7 @@ void CocoaMouseCursor::setCursor(Cursor aCursor)
void CocoaMouseCursor::setCursorVisible(bool aVis)
{
CocoaAutoreleasePool pool;
if (aVis) {
[NSCursor unhide];
} else {

View file

@ -11,6 +11,7 @@
#include <simgear/misc/strutils.hxx>
#include <Main/fg_props.hxx>
#include <GUI/CocoaAutoreleasePool.hxx>
#include <iostream>
@ -69,18 +70,29 @@ static NSString* stdStringToCocoa(const string& s)
return [NSString stringWithUTF8String:s.c_str()];
}
static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut)
static void setFunctionKeyShortcut(const std::string& shortcut, NSMenuItem* item)
{
unichar shortcutChar = NSF1FunctionKey;
if (shortcut == "F11") {
shortcutChar = NSF11FunctionKey;
} else if (shortcut == "F12") {
shortcutChar = NSF12FunctionKey;
} else {
SG_LOG(SG_GENERAL, SG_WARN, "CocoaMenu:setFunctionKeyShortcut: unsupported:" << shortcut);
}
unichar ch[1];
ch[0] = shortcut;
ch[0] = shortcutChar;
[item setKeyEquivalentModifierMask:NSFunctionKeyMask];
[item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]];
}
static void setItemShortcutFromString(NSMenuItem* item, const string& s)
{
const char* shortcut = "";
std::string shortcut;
bool hasCtrl = strutils::starts_with(s, "Ctrl-");
bool hasShift = strutils::starts_with(s, "Shift-");
@ -91,21 +103,17 @@ static void setItemShortcutFromString(NSMenuItem* item, const string& s)
if (hasCtrl) offset += 5;
if (hasAlt) offset += 4;
shortcut = s.c_str() + offset;
if (!strcmp(shortcut, "Esc"))
shortcut = s.substr(offset);
if (shortcut == "Esc")
shortcut = "\e";
if (!strcmp(shortcut, "F11")) {
setFunctionKeyShortcut(item, NSF11FunctionKey);
return;
}
if (!strcmp(shortcut, "F12")) {
setFunctionKeyShortcut(item, NSF12FunctionKey);
return;
}
[item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]];
if ((shortcut.length() >= 2) && (shortcut[0] == 'F') && isdigit(shortcut[1])) {
setFunctionKeyShortcut(shortcut, item);
return;
}
simgear::strutils::lowercase(shortcut);
[item setKeyEquivalent:[NSString stringWithCString:shortcut.c_str() encoding:NSUTF8StringEncoding]];
NSUInteger modifiers = 0;
if (hasCtrl) modifiers |= NSControlKeyMask;
if (hasShift) modifiers |= NSShiftKeyMask;
@ -115,23 +123,7 @@ static void setItemShortcutFromString(NSMenuItem* item, const string& s)
}
namespace {
class CocoaAutoreleasePool
{
public:
CocoaAutoreleasePool()
{
pool = [[NSAutoreleasePool alloc] init];
}
~CocoaAutoreleasePool()
{
[pool release];
}
private:
NSAutoreleasePool* pool;
};
class CocoaEnabledListener : public SGPropertyChangeListener
{
public:

148
src/GUI/MessageBox.cxx Normal file
View file

@ -0,0 +1,148 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <simgear/simgear_config.h>
#include "MessageBox.hxx"
#include <Main/globals.hxx>
#include <Viewer/renderer.hxx>
#include <osgViewer/Viewer>
#include <simgear/structure/commands.hxx>
#ifdef SG_WINDOWS
#include <windows.h>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/api/Win32/GraphicsWindowWin32>
#endif
#if defined(SG_MAC)
// externs from CocoaMessageBox.mm
flightgear::MessageBoxResult
cocoaFatalMessage(const std::string& msg, const std::string& text);
flightgear::MessageBoxResult
cocoaMessageBox(const std::string& msg, const std::string& text);
#endif
using namespace simgear::strutils;
namespace {
bool isCanvasImplementationRegistered()
{
SGCommandMgr* cmd = globals->get_commands();
return (cmd->getCommand("canvas-message-box") != NULL);
}
#if defined(SG_WINDOWS)
HWND getMainViewerHWND()
{
osgViewer::Viewer::Windows windows;
if (!globals->get_renderer() || !globals->get_renderer()->getViewer()) {
return 0;
}
globals->get_renderer()->getViewer()->getWindows(windows);
osgViewer::Viewer::Windows::const_iterator it = windows.begin();
for(; it != windows.end(); ++it) {
if (strcmp((*it)->className(), "GraphicsWindowWin32")) {
continue;
}
osgViewer::GraphicsWindowWin32* platformWin =
static_cast<osgViewer::GraphicsWindowWin32*>(*it);
return platformWin->getHWND();
}
return 0;
}
flightgear::MessageBoxResult
win32MessageBox(const std::string& caption,
const std::string& msg,
const std::string& moreText)
{
// during early startup (aircraft / fg-data validation) there is no
// osgViewer so no HWND.
HWND ownerWindow = getMainViewerHWND();
std::string fullMsg(msg);
if (!moreText.empty()) {
fullMsg += "\n\n" + moreText;
}
UINT mbType = MB_OK;
WCharVec wMsg(convertUtf8ToWString(fullMsg)),
wCap(convertUtf8ToWString(caption));
wMsg.push_back(0);
wCap.push_back(0);
::MessageBoxExW(ownerWindow, wMsg.data(), wCap.data(),
mbType, 0 /* system lang */);
return flightgear::MSG_BOX_OK;
}
#endif
} // anonymous namespace
namespace flightgear
{
MessageBoxResult modalMessageBox(const std::string& caption,
const std::string& msg,
const std::string& moreText)
{
// prefer canvas
if (isCanvasImplementationRegistered()) {
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("caption", caption);
args->setStringValue("message", msg);
args->setStringValue("more", moreText);
globals->get_commands()->execute("canvas-message-box", args);
// how to make it modal?
return MSG_BOX_OK;
}
#if defined(SG_WINDOWS)
return win32MessageBox(caption, msg, moreText);
#elif defined(SG_MAC)
return cocoaFatalMessage(msg, moreText);
#else
SG_LOG(SG_GENERAL, SG_ALERT, caption << ":" << msg);
if (!moreText.empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "(" << moreText << ")");
}
return MSG_BOX_OK;
#endif
}
MessageBoxResult fatalMessageBox(const std::string& caption,
const std::string& msg,
const std::string& moreText)
{
#if defined(SG_WINDOWS)
return win32MessageBox(caption, msg, moreText);
#elif defined(SG_MAC)
return cocoaFatalMessage(msg, moreText);
#else
std::cerr << "FATAL:" << msg << "\n";
if (!moreText.empty()) {
std::cerr << "(" << moreText << ")";
}
std::cerr << std::endl;
return MSG_BOX_OK;
#endif
}
} // of namespace flightgear

26
src/GUI/MessageBox.hxx Normal file
View file

@ -0,0 +1,26 @@
#ifndef FG_GUI_MESSAGE_BOX_HXX
#define FG_GUI_MESSAGE_BOX_HXX
#include <string>
namespace flightgear
{
enum MessageBoxResult
{
MSG_BOX_OK,
MSG_BOX_YES,
MSG_BOX_NO
};
MessageBoxResult modalMessageBox(const std::string& caption,
const std::string& msg,
const std::string& moreText = std::string());
MessageBoxResult fatalMessageBox(const std::string& caption,
const std::string& msg,
const std::string& moreText = std::string());
} // of namespace flightgear
#endif // of FG_GUI_MESSAGE_BOX_HXX

View file

@ -57,12 +57,16 @@ using std::endl;
#include <Viewer/fgviewer.hxx>
#include "main.hxx"
#include "globals.hxx"
#include "fg_props.hxx"
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <GUI/MessageBox.hxx>
#include "fg_os.hxx"
#if defined(SG_MAC)
#include <Carbon/Carbon.h>
#endif
std::string homedir;
std::string hostname;
@ -120,7 +124,7 @@ static void initFPE()
}
#endif
#if defined(_MSC_VER) || defined(_WIN32)
#if defined(SG_WINDOWS)
int main ( int argc, char **argv );
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
@ -141,7 +145,7 @@ int _bootstrap_OSInit;
// Main entry point; catch any exceptions that have made it this far.
int main ( int argc, char **argv )
{
#if defined(_MSC_VER) || defined(_WIN32)
#if defined(SG_WINDOWS)
// Don't show blocking "no disk in drive" error messages on Windows 7,
// silently return errors to application instead.
// See Microsoft MSDN #ms680621: "GUI apps should specify SEM_NOOPENFILEERRORBOX"
@ -157,6 +161,14 @@ int main ( int argc, char **argv )
signal(SIGPIPE, SIG_IGN);
#endif
#if defined(SG_MAC)
// required so native messages boxes work prior to osgViewer init
// (only needed when not running as a bundled app)
ProcessSerialNumber sn = { 0, kCurrentProcess };
TransformProcessType(&sn,kProcessTransformToForegroundApplication);
SetFrontProcess(&sn);
#endif
#ifdef PTW32_STATIC_LIB
// Initialise static pthread win32 lib
pthread_win32_process_attach_np ();
@ -208,19 +220,15 @@ int main ( int argc, char **argv )
} catch (const sg_throwable &t) {
// We must use cerr rather than
// logging, since logging may be
// disabled.
cerr << "Fatal error: " << t.getFormattedMessage() << endl;
std::string info;
if (std::strlen(t.getOrigin()) != 0)
cerr << " (received from " << t.getOrigin() << ')' << endl;
info = std::string("received from ") + t.getOrigin();
flightgear::fatalMessageBox("Fatal exception", t.getFormattedMessage(), info);
} catch (const std::exception &e ) {
cerr << "Fatal error (std::exception): " << e.what() << endl;
flightgear::fatalMessageBox("Fatal exception", e.what());
} catch (const std::string &s) {
cerr << "Fatal error (std::string): " << s << endl;
flightgear::fatalMessageBox("Fatal exception", s);
} catch (const char *s) {
cerr << "Fatal error (const char*): " << s << endl;

View file

@ -76,6 +76,7 @@
#include <Canvas/canvas_mgr.hxx>
#include <Canvas/gui_mgr.hxx>
#include <GUI/new_gui.hxx>
#include <GUI/MessageBox.hxx>
#include <Input/input.hxx>
#include <Instrumentation/instrument_mgr.hxx>
#include <Model/acmodel.hxx>
@ -127,17 +128,13 @@ using namespace boost::algorithm;
string fgBasePackageVersion() {
SGPath base_path( globals->get_fg_root() );
base_path.append("version");
if (!base_path.exists()) {
return string();
}
sg_gzifstream in( base_path.str() );
if ( !in.is_open() ) {
SGPath old_path( globals->get_fg_root() );
old_path.append( "Thanks" );
sg_gzifstream old( old_path.str() );
if ( !old.is_open() ) {
return "[none]";
} else {
return "[old version]";
}
if (!in.is_open()) {
return string();
}
string version;
@ -219,6 +216,7 @@ public:
{
std::string aircraft = fgGetString( "/sim/aircraft", "");
if (aircraft.empty()) {
flightgear::fatalMessageBox("No aircraft", "No aircraft was specified");
SG_LOG(SG_GENERAL, SG_ALERT, "no aircraft specified");
return false;
}
@ -236,6 +234,9 @@ public:
readProperties(setFile.str(), globals->get_props());
} catch ( const sg_exception &e ) {
SG_LOG(SG_INPUT, SG_ALERT, "Error reading aircraft: " << e.getFormattedMessage());
flightgear::fatalMessageBox("Error reading aircraft",
"An error occured reading the requested aircraft (" + aircraft + ")",
e.getFormattedMessage());
return false;
}
@ -243,6 +244,9 @@ public:
} else {
SG_LOG(SG_GENERAL, SG_ALERT, "aircraft '" << _searchAircraft <<
"' not found in specified dir:" << aircraftDir);
flightgear::fatalMessageBox("Aircraft not found",
"The requested aircraft '" + aircraft + "' could not be found in the specified location.",
aircraftDir);
return false;
}
}
@ -262,6 +266,9 @@ public:
if (_foundPath.str().empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find specified aircraft: " << aircraft );
flightgear::fatalMessageBox("Aircraft not found",
"The requested aircraft '" + aircraft + "' could not be found in any of the search paths");
return false;
}
@ -276,6 +283,9 @@ public:
readProperties(_foundPath.str(), globals->get_props());
} catch ( const sg_exception &e ) {
SG_LOG(SG_INPUT, SG_ALERT, "Error reading aircraft: " << e.getFormattedMessage());
flightgear::fatalMessageBox("Error reading aircraft",
"An error occured reading the requested aircraft (" + aircraft + ")",
e.getFormattedMessage());
return false;
}

View file

@ -50,8 +50,11 @@
#include <simgear/sound/soundmgr_openal.hxx>
#include <simgear/misc/strutils.hxx>
#include <Autopilot/route_mgr.hxx>
#include <GUI/gui.h>
#include <GUI/gui.h>
#include <GUI/MessageBox.hxx>
#include <Main/locale.hxx>
#include "globals.hxx"
#include "fg_init.hxx"
#include "fg_props.hxx"
@ -2289,21 +2292,23 @@ void Options::setupRoot()
// validate it
static char required_version[] = FLIGHTGEAR_VERSION;
string base_version = fgBasePackageVersion();
if ( !(base_version == required_version) ) {
// tell the operator how to use this application
simgear::requestConsole(); // ensure console is shown on Windows
if (base_version.empty()) {
flightgear::fatalMessageBox("Base package not found",
"Required data files not found, check your installation.",
"Looking for base-package files at: '" + root + "'");
exit(-1);
}
if (base_version != required_version) {
// tell the operator how to use this application
flightgear::fatalMessageBox("Base package version mismatch",
"Version check failed: please check your installation.",
"Found data files for version '" + base_version +
"' at '" + globals->get_fg_root() + "', version '"
+ required_version + "' is required.");
cerr << endl << "Base package check failed:" << endl \
<< " Version " << base_version << " found at: " \
<< globals->get_fg_root() << endl \
<< " Version " << required_version << " is required." << endl \
<< "Please upgrade/downgrade base package and set the path to your fgdata" << endl \
<< "with --fg-root=path_to_your_fgdata" << endl;
#ifdef _MSC_VER
cerr << "Hit a key to continue..." << endl;
cin.get();
#endif
exit(-1);
}
}