]> git.mxchange.org Git - flightgear.git/commitdiff
Message box support.
authorJames Turner <zakalawe@mac.com>
Wed, 6 Nov 2013 23:49:58 +0000 (15:49 -0800)
committerJames Turner <zakalawe@mac.com>
Thu, 14 Nov 2013 22:02:29 +0000 (22:02 +0000)
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.

src/GUI/CMakeLists.txt
src/GUI/CocoaAutoreleasePool.hxx [new file with mode: 0644]
src/GUI/CocoaMessageBox.mm [new file with mode: 0644]
src/GUI/CocoaMouseCursor.mm
src/GUI/FGCocoaMenuBar.mm
src/GUI/MessageBox.cxx [new file with mode: 0644]
src/GUI/MessageBox.hxx [new file with mode: 0644]
src/Main/bootstrap.cxx
src/Main/fg_init.cxx
src/Main/options.cxx

index 6f36a1da21f0f204096049c9903ccecf16166532..573bbf452d3e8cf1f0e9416594decc78e0bb31cc 100644 (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}")
diff --git a/src/GUI/CocoaAutoreleasePool.hxx b/src/GUI/CocoaAutoreleasePool.hxx
new file mode 100644 (file)
index 0000000..bd283df
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/src/GUI/CocoaMessageBox.mm b/src/GUI/CocoaMessageBox.mm
new file mode 100644 (file)
index 0000000..cc52168
--- /dev/null
@@ -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;
+}
+
index 8858ff52aa6c67d014a54c6f700eda6af31cb064..53dcccf009a1637d4c5efa79c43d4366265705bc 100644 (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 {
index 7bedc33d5abf9687ec6033d7a3e4cc39839a6e96..d70baa5d5f01bed6b23486ade24e03a7cc526eba 100644 (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:
diff --git a/src/GUI/MessageBox.cxx b/src/GUI/MessageBox.cxx
new file mode 100644 (file)
index 0000000..93563c6
--- /dev/null
@@ -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
diff --git a/src/GUI/MessageBox.hxx b/src/GUI/MessageBox.hxx
new file mode 100644 (file)
index 0000000..505b8cc
--- /dev/null
@@ -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
index 2aef4c2c0896efe6f021ef54c021468a27c2f639..2b5cb2b262c062372f233b5d23da6e075392028e 100644 (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;
 
index 1c39233f4fd75d45a79c100ed35742ce7792c14c..8ad9803f9be9c1de0270c8c76838b9406603b6db 100644 (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;
     }
     
index 036d3146a712b841b321690404d5cf22abb35658..306bd4fe2780227d9c88ec03cf94cd7b28986775 100644 (file)
 #include <simgear/sound/soundmgr_openal.hxx>
 #include <simgear/misc/strutils.hxx>
 #include <Autopilot/route_mgr.hxx>
+
 #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
+    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);
+    }
     
-    simgear::requestConsole(); // ensure console is shown on Windows
-
-    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
+ 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.");
+
     exit(-1);
   }
 }