Add an abstract interface, version that forwards to the existing PUI dialog, and a Cocoa-native version.
property_list.cxx
FGFontCache.cxx
FGColor.cxx
+ FileDialog.cxx
+ PUIFileDialog.cxx
)
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}")
--- /dev/null
+// 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
--- /dev/null
+
+
+#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.
+ }
+
+ }];
+}
--- /dev/null
+// 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);
+}
--- /dev/null
+// 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
--- /dev/null
+
+
+#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);
+}
--- /dev/null
+// 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
using std::map;
+void postinitNasalGUI(naRef globals, naContext c);
+
static FGNasalSys* nasalSys = 0;
// Listener class for loading Nasal modules on demand
// now Nasal modules are loaded, we can do some delayed work
postinitNasalPositioned(_globals, _context);
+ postinitNasalGUI(_globals, _context);
}
void FGNasalSys::update(double)