summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2012-11-22 03:17:28 +0000
committerDavid Robillard <d@drobilla.net>2012-11-22 03:17:28 +0000
commitb6376fc247490134827c51c554a6108ab9f5a898 (patch)
tree1274679011e2f3da2a87adb43a217fe26861ca7e
parentb36e8ed06c9d1fbec67b8a3e3a207b3396e29a5a (diff)
downloadingen-b6376fc247490134827c51c554a6108ab9f5a898.tar.gz
ingen-b6376fc247490134827c51c554a6108ab9f5a898.tar.bz2
ingen-b6376fc247490134827c51c554a6108ab9f5a898.zip
Configuration file saving.
Automatically save and restore GUI settings (last patch directory, human names, etc.). git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@4848 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--ingen/Configuration.hpp56
-rw-r--r--src/Configuration.cpp150
-rw-r--r--src/gui/App.cpp14
-rw-r--r--src/gui/GraphBox.cpp3
-rw-r--r--src/gui/LoadGraphWindow.cpp4
5 files changed, 185 insertions, 42 deletions
diff --git a/ingen/Configuration.hpp b/ingen/Configuration.hpp
index ba06ba33..a5cf3387 100644
--- a/ingen/Configuration.hpp
+++ b/ingen/Configuration.hpp
@@ -17,7 +17,6 @@
#ifndef INGEN_CONFIGURATION_HPP
#define INGEN_CONFIGURATION_HPP
-#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -27,6 +26,7 @@
#include <string>
#include "ingen/Forge.hpp"
+#include "ingen/URIMap.hpp"
#include "raul/Atom.hpp"
#include "raul/Exception.hpp"
@@ -40,12 +40,23 @@ class Configuration {
public:
Configuration(Forge& forge);
+ /** The scope of a configuration option.
+ *
+ * This controls when and where an option will be saved or restored.
+ */
+ enum Scope {
+ GLOBAL = 1, ///< Applies to any Ingen instance
+ SESSION = 1<<1, ///< Applies to this Ingen instance only
+ GUI = 1<<2 ///< Persistent GUI settings saved at exit
+ };
+
/** Add a configuration option.
*
* @param key URI local name, in camelCase
* @param name Long option name (without leading "--")
* @param letter Short option name (without leading "-")
* @param desc Description
+ * @param scope Scope of option
* @param type Type
* @param value Default value
*/
@@ -53,20 +64,41 @@ public:
const std::string& name,
char letter,
const std::string& desc,
+ Scope scope,
const Raul::Atom::TypeID type,
const Raul::Atom& value);
void print_usage(const std::string& program, std::ostream& os);
- struct CommandLineError : public Raul::Exception {
- explicit CommandLineError(const std::string& m) : Exception(m) {}
+ struct OptionError : public Raul::Exception {
+ explicit OptionError(const std::string& m) : Exception(m) {}
+ };
+
+ struct FileError : public Raul::Exception {
+ explicit FileError(const std::string& m) : Exception(m) {}
};
- void parse(int argc, char** argv) throw (CommandLineError);
+ void parse(int argc, char** argv) throw (OptionError);
/** Load a specific file. */
bool load(const std::string& path);
+ /** Save configuration to a file.
+ *
+ * @param filename If absolute, the configuration will be saved to this
+ * path. Otherwise the configuration will be saved to the user
+ * configuration directory (e.g. ~/.config/ingen/filename).
+ *
+ * @param scopes Bitwise OR of Scope values. Only options which match the
+ * given scopes will be saved.
+ *
+ * @return The absolute path of the saved configuration file.
+ */
+ std::string save(URIMap& uri_map,
+ const std::string& app,
+ const std::string& filename,
+ unsigned scopes) throw (FileError);
+
/** Load files from the standard configuration directories for the app.
*
* The system configuration file(s), e.g. /etc/xdg/appname/filename,
@@ -74,7 +106,7 @@ public:
* so the user options will override the system options.
*/
std::list<std::string> load_default(const std::string& app,
- const std::string& file);
+ const std::string& filename);
const Raul::Atom& option(const std::string& long_name) const;
bool set(const std::string& long_name, const Raul::Atom& value);
@@ -84,14 +116,19 @@ public:
private:
struct Option {
public:
- Option(const std::string& n, char l, const std::string& d,
+ Option(const std::string& k, const std::string& n, char l,
+ const std::string& d, Scope s,
const Raul::Atom::TypeID type, const Raul::Atom& def)
- : name(n), letter(l), desc(d), type(type), value(def)
+ : key(k), name(n), letter(l)
+ , desc(d), scope(s)
+ , type(type), value(def)
{}
+ std::string key;
std::string name;
char letter;
std::string desc;
+ Scope scope;
Raul::Atom::TypeID type;
Raul::Atom value;
};
@@ -107,8 +144,9 @@ private:
typedef std::map<std::string, std::string> Keys;
typedef std::list<std::string> Files;
- int set_value_from_string(Configuration::Option& option, const std::string& value)
- throw (Configuration::CommandLineError);
+ int set_value_from_string(Configuration::Option& option,
+ const std::string& value)
+ throw (Configuration::OptionError);
Forge& _forge;
const std::string _shortdesc;
diff --git a/src/Configuration.cpp b/src/Configuration.cpp
index b3f69c4a..60f41597 100644
--- a/src/Configuration.cpp
+++ b/src/Configuration.cpp
@@ -14,6 +14,10 @@
along with Ingen. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
#include <iostream>
#include <glibmm/fileutils.h>
@@ -22,6 +26,7 @@
#include "ingen/Configuration.hpp"
#include "raul/fmt.hpp"
#include "sord/sordmm.hpp"
+#include "sratom/sratom.h"
#define NS_INGEN "http://drobilla.net/ns/ingen#"
@@ -44,23 +49,23 @@ Configuration::Configuration(Forge& forge)
" ingen -egl foo.ingen # Run an engine and a GUI and load a graph")
, _max_name_length(0)
{
- add("clientPort", "client-port", 'C', "Client port", forge.Int, Raul::Atom());
- add("connect", "connect", 'c', "Connect to engine URI", forge.String, forge.alloc("unix:///tmp/ingen.sock"));
- add("engine", "engine", 'e', "Run (JACK) engine", forge.Bool, forge.make(false));
- add("enginePort", "engine-port", 'E', "Engine listen port", forge.Int, forge.make(16180));
- add("socket", "socket", 'S', "Engine socket path", forge.String, forge.alloc("/tmp/ingen.sock"));
- add("gui", "gui", 'g', "Launch the GTK graphical interface", forge.Bool, forge.make(false));
- add("", "help", 'h', "Print this help message", forge.Bool, forge.make(false));
- add("jackName", "jack-name", 'n', "JACK name", forge.String, forge.alloc("ingen"));
- add("jackServer", "jack-server", 's', "JACK server name", forge.String, forge.alloc(""));
- add("uuid", "uuid", 'u', "JACK session UUID", forge.String, Raul::Atom());
- add("load", "load", 'l', "Load graph", forge.String, Raul::Atom());
- add("path", "path", 'L', "Target path for loaded graph", forge.String, Raul::Atom());
- add("queueSize", "queue-size", 'q', "Event queue size", forge.Int, forge.make(4096));
- add("run", "run", 'r', "Run script", forge.String, Raul::Atom());
- add("humanNames", "human-names", 0, "Show human names in GUI", forge.Bool, forge.make(true));
- add("portLabels", "port-labels", 0, "Show port labels in GUI", forge.Bool, forge.make(true));
- add("graphDirectory", "graph-directory", 0, "Default directory for opening graphs", forge.String, Raul::Atom());
+ add("clientPort", "client-port", 'C', "Client port", SESSION, forge.Int, Raul::Atom());
+ add("connect", "connect", 'c', "Connect to engine URI", SESSION, forge.String, forge.alloc("unix:///tmp/ingen.sock"));
+ add("engine", "engine", 'e', "Run (JACK) engine", SESSION, forge.Bool, forge.make(false));
+ add("enginePort", "engine-port", 'E', "Engine listen port", SESSION, forge.Int, forge.make(16180));
+ add("socket", "socket", 'S', "Engine socket path", SESSION, forge.String, forge.alloc("/tmp/ingen.sock"));
+ add("gui", "gui", 'g', "Launch the GTK graphical interface", SESSION, forge.Bool, forge.make(false));
+ add("", "help", 'h', "Print this help message", SESSION, forge.Bool, forge.make(false));
+ add("jackName", "jack-name", 'n', "JACK name", SESSION, forge.String, forge.alloc("ingen"));
+ add("jackServer", "jack-server", 's', "JACK server name", GLOBAL, forge.String, forge.alloc(""));
+ add("uuid", "uuid", 'u', "JACK session UUID", SESSION, forge.String, Raul::Atom());
+ add("load", "load", 'l', "Load graph", SESSION, forge.String, Raul::Atom());
+ add("path", "path", 'L', "Target path for loaded graph", SESSION, forge.String, Raul::Atom());
+ add("queueSize", "queue-size", 'q', "Event queue size", GLOBAL, forge.Int, forge.make(4096));
+ add("run", "run", 'r', "Run script", SESSION, forge.String, Raul::Atom());
+ add("humanNames", "human-names", 0, "Show human names in GUI", GUI, forge.Bool, forge.make(true));
+ add("portLabels", "port-labels", 0, "Show port labels in GUI", GUI, forge.Bool, forge.make(true));
+ add("graphDirectory", "graph-directory", 0, "Default directory for opening graphs", GUI, forge.String, Raul::Atom());
}
Configuration&
@@ -68,12 +73,13 @@ Configuration::add(const std::string& key,
const std::string& name,
char letter,
const std::string& desc,
+ Scope scope,
const Raul::Atom::TypeID type,
const Raul::Atom& value)
{
assert(value.type() == type || value.type() == 0);
_max_name_length = std::max(_max_name_length, name.length());
- _options.insert(make_pair(name, Option(name, letter, desc, type, value)));
+ _options.insert(make_pair(name, Option(key, name, letter, desc, scope, type, value)));
if (!key.empty()) {
_keys.insert(make_pair(key, name));
}
@@ -106,7 +112,7 @@ Configuration::print_usage(const std::string& program, std::ostream& os)
int
Configuration::set_value_from_string(Configuration::Option& option,
const std::string& value)
- throw (Configuration::CommandLineError)
+ throw (Configuration::OptionError)
{
if (option.type == _forge.Int) {
char* endptr = NULL;
@@ -114,7 +120,7 @@ Configuration::set_value_from_string(Configuration::Option& option,
if (endptr && *endptr == '\0') {
option.value = _forge.make(intval);
} else {
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("option `%1%' has non-integer value `%2%'")
% option.name % value).str());
}
@@ -125,7 +131,7 @@ Configuration::set_value_from_string(Configuration::Option& option,
option.value = _forge.make(bool(!strcmp(value.c_str(), "true")));
assert(option.value.type() == _forge.Bool);
} else {
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("bad option type `%1%'") % option.name).str());
}
return EXIT_SUCCESS;
@@ -133,7 +139,7 @@ Configuration::set_value_from_string(Configuration::Option& option,
/** Parse command line arguments. */
void
-Configuration::parse(int argc, char** argv) throw (Configuration::CommandLineError)
+Configuration::parse(int argc, char** argv) throw (Configuration::OptionError)
{
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-' || !strcmp(argv[i], "-")) {
@@ -142,14 +148,14 @@ Configuration::parse(int argc, char** argv) throw (Configuration::CommandLineErr
const std::string name = std::string(argv[i]).substr(2);
Options::iterator o = _options.find(name);
if (o == _options.end()) {
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("unrecognized option `%1%'") % name).str());
}
if (o->second.type == _forge.Bool) {
o->second.value = _forge.make(true);
} else {
if (++i >= argc)
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("missing value for `%1'") % name).str());
set_value_from_string(o->second, argv[i]);
}
@@ -159,12 +165,12 @@ Configuration::parse(int argc, char** argv) throw (Configuration::CommandLineErr
char letter = argv[i][j];
ShortNames::iterator n = _short_names.find(letter);
if (n == _short_names.end())
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("unrecognized option `%1%'") % letter).str());
Options::iterator o = _options.find(n->second);
if (j < len - 1) {
if (o->second.type != _forge.Bool)
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("missing value for `%1%'") % letter).str());
o->second.value = _forge.make(true);
} else {
@@ -172,7 +178,7 @@ Configuration::parse(int argc, char** argv) throw (Configuration::CommandLineErr
o->second.value = _forge.make(true);
} else {
if (++i >= argc)
- throw CommandLineError(
+ throw OptionError(
(Raul::fmt("missing value for `%1%'") % letter).str());
set_value_from_string(o->second, argv[i]);
}
@@ -212,14 +218,96 @@ Configuration::load(const std::string& path)
}
}
}
-
+
serd_node_free(&node);
serd_env_free(env);
return true;
}
+std::string
+Configuration::save(URIMap& uri_map,
+ const std::string& app,
+ const std::string& filename,
+ unsigned scopes)
+ throw (FileError)
+{
+ // Save to file if it is absolute, otherwise save to user config dir
+ std::string path = filename;
+ if (!Glib::path_is_absolute(path)) {
+ path = Glib::build_filename(Glib::get_user_config_dir(), app, filename);
+ }
+
+ // Create parent directories if necessary
+ const std::string dir = Glib::path_get_dirname(path);
+ if (g_mkdir_with_parents(dir.c_str(), 0755) < 0) {
+ throw FileError((Raul::fmt("Error creating directory %1% (%2%)")
+ % dir % strerror(errno)).str());
+ }
+
+ // Attempt to open file for writing
+ FILE* file = fopen(path.c_str(), "w");
+ if (!file) {
+ throw FileError((Raul::fmt("Failed to open file %1% (%2%)")
+ % path % strerror(errno)).str());
+ }
+
+ // Use the file's URI as the base URI
+ SerdURI base_uri;
+ SerdNode base = serd_node_new_file_uri(
+ (const uint8_t*)path.c_str(), NULL, &base_uri, true);
+
+ // Create environment with ingen prefix
+ SerdEnv* env = serd_env_new(&base);
+ serd_env_set_prefix_from_strings(
+ env, (const uint8_t*)"ingen", (const uint8_t*)NS_INGEN);
+
+ // Create Turtle writer
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED),
+ env,
+ &base_uri,
+ serd_file_sink,
+ file);
+
+ // Write a prefix directive for each prefix in the environment
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+
+ // Create an atom serialiser and connect it to the Turtle writer
+ Sratom* sratom = sratom_new(&uri_map.urid_map_feature()->urid_map);
+ sratom_set_pretty_numbers(sratom, true);
+ sratom_set_sink(sratom, (const char*)base.buf,
+ (SerdStatementSink)serd_writer_write_statement, NULL,
+ writer);
+
+ // Write a statement for each valid option
+ for (Options::iterator o = _options.begin(); o != _options.end(); ++o) {
+ const Raul::Atom& value = o->second.value;
+ if (!(o->second.scope & scopes) ||
+ o->second.key.empty() ||
+ !value.is_valid()) {
+ continue;
+ }
+
+ const std::string key(std::string("ingen:") + o->second.key);
+ SerdNode pred = serd_node_from_string(
+ SERD_CURIE, (const uint8_t*)key.c_str());
+ sratom_write(sratom, &uri_map.urid_unmap_feature()->urid_unmap, 0,
+ &base, &pred, value.type(), value.size(), value.get_body());
+ }
+
+ sratom_free(sratom);
+ serd_writer_free(writer);
+ serd_env_free(env);
+ serd_node_free(&base);
+ fclose(file);
+
+ return path;
+}
+
std::list<std::string>
-Configuration::load_default(const std::string& app, const std::string& file)
+Configuration::load_default(const std::string& app,
+ const std::string& filename)
{
std::list<std::string> loaded;
@@ -227,14 +315,14 @@ Configuration::load_default(const std::string& app, const std::string& file)
for (std::vector<std::string>::const_iterator i = dirs.begin();
i != dirs.end();
++i) {
- const std::string path = Glib::build_filename(*i, app, file);
+ const std::string path = Glib::build_filename(*i, app, filename);
if (load(path)) {
loaded.push_back(path);
}
}
const std::string path = Glib::build_filename(
- Glib::get_user_config_dir(), app, file);
+ Glib::get_user_config_dir(), app, filename);
if (load(path)) {
loaded.push_back(path);
}
diff --git a/src/gui/App.cpp b/src/gui/App.cpp
index 30c31f64..b458bc3c 100644
--- a/src/gui/App.cpp
+++ b/src/gui/App.cpp
@@ -23,13 +23,14 @@
#include <gtkmm/stock.h>
#include "ganv/Edge.hpp"
+#include "ingen/Configuration.hpp"
#include "ingen/EngineBase.hpp"
#include "ingen/Interface.hpp"
#include "ingen/Log.hpp"
#include "ingen/World.hpp"
#include "ingen/client/ClientStore.hpp"
-#include "ingen/client/ObjectModel.hpp"
#include "ingen/client/GraphModel.hpp"
+#include "ingen/client/ObjectModel.hpp"
#include "ingen/client/SigClientInterface.hpp"
#include "ingen/runtime_paths.hpp"
#include "lilv/lilv.h"
@@ -74,6 +75,8 @@ App::App(Ingen::World* world)
, _enable_signal(true)
, _requested_plugins(false)
{
+ _world->conf().load_default("ingen", "gui.ttl");
+
WidgetFactory::get_widget_derived("connect_win", _connect_window);
WidgetFactory::get_widget_derived("messages_win", _messages_window);
WidgetFactory::get_widget_derived("graph_tree_win", _graph_tree_window);
@@ -348,6 +351,15 @@ App::quit(Gtk::Window* dialog_parent)
if (quit)
Gtk::Main::quit();
+ try {
+ const std::string path = _world->conf().save(
+ _world->uri_map(), "ingen", "gui.ttl", Configuration::GUI);
+ cout << (Raul::fmt("Saved GUI settings to %1%\n") % path);
+ } catch (const std::exception& e) {
+ cerr << (Raul::fmt("Error saving GUI settings (%1%)\n")
+ % e.what());
+ }
+
return quit;
}
diff --git a/src/gui/GraphBox.cpp b/src/gui/GraphBox.cpp
index 6607ebbd..0401143c 100644
--- a/src/gui/GraphBox.cpp
+++ b/src/gui/GraphBox.cpp
@@ -554,8 +554,9 @@ GraphBox::event_save_as()
}
_app->world()->conf().set(
- "graph-folder",
+ "graph-directory",
_app->world()->forge().alloc(dialog.get_current_folder()));
+
break;
}
}
diff --git a/src/gui/LoadGraphWindow.cpp b/src/gui/LoadGraphWindow.cpp
index 760463ea..a547c279 100644
--- a/src/gui/LoadGraphWindow.cpp
+++ b/src/gui/LoadGraphWindow.cpp
@@ -197,6 +197,10 @@ LoadGraphWindow::ok_clicked()
_graph.reset();
hide();
+
+ _app->world()->conf().set(
+ "graph-directory",
+ _app->world()->forge().alloc(get_current_folder()));
}
void