1
0
Fork 0

Initial work on native file dialog support.

Add an abstract interface, version that forwards to the existing PUI dialog, and a Cocoa-native version.
This commit is contained in:
James Turner 2012-12-28 14:48:19 +00:00 committed by James Turner
parent a138952ee5
commit 2e1fb7972e
8 changed files with 499 additions and 2 deletions

View 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}")

View file

@ -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

View file

@ -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.
}
}];
}

178
src/GUI/FileDialog.cxx Normal file
View file

@ -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);
}

80
src/GUI/FileDialog.hxx Normal file
View file

@ -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

85
src/GUI/PUIFileDialog.cxx Normal file
View file

@ -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);
}

30
src/GUI/PUIFileDialog.hxx Normal file
View file

@ -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

View 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)