]> git.mxchange.org Git - flightgear.git/commitdiff
Initial work on native file dialog support.
authorJames Turner <jmt@Bishop.local>
Fri, 28 Dec 2012 14:48:19 +0000 (14:48 +0000)
committerJames Turner <zakalawe@mac.com>
Sat, 19 Jan 2013 14:41:45 +0000 (14:41 +0000)
Add an abstract interface, version that forwards to the existing PUI dialog, and a Cocoa-native version.

src/GUI/CMakeLists.txt
src/GUI/CocoaFileDialog.hxx [new file with mode: 0644]
src/GUI/CocoaFileDialog.mm [new file with mode: 0644]
src/GUI/FileDialog.cxx [new file with mode: 0644]
src/GUI/FileDialog.hxx [new file with mode: 0644]
src/GUI/PUIFileDialog.cxx [new file with mode: 0644]
src/GUI/PUIFileDialog.hxx [new file with mode: 0644]
src/Scripting/NasalSys.cxx

index b021d840b13c711aa42c51990ed9913ab1af7ee2..d3bf72bdbd758d7651c3a310033258b6ff6b3566 100644 (file)
@@ -18,6 +18,8 @@ set(SOURCES
        property_list.cxx
        FGFontCache.cxx
        FGColor.cxx
+    FileDialog.cxx
+    PUIFileDialog.cxx
        )
 
 set(HEADERS
@@ -35,11 +37,13 @@ set(HEADERS
        property_list.hxx
        FGFontCache.hxx
        FGColor.hxx
+    FileDialog.hxx
+    PUIFileDialog.hxx
        )
                
 if (APPLE)
-    list(APPEND HEADERS FGCocoaMenuBar.hxx)
-    list(APPEND SOURCES FGCocoaMenuBar.mm)
+    list(APPEND HEADERS FGCocoaMenuBar.hxx CocoaFileDialog.hxx)
+    list(APPEND SOURCES FGCocoaMenuBar.mm CocoaFileDialog.mm)
 endif()
                
 flightgear_component(GUI "${SOURCES}" "${HEADERS}")
diff --git a/src/GUI/CocoaFileDialog.hxx b/src/GUI/CocoaFileDialog.hxx
new file mode 100644 (file)
index 0000000..0f6fbc4
--- /dev/null
@@ -0,0 +1,22 @@
+// CocoaFileDialog.hxx - file dialog implemented using Cocoa
+
+#ifndef FG_COCOA_FILE_DIALOG_HXX
+#define FG_COCOA_FILE_DIALOG_HXX 1
+
+#include <GUI/FileDialog.hxx>
+
+class CocoaFileDialog : public FGFileDialog
+{
+public:
+    CocoaFileDialog(const std::string& aTitle, FGFileDialog::Usage use);
+    
+    virtual ~CocoaFileDialog();
+    
+    virtual void exec();
+    
+private:
+    class CocoaFileDialogPrivate;
+    std::auto_ptr<CocoaFileDialogPrivate> d;
+};
+
+#endif // FG_COCOA_FILE_DIALOG_HXX
diff --git a/src/GUI/CocoaFileDialog.mm b/src/GUI/CocoaFileDialog.mm
new file mode 100644 (file)
index 0000000..f802eea
--- /dev/null
@@ -0,0 +1,95 @@
+
+
+#include "CocoaFileDialog.hxx"
+
+// bring it all in!
+#include <Cocoa/Cocoa.h>
+
+#include <boost/foreach.hpp>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/misc/strutils.hxx>
+
+#include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
+
+static NSString* stdStringToCocoa(const std::string& s)
+{
+    return [NSString stringWithUTF8String:s.c_str()];
+}
+
+static NSURL* pathToNSURL(const SGPath& aPath)
+{
+    return [NSURL fileURLWithPath:stdStringToCocoa(aPath.str())];
+}
+
+class CocoaFileDialog::CocoaFileDialogPrivate
+{
+public:
+    CocoaFileDialogPrivate() :
+        panel(nil)
+    {
+        
+    }
+    
+    ~CocoaFileDialogPrivate()
+    {
+        [panel release];
+    }
+    
+    NSSavePanel* panel;
+};
+
+CocoaFileDialog::CocoaFileDialog(const std::string& aTitle, FGFileDialog::Usage use) :
+    FGFileDialog(aTitle, use)
+{
+    d.reset(new CocoaFileDialogPrivate);
+    if (use == USE_SAVE_FILE) {
+        d->panel = [NSSavePanel savePanel];
+    } else {
+        d->panel = [NSOpenPanel openPanel];
+    }
+    
+    if (use == USE_CHOOSE_DIR) {
+        [d->panel setCanChooseDirectories:YES];
+    }
+}
+
+CocoaFileDialog::~CocoaFileDialog()
+{
+    
+}
+
+void CocoaFileDialog::exec()
+{
+    if (_usage == USE_SAVE_FILE) {
+        [d->panel setNameFieldStringValue:stdStringToCocoa(_placeholder)];
+    }
+    
+    NSMutableArray* extensions = [NSMutableArray arrayWithCapacity:0];
+    BOOST_FOREACH(std::string ext, _filterPatterns) {
+        if (!simgear::strutils::starts_with(ext, "*.")) {
+            SG_LOG(SG_GENERAL, SG_INFO, "can't use pattern on Cococa:" << ext);
+            continue;
+        }
+        [extensions addObject:stdStringToCocoa(ext.substr(2))];
+    }
+
+    [d->panel setAllowedFileTypes:extensions];
+    [d->panel setTitle:stdStringToCocoa(_title)];
+    if (_showHidden) {
+        [d->panel setShowsHiddenFiles:YES];
+    }
+    
+    [d->panel setDirectoryURL: pathToNSURL(_initialPath)];
+    
+    [d->panel beginWithCompletionHandler:^(NSInteger result)
+    {
+        if (result == NSFileHandlingPanelOKButton) {
+            NSURL*  theDoc = [d->panel URL];
+            NSLog(@"the URL is: %@", theDoc);
+            // Open  the document.
+        }
+        
+    }];
+}
diff --git a/src/GUI/FileDialog.cxx b/src/GUI/FileDialog.cxx
new file mode 100644 (file)
index 0000000..8b59ae0
--- /dev/null
@@ -0,0 +1,178 @@
+// FileDialog -- generic FileDialog interface and Nasal wrapper
+//
+// Written by James Turner, started 2012.
+//
+// Copyright (C) 2012 James Turner  <zakalawe@mac.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+
+#include "FileDialog.hxx"
+
+#include <boost/shared_ptr.hpp>
+
+#include <simgear/nasal/cppbind/from_nasal.hxx>
+#include <simgear/nasal/cppbind/to_nasal.hxx>
+#include <simgear/nasal/cppbind/NasalHash.hxx>
+
+#include <Main/globals.hxx>
+#include <Scripting/NasalSys.hxx>
+#include "PUIFileDialog.hxx"
+
+#ifdef SG_MAC
+    #include "CocoaFileDialog.hxx"
+#endif
+
+FGFileDialog::FGFileDialog(const std::string& aTitle, Usage use) :
+    _usage(use),
+    _title(aTitle),
+    _showHidden(false)
+{
+    
+}
+
+FGFileDialog::~FGFileDialog()
+{
+    // ensure this is concrete so callback gets cleaned up.
+}
+
+void FGFileDialog::setButton(const std::string& aText)
+{
+    _buttonText = aText;
+}
+
+void FGFileDialog::setDirectory(const SGPath& aPath)
+{
+    _initialPath = aPath;
+}
+
+void FGFileDialog::setFilterPatterns(const string_list& patterns)
+{
+    _filterPatterns = patterns;
+}
+
+void FGFileDialog::setPlaceholderName(const std::string& aName)
+{
+    _placeholder = aName;
+}
+
+void FGFileDialog::setCallback(Callback* aCB)
+{
+    _callback.reset(aCB);
+}
+
+void FGFileDialog::setShowHidden(bool show)
+{
+    _showHidden = show;
+}
+
+naRef FGFileDialog::openFromNasal(const nasal::CallContext& ctx)
+{
+    exec();
+    return naNil();
+}
+
+class NasalCallback : public FGFileDialog::Callback
+{
+public:
+    NasalCallback(naRef f, naRef obj) :
+        func(f),
+        object(obj)
+    {
+        FGNasalSys* sys = static_cast<FGNasalSys*>(globals->get_subsystem("nasal"));
+        _gcKeys[0] = sys->gcSave(f);
+        _gcKeys[1] = sys->gcSave(obj);
+    }
+    
+    virtual void onFileDialogDone(FGFileDialog* instance, const SGPath& aPath)
+    {
+        FGNasalSys* sys = static_cast<FGNasalSys*>(globals->get_subsystem("nasal"));
+        naContext ctx = sys->context();
+        
+        naRef args[1];
+        args[0] = nasal::to_nasal(ctx, aPath);
+        
+        sys->callMethod(func, object, 1, args, naNil() /* locals */);
+    }
+    
+    ~NasalCallback()
+    {
+        FGNasalSys* sys = static_cast<FGNasalSys*>(globals->get_subsystem("nasal"));
+        sys->gcRelease(_gcKeys[0]);
+        sys->gcRelease(_gcKeys[1]);
+    }
+private:
+    naRef func;
+    naRef object;
+    int _gcKeys[2];
+};
+
+naRef FGFileDialog::setCallbackFromNasal(const nasal::CallContext& ctx)
+{
+    // wrap up the naFunc in our callback type
+    naRef func = ctx.requireArg<naRef>(0);
+    naRef object = ctx.getArg<naRef>(1, naNil());
+    
+    setCallback(new NasalCallback(func, object));
+    return naNil();
+}
+
+typedef boost::shared_ptr<FGFileDialog> FileDialogPtr;
+typedef nasal::Ghost<FileDialogPtr> NasalFileDialog;
+
+/**
+ * Create new Canvas and get ghost for it.
+ */
+static naRef f_createFileDialog(naContext c, naRef me, int argc, naRef* args)
+{
+    nasal::CallContext ctx(c, argc, args);
+    std::string title = ctx.requireArg<std::string>(0);
+    FGFileDialog::Usage usage = (FGFileDialog::Usage) ctx.requireArg<int>(1);
+    
+#ifdef SG_MAC
+    FileDialogPtr fd(new CocoaFileDialog(title, usage));
+#else
+    FileDialogPtr fd(new PUIFileDialog(title, usage));
+#endif
+    
+    return NasalFileDialog::create(c, fd);
+}
+
+void postinitNasalGUI(naRef globals, naContext c)
+{
+    NasalFileDialog::init("gui.FileSelector")
+    .member("button", &FGFileDialog::getButton,  &FGFileDialog::setButton)
+    .member("directory", &FGFileDialog::getDirectory, &FGFileDialog::setDirectory)
+    .member("show-hidden", &FGFileDialog::showHidden, &FGFileDialog::setShowHidden)
+    .member("placeholder", &FGFileDialog::getPlaceholder, &FGFileDialog::setPlaceholderName)
+    .member("pattern", &FGFileDialog::filterPatterns, &FGFileDialog::setFilterPatterns)
+    .method<&FGFileDialog::openFromNasal>("open")
+    .method<&FGFileDialog::setCallbackFromNasal>("setCallback");
+    
+    naRef guiModule = naHash_cget(globals, (char*) "gui");
+    if (naIsNil(guiModule)) {
+        SG_LOG(SG_GENERAL, SG_WARN, "postinitNasalGUI: gui.nas not loaded");
+        return;
+    }
+    
+    nasal::Hash globals_module(globals, c),
+        gui_module = globals_module.get<nasal::Hash>("gui");
+    
+    gui_module.set("_newFileDialog", f_createFileDialog);
+}
diff --git a/src/GUI/FileDialog.hxx b/src/GUI/FileDialog.hxx
new file mode 100644 (file)
index 0000000..703b1aa
--- /dev/null
@@ -0,0 +1,80 @@
+// FileDialog.hxx - abstract inteface for a file open/save dialog
+
+#ifndef FG_GUI_FILE_DIALOG_HXX
+#define FG_GUI_FILE_DIALOG_HXX 1
+
+#include <memory> // for std::auto_ptr
+
+#include <simgear/misc/strutils.hxx> // for string_list
+#include <simgear/misc/sg_path.hxx>
+
+#include <simgear/nasal/cppbind/Ghost.hxx>
+
+// forward decls
+class SGPropertyNode;
+
+class FGFileDialog
+{
+public:
+    typedef enum {
+        USE_OPEN_FILE,
+        USE_SAVE_FILE,
+        USE_CHOOSE_DIR
+    } Usage;
+    
+    std::string getButton() const
+    { return _buttonText; }
+    
+    void setButton(const std::string& aText);
+
+    SGPath getDirectory() const
+    { return _initialPath; }
+    
+    void setDirectory(const SGPath& aPath);
+    
+    string_list filterPatterns() const
+    { return _filterPatterns; }
+    
+    void setFilterPatterns(const string_list& patterns);
+    
+    /// for saving
+    std::string getPlaceholder() const
+    { return _placeholder; }
+    
+    void setPlaceholderName(const std::string& aName);
+
+    bool showHidden() const
+    { return _showHidden; }
+    void setShowHidden(bool show);
+    
+    /**
+     * Destructor.
+     */
+    virtual ~FGFileDialog ();
+
+     virtual void exec() = 0;
+
+     class Callback
+     {
+     public:
+         virtual ~Callback() { }
+         virtual void onFileDialogDone(FGFileDialog* ins, const SGPath& result) = 0;
+     };
+
+     virtual void setCallback(Callback* aCB);
+
+    naRef openFromNasal(const nasal::CallContext& ctx);
+    naRef setCallbackFromNasal(const nasal::CallContext& ctx);
+protected:
+    FGFileDialog(const std::string& aTitle, Usage use);
+    
+    const Usage _usage;
+    std::string _title, _buttonText;
+    SGPath _initialPath;
+    string_list _filterPatterns;
+    std::string _placeholder;
+    bool _showHidden;
+    std::auto_ptr<Callback> _callback;
+};
+
+#endif // FG_GUI_FILE_DIALOG_HXX
diff --git a/src/GUI/PUIFileDialog.cxx b/src/GUI/PUIFileDialog.cxx
new file mode 100644 (file)
index 0000000..e76d1a8
--- /dev/null
@@ -0,0 +1,85 @@
+
+
+#include "PUIFileDialog.hxx"
+
+#include <boost/foreach.hpp>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/props/props_io.hxx>
+
+#include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
+#include <GUI/new_gui.hxx>
+
+class PUIFileDialog::PathListener : public SGPropertyChangeListener
+{
+public:
+    PathListener(PUIFileDialog* dlg) :
+        _dialog(dlg)
+    { ; }
+    
+    virtual void valueChanged(SGPropertyNode* node)
+    {
+        _dialog->pathChanged(SGPath(node->getStringValue()));
+    }
+    
+private:
+    PUIFileDialog* _dialog;
+};
+
+PUIFileDialog::PUIFileDialog(const std::string& aTitle, Usage use) :
+    FGFileDialog(aTitle, use),
+    _listener(NULL)
+{
+    SG_LOG(SG_GENERAL, SG_INFO, "created PUIFileDialog");
+}
+
+PUIFileDialog::~PUIFileDialog()
+{
+    if (_listener) {
+        SGPropertyNode_ptr path = _dialogRoot->getNode("path");
+        path->removeChangeListener(_listener);
+    }
+}
+
+void PUIFileDialog::exec()
+{
+    NewGUI* gui = static_cast<NewGUI*>(globals->get_subsystem("gui"));
+    std::string name("native-file-0");
+    _dialogRoot = fgGetNode("/sim/gui/dialogs/" + name, true);
+    
+    SGPropertyNode_ptr dlg = _dialogRoot->getChild("dialog", 0, true);
+    SGPath dlgXML = globals->resolve_resource_path("gui/dialogs/file-select.xml");
+    readProperties(dlgXML.str(), dlg);
+    
+    dlg->setStringValue("name", name);
+    gui->newDialog(dlg);
+    
+    _dialogRoot->setStringValue("title", _title);
+    _dialogRoot->setStringValue("button", _buttonText);
+    _dialogRoot->setStringValue("directory", _initialPath.str());
+    _dialogRoot->setStringValue("selection", _placeholder);
+    
+// convert patterns vector into pattern nodes
+    _dialogRoot->removeChildren("pattern");
+    int index=0;
+    BOOST_FOREACH(std::string pat, _filterPatterns) {
+        _dialogRoot->getNode("pattern", index++, true)->setStringValue(pat);
+    }
+    
+    _dialogRoot->setBoolValue("show-files", _usage != USE_CHOOSE_DIR);
+    _dialogRoot->setBoolValue("dotfiles", _showHidden);
+    
+    if (!_listener) {
+        _listener = new PathListener(this);
+    }
+    SGPropertyNode_ptr path = _dialogRoot->getNode("path", 0, true);
+    path->addChangeListener(_listener);
+    
+    gui->showDialog(name);
+}
+
+void PUIFileDialog::pathChanged(const SGPath& aPath)
+{
+    _callback->onFileDialogDone(this, aPath);
+}
diff --git a/src/GUI/PUIFileDialog.hxx b/src/GUI/PUIFileDialog.hxx
new file mode 100644 (file)
index 0000000..3474566
--- /dev/null
@@ -0,0 +1,30 @@
+// PUIFileDialog.hxx - file dialog implemented using PUI
+
+#ifndef FG_PUI_FILE_DIALOG_HXX
+#define FG_PUI_FILE_DIALOG_HXX 1
+
+#include <simgear/props/props.hxx>
+#include <GUI/FileDialog.hxx>
+
+class PUIFileDialog : public FGFileDialog
+{
+public:
+    PUIFileDialog(const std::string& aTitle, FGFileDialog::Usage use);
+
+    virtual ~PUIFileDialog();
+    
+    virtual void exec();
+
+private:
+    class PathListener;
+    friend class PathListener;
+    
+    // called by the listener
+    void pathChanged(const SGPath& aPath);
+    
+    
+    SGPropertyNode_ptr _dialogRoot;
+    PathListener* _listener;
+};
+
+#endif // FG_PUI_FILE_DIALOG_HXX
index aee1dc54d94e4b556f81d4c14fead7fa8a2d0be8..ff94e2ee645d8e63044128cf9293844cfc69910a 100644 (file)
@@ -37,6 +37,8 @@
 
 using std::map;
 
+void postinitNasalGUI(naRef globals, naContext c);
+
 static FGNasalSys* nasalSys = 0;
 
 // Listener class for loading Nasal modules on demand
@@ -585,6 +587,7 @@ void FGNasalSys::init()
   
     // now Nasal modules are loaded, we can do some delayed work
     postinitNasalPositioned(_globals, _context);
+    postinitNasalGUI(_globals, _context);
 }
 
 void FGNasalSys::update(double)