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:
parent
a138952ee5
commit
2e1fb7972e
8 changed files with 499 additions and 2 deletions
|
@ -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}")
|
||||
|
|
22
src/GUI/CocoaFileDialog.hxx
Normal file
22
src/GUI/CocoaFileDialog.hxx
Normal 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
|
95
src/GUI/CocoaFileDialog.mm
Normal file
95
src/GUI/CocoaFileDialog.mm
Normal 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
178
src/GUI/FileDialog.cxx
Normal 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
80
src/GUI/FileDialog.hxx
Normal 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
85
src/GUI/PUIFileDialog.cxx
Normal 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
30
src/GUI/PUIFileDialog.hxx
Normal 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
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue