From 2e1fb7972ef6146b275d20085a62c11ac7576813 Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 28 Dec 2012 14:48:19 +0000 Subject: [PATCH] Initial work on native file dialog support. Add an abstract interface, version that forwards to the existing PUI dialog, and a Cocoa-native version. --- src/GUI/CMakeLists.txt | 8 +- src/GUI/CocoaFileDialog.hxx | 22 +++++ src/GUI/CocoaFileDialog.mm | 95 +++++++++++++++++++ src/GUI/FileDialog.cxx | 178 ++++++++++++++++++++++++++++++++++++ src/GUI/FileDialog.hxx | 80 ++++++++++++++++ src/GUI/PUIFileDialog.cxx | 85 +++++++++++++++++ src/GUI/PUIFileDialog.hxx | 30 ++++++ src/Scripting/NasalSys.cxx | 3 + 8 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 src/GUI/CocoaFileDialog.hxx create mode 100644 src/GUI/CocoaFileDialog.mm create mode 100644 src/GUI/FileDialog.cxx create mode 100644 src/GUI/FileDialog.hxx create mode 100644 src/GUI/PUIFileDialog.cxx create mode 100644 src/GUI/PUIFileDialog.hxx diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index b021d840b..d3bf72bdb 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -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 index 000000000..0f6fbc4ec --- /dev/null +++ b/src/GUI/CocoaFileDialog.hxx @@ -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 + +class CocoaFileDialog : public FGFileDialog +{ +public: + CocoaFileDialog(const std::string& aTitle, FGFileDialog::Usage use); + + virtual ~CocoaFileDialog(); + + virtual void exec(); + +private: + class CocoaFileDialogPrivate; + std::auto_ptr d; +}; + +#endif // FG_COCOA_FILE_DIALOG_HXX diff --git a/src/GUI/CocoaFileDialog.mm b/src/GUI/CocoaFileDialog.mm new file mode 100644 index 000000000..f802eea06 --- /dev/null +++ b/src/GUI/CocoaFileDialog.mm @@ -0,0 +1,95 @@ + + +#include "CocoaFileDialog.hxx" + +// bring it all in! +#include + +#include + +#include +#include + +#include
+#include
+ +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 index 000000000..8b59ae0c7 --- /dev/null +++ b/src/GUI/FileDialog.cxx @@ -0,0 +1,178 @@ +// FileDialog -- generic FileDialog interface and Nasal wrapper +// +// Written by James Turner, started 2012. +// +// Copyright (C) 2012 James Turner +// +// 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 + +#include +#include +#include + +#include
+#include +#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(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(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(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(0); + naRef object = ctx.getArg(1, naNil()); + + setCallback(new NasalCallback(func, object)); + return naNil(); +} + +typedef boost::shared_ptr FileDialogPtr; +typedef nasal::Ghost 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(0); + FGFileDialog::Usage usage = (FGFileDialog::Usage) ctx.requireArg(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("gui"); + + gui_module.set("_newFileDialog", f_createFileDialog); +} diff --git a/src/GUI/FileDialog.hxx b/src/GUI/FileDialog.hxx new file mode 100644 index 000000000..703b1aabc --- /dev/null +++ b/src/GUI/FileDialog.hxx @@ -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 // for std::auto_ptr + +#include // for string_list +#include + +#include + +// 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; +}; + +#endif // FG_GUI_FILE_DIALOG_HXX diff --git a/src/GUI/PUIFileDialog.cxx b/src/GUI/PUIFileDialog.cxx new file mode 100644 index 000000000..e76d1a87e --- /dev/null +++ b/src/GUI/PUIFileDialog.cxx @@ -0,0 +1,85 @@ + + +#include "PUIFileDialog.hxx" + +#include + +#include +#include + +#include
+#include
+#include + +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(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 index 000000000..34745668e --- /dev/null +++ b/src/GUI/PUIFileDialog.hxx @@ -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 +#include + +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 diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index aee1dc54d..ff94e2ee6 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -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) -- 2.39.5