X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fpreferences.cpp;h=3815d44c563eaf32d19a1af7a03e835a79ae079b;hb=c7cf14ac71346f76ae219ce67fb88c1bd34832e2;hp=7ebc4b30f94773683748307a677a6dec954d1bf1;hpb=0ab91da1b107e5a692b6c0829968bdd7fe0f21dc;p=inkscape.git diff --git a/src/preferences.cpp b/src/preferences.cpp index 7ebc4b30f..3815d44c5 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -3,50 +3,109 @@ */ /* Authors: * Krzysztof Kosiński + * Jon A. Cruz * - * Copyright (C) 2008 Authors + * Copyright (C) 2008,2009 Authors * * Released under GNU GPL. Read the file 'COPYING' for more information. */ -#include "preferences.h" -#include "preferences-skeleton.h" -#include "inkscape.h" -#include "xml/repr.h" -#include "xml/node-observer.h" +#include +#include #include #include #include #include -#include +#include +#include "preferences.h" +#include "preferences-skeleton.h" +#include "inkscape.h" +#include "xml/node-observer.h" +#include "xml/node-iterators.h" +#include "xml/attribute-record.h" #define PREFERENCES_FILE_NAME "preferences.xml" namespace Inkscape { +static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg ); +static void migrateDetails( Inkscape::XML::Document *from, Inkscape::XML::Document *to ); + +static Inkscape::XML::Document *migrateFromDoc = 0; + +// TODO clean up. Function copied from file.cpp: +// what gets passed here is not actually an URI... it is an UTF-8 encoded filename (!) +static void file_add_recent(gchar const *uri) +{ + if (!uri) { + g_warning("file_add_recent: uri == NULL"); + } else { + GtkRecentManager *recent = gtk_recent_manager_get_default(); + gchar *fn = g_filename_from_utf8(uri, -1, NULL, NULL, NULL); + if (fn) { + if (g_file_test(fn, G_FILE_TEST_EXISTS)) { + gchar *uriToAdd = g_filename_to_uri(fn, NULL, NULL); + if (uriToAdd) { + gtk_recent_manager_add_item(recent, uriToAdd); + g_free(uriToAdd); + } + } + g_free(fn); + } + } +} + + +// private inner class definition + +/** + * @brief XML - prefs observer bridge + * + * This is an XML node observer that watches for changes in the XML document storing the preferences. + * It is used to implement preference observers. + */ +class Preferences::PrefNodeObserver : public XML::NodeObserver { +public: + PrefNodeObserver(Observer &o, Glib::ustring const &filter) : + _observer(o), + _filter(filter) + {} + virtual ~PrefNodeObserver() {} + virtual void notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared, Util::ptr_shared); +private: + Observer &_observer; + Glib::ustring const _filter; +}; + Preferences::Preferences() : _prefs_basename(PREFERENCES_FILE_NAME), _prefs_dir(""), _prefs_filename(""), + _prefs_doc(0), + _errorHandler(0), _writable(false), - _prefs_doc(NULL) + _hasError(false) { // profile_path essentailly returns the argument prefixed by the profile directory. gchar *path = profile_path(NULL); _prefs_dir = path; g_free(path); - - path = profile_path(_prefs_basename.data()); + + path = profile_path(_prefs_basename.c_str()); _prefs_filename = path; g_free(path); - + + _loadDefaults(); _load(); } Preferences::~Preferences() { - // when the preferences are unloaded, save them - save(); + // delete all PrefNodeObservers + for (_ObsMap::iterator i = _observer_map.begin(); i != _observer_map.end(); ) { + delete (*i++).second; // avoids reference to a deleted key + } + // unref XML document Inkscape::GC::release(_prefs_doc); } @@ -65,120 +124,134 @@ void Preferences::_loadDefaults() * @brief Load the user's customized preferences * * Tries to load the user's preferences.xml file. If there is none, creates it. - * Displays dialog boxes on any errors. */ void Preferences::_load() { - _loadDefaults(); - Glib::ustring const not_saved = _("Inkscape will run with default settings, " - "and new settings will not be saved. "); - + "and new settings will not be saved. "); + // NOTE: After we upgrade to Glib 2.16, use Glib::ustring::compose - + // 1. Does the file exist? - if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_EXISTS)) { + if (!g_file_test(_prefs_filename.c_str(), G_FILE_TEST_EXISTS)) { // No - we need to create one. // Does the profile directory exist? - if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_EXISTS)) { + if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_EXISTS)) { // No - create the profile directory - if (g_mkdir(_prefs_dir.data(), 0755)) { + if (g_mkdir(_prefs_dir.c_str(), 0755)) { // the creation failed - //_errorDialog(Glib::ustring::compose(_("Cannot create profile directory %1."), + //_reportError(Glib::ustring::compose(_("Cannot create profile directory %1."), // Glib::filename_to_utf8(_prefs_dir)), not_saved); gchar *msg = g_strdup_printf(_("Cannot create profile directory %s."), - Glib::filename_to_utf8(_prefs_dir).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(_prefs_dir).c_str()); + _reportError(msg, not_saved); g_free(msg); return; } // create some subdirectories for user stuff char const *user_dirs[] = {"keys", "templates", "icons", "extensions", "palettes", NULL}; - for(int i=0; user_dirs[i]; ++i) { + for (int i=0; user_dirs[i]; ++i) { char *dir = profile_path(user_dirs[i]); g_mkdir(dir, 0755); g_free(dir); } - - } else if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_IS_DIR)) { + + } else if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_IS_DIR)) { // The profile dir is not actually a directory - //_errorDialog(Glib::ustring::compose(_("%1 is not a valid directory."), + //_reportError(Glib::ustring::compose(_("%1 is not a valid directory."), // Glib::filename_to_utf8(_prefs_dir)), not_saved); gchar *msg = g_strdup_printf(_("%s is not a valid directory."), - Glib::filename_to_utf8(_prefs_dir).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(_prefs_dir).c_str()); + _reportError(msg, not_saved); g_free(msg); return; } // The profile dir exists and is valid. - if (!g_file_set_contents(_prefs_filename.data(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) { + if (!g_file_set_contents(_prefs_filename.c_str(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) { // The write failed. - //_errorDialog(Glib::ustring::compose(_("Failed to create the preferences file %1."), + //_reportError(Glib::ustring::compose(_("Failed to create the preferences file %1."), // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("Failed to create the preferences file %s."), - Glib::filename_to_utf8(_prefs_filename).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(_prefs_filename).c_str()); + _reportError(msg, not_saved); g_free(msg); return; } - + + if ( migrateFromDoc ) { + migrateDetails( migrateFromDoc, _prefs_doc ); + } + // The prefs file was just created. // We can return now and skip the rest of the load process. _writable = true; return; } - + // Yes, the pref file exists. + Glib::ustring errMsg; + Inkscape::XML::Document *prefs_read = loadImpl( _prefs_filename, errMsg ); + + if ( prefs_read ) { + // Merge the loaded prefs with defaults. + _prefs_doc->root()->mergeFrom(prefs_read->root(), "id"); + Inkscape::GC::release(prefs_read); + _writable = true; + } else { + _reportError(errMsg, not_saved); + } +} + +//_reportError(msg, not_saved); +static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg ) +{ // 2. Is it a regular file? - if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_IS_REGULAR)) { - //_errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a regular file."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); + if (!g_file_test(prefsFilename.c_str(), G_FILE_TEST_IS_REGULAR)) { gchar *msg = g_strdup_printf(_("The preferences file %s is not a regular file."), - Glib::filename_to_utf8(_prefs_filename).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } - + // 3. Is the file readable? gchar *prefs_xml = NULL; gsize len = 0; - if (!g_file_get_contents(_prefs_filename.data(), &prefs_xml, &len, NULL)) { - //_errorDialog(Glib::ustring::compose(_("The preferences file %1 could not be read."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); + if (!g_file_get_contents(prefsFilename.c_str(), &prefs_xml, &len, NULL)) { gchar *msg = g_strdup_printf(_("The preferences file %s could not be read."), - Glib::filename_to_utf8(_prefs_filename).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } + // 4. Is it valid XML? Inkscape::XML::Document *prefs_read = sp_repr_read_mem(prefs_xml, len, NULL); g_free(prefs_xml); if (!prefs_read) { - //_errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a valid XML document."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("The preferences file %s is not a valid XML document."), - Glib::filename_to_utf8(_prefs_filename).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } + // 5. Basic sanity check: does the root element have a correct name? if (strcmp(prefs_read->root()->name(), "inkscape")) { - //_errorDialog(Glib::ustring::compose(_("The file %1 is not a valid Inkscape preferences file."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("The file %s is not a valid Inkscape preferences file."), - Glib::filename_to_utf8(_prefs_filename).data()); - _errorDialog(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); Inkscape::GC::release(prefs_read); - return; + return 0; } - - // Merge the loaded prefs with defaults. - _prefs_doc->root()->mergeFrom(prefs_read->root(), "id"); - Inkscape::GC::release(prefs_read); - _writable = true; + + return prefs_read; +} + +static void migrateDetails( Inkscape::XML::Document *from, Inkscape::XML::Document *to ) +{ + // TODO pull in additional prefs with more granularity + to->root()->mergeFrom(from->root(), "id"); } /** @@ -186,303 +259,566 @@ void Preferences::_load() */ void Preferences::save() { - if (!_writable) return; // no-op if the prefs file is not writable - - // sp_repr_save_file uses utf-8 instead of the glib filename encoding. - // I don't know why filenames are kept in utf-8 in Inkscape and then - // converted to filename encoding when necessary through sepcial functions - // - wouldn't it be easier to keep things in the encoding they are supposed - // to be in? - Glib::ustring utf8name = Glib::filename_from_utf8(_prefs_filename); - if (utf8name.empty()) return; - sp_repr_save_file(_prefs_doc, utf8name.data()); + // no-op if the prefs file is not writable + if (_writable) { + // sp_repr_save_file uses utf-8 instead of the glib filename encoding. + // I don't know why filenames are kept in utf-8 in Inkscape and then + // converted to filename encoding when necessary through special functions + // - wouldn't it be easier to keep things in the encoding they are supposed + // to be in? + + // No, it would not. There are many reasons, one key reason being that the + // rest of GTK+ is explicitly UTF-8. From an engineering standpoint, keeping + // the filesystem encoding would change things from a one-to-many problem to + // instead be a many-to-many problem. Also filesystem encoding can change + // from one run of the program to the next, so can not be stored. + // There are many other factors, so ask if you would like to learn them. - JAC + Glib::ustring utf8name = Glib::filename_to_utf8(_prefs_filename); + if (!utf8name.empty()) { + sp_repr_save_file(_prefs_doc, utf8name.c_str()); + } + } } -void Preferences::addPrefsObserver(Inkscape::XML::NodeObserver *observer) +bool Preferences::getLastError( Glib::ustring& primary, Glib::ustring& secondary ) { - _prefs_doc->addSubtreeObserver(*observer); + bool result = _hasError; + if ( _hasError ) { + primary = _lastErrPrimary; + secondary = _lastErrSecondary; + _hasError = false; + _lastErrPrimary.clear(); + _lastErrSecondary.clear(); + } else { + primary.clear(); + secondary.clear(); + } + return result; } +void Preferences::migrate( std::string const& legacyDir, std::string const& prefdir ) +{ + int mode = S_IRWXU; +#ifdef S_IRGRP + mode |= S_IRGRP; +#endif +#ifdef S_IXGRP + mode |= S_IXGRP; +#endif +#ifdef S_IXOTH + mode |= S_IXOTH; +#endif + if ( g_mkdir_with_parents(prefdir.c_str(), mode) == -1 ) { + } else { + } + gchar * oldPrefFile = g_build_filename(legacyDir.c_str(), PREFERENCES_FILE_NAME, NULL); + if (oldPrefFile) { + if (g_file_test(oldPrefFile, G_FILE_TEST_EXISTS)) { + Glib::ustring errMsg; + Inkscape::XML::Document *oldPrefs = loadImpl( oldPrefFile, errMsg ); + if (oldPrefs) { + Glib::ustring docId("documents"); + Glib::ustring recentId("recent"); + Inkscape::XML::Node *node = oldPrefs->root(); + Inkscape::XML::Node *child = 0; + Inkscape::XML::Node *recentNode = 0; + if (node->attribute("version")) { + node->setAttribute("version", "1"); + } + for (child = node->firstChild(); child; child = child->next()) { + if (docId == child->attribute("id")) { + for (child = child->firstChild(); child; child = child->next()) { + if (recentId == child->attribute("id")) { + recentNode = child; + for (child = child->firstChild(); child; child = child->next()) { + gchar const* uri = child->attribute("uri"); + if (uri) { + file_add_recent(uri); + } + } + break; + } + } + break; + } + } -// Now for the meat. -// Most of the logic is similar to former prefs-utils.cpp + if (recentNode) { + while (recentNode->firstChild()) { + recentNode->removeChild(recentNode->firstChild()); + } + } + migrateFromDoc = oldPrefs; + //Inkscape::GC::release(oldPrefs); + oldPrefs = 0; + } else { + g_warning( "%s", errMsg.c_str() ); + } + } + g_free(oldPrefFile); + oldPrefFile = 0; + } +} +// Now for the meat. /** - * @brief Check for the existence of a given pref key - * @param pref_key Preference key to check - * @return True if the key exists, false otherwise + * @brief Get names of all entries in the specified path + * @param path Preference path to query + * @return A vector containing all entries in the given directory */ -bool Preferences::exists(Glib::ustring const &pref_key) +std::vector Preferences::getAllEntries(Glib::ustring const &path) { - return _getNode(pref_key) != NULL; + std::vector temp; + Inkscape::XML::Node *node = _getNode(path, false); + if (node) { + // argh - purge this Util::List nonsense from XML classes fast + Inkscape::Util::List alist = node->attributeList(); + for (; alist; ++alist) { + temp.push_back( Entry(path + '/' + g_quark_to_string(alist->key), static_cast(alist->value.pointer())) ); + } + } + return temp; } /** - * @brief Get the number of sub-preferences of a given pref - * @param pref_key Preference key to check - * @return Number of sub-preferences - * - * Note: This does not count attributes, only child preferences. + * @brief Get the paths to all subdirectories of the specified path + * @param path Preference path to query + * @return A vector containing absolute paths to all subdirectories in the given path */ -unsigned int Preferences::childCount(Glib::ustring const &pref_key) +std::vector Preferences::getAllDirs(Glib::ustring const &path) { - Inkscape::XML::Node *node = _getNode(pref_key); - return ( node ? node->childCount() : 0 ); + std::vector temp; + Inkscape::XML::Node *node = _getNode(path, false); + if (node) { + for (Inkscape::XML::NodeSiblingIterator i = node->firstChild(); i; ++i) { + temp.push_back(path + '/' + i->attribute("id")); + } + } + return temp; } -/** - * @brief Get the key of the n-th sub-preference of the specified pref - * @param father_key Parent key - * @param n The zero-based index of the pref key to retrieve - * @return The key of the n-th sub-preference - */ -Glib::ustring Preferences::getNthChild(Glib::ustring const &father_key, unsigned int n) -{ - Inkscape::XML::Node *node = _getNode(father_key), *child; - if (!node) return ""; - child = node->nthChild(n); - if (!child) return ""; - if (child->attribute("id")) { - Glib::ustring child_key = father_key; - child_key += '.'; - child_key += child->attribute("id"); - return child_key; - } - return ""; +// getter methods + +Preferences::Entry const Preferences::getEntry(Glib::ustring const &pref_path) +{ + gchar const *v; + _getRawValue(pref_path, v); + return Entry(pref_path, v); } +// setter methods /** - * @brief Create the preference with the specified key - * @return True if the node was created, false if it already existed - * - * This method is redundant, because the setters automatically create prefs - * if they don't already exist. It is only left to accomodate some legacy code - * which manipulates the DOM of the preferences file directly. + * @brief Set a boolean attribute of a preference + * @param pref_path Path of the preference to modify + * @param value The new value of the pref attribute */ -bool Preferences::create(Glib::ustring const &pref_key) +void Preferences::setBool(Glib::ustring const &pref_path, bool value) { - if (_getNode(pref_key)) return false; - _getNode(pref_key, true); - return true; + /// @todo Boolean values should be stored as "true" and "false", + /// but this is not possible due to an interaction with event contexts. + /// Investigate this in depth. + _setRawValue(pref_path, ( value ? "1" : "0" )); } -// getter methods - /** - * @brief Get a boolean attribute of a preference - * @param pref_key Key of he preference to retrieve - * @param attr Attribute to retrieve - * @param def The default value to return if the preference is not set - * @return The retrieved value + * @brief Set an integer attribute of a preference + * @param pref_path Path of the preference to modify + * @param value The new value of the pref attribute */ -bool Preferences::getBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool def) +void Preferences::setInt(Glib::ustring const &pref_path, int value) { - Inkscape::XML::Node *node = _getNode(pref_key); - if (!node) return def; - gchar const *rawstr = node->attribute(attr.data()); - if(!rawstr || !rawstr[0]) return def; - Glib::ustring str = rawstr; - - // This is to handle legacy preferences using ints as booleans - if (str == "true" || str == "1") return true; - return false; + gchar intstr[32]; + g_snprintf(intstr, 32, "%d", value); + _setRawValue(pref_path, intstr); } - /** - * @brief Get an integer attribute of a preference - * @param pref_key Key of he preference to retrieve - * @param attr Attribute to retrieve - * @param def The default value to return if the preference is not set - * @return The retrieved value + * @brief Set a floating point attribute of a preference + * @param pref_path Path of the preference to modify + * @param value The new value of the pref attribute */ -int Preferences::getInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int def) +void Preferences::setDouble(Glib::ustring const &pref_path, double value) { - Inkscape::XML::Node *node = _getNode(pref_key); - if (!node) return def; - gchar const *rawstr = node->attribute(attr.data()); - if (!rawstr || !rawstr[0]) return def; - Glib::ustring str = rawstr; - // Protection against leftover getInt calls when the value is in fact a boolean - if (str == "true") return 1; - if (str == "false") return 0; - return atoi(str.data()); + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, value); + _setRawValue(pref_path, buf); } -int Preferences::getIntLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, int def, int min, int max) +void Preferences::setColor(Glib::ustring const &pref_path, guint32 value) { - int value = getInt(pref_key, attr, def); - return ( value >= min && value <= max ? value : def); + gchar buf[16]; + g_snprintf(buf, 16, "#%08x", value); + _setRawValue(pref_path, buf); } /** - * @brief Get a floating point attribute of a preference - * @param pref_key Key of he preference to retrieve - * @param attr Attribute to retrieve - * @param def The default value to return if the preference is not set - * @return The retrieved value + * @brief Set a string attribute of a preference + * @param pref_path Path of the preference to modify + * @param value The new value of the pref attribute */ -double Preferences::getDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double def) +void Preferences::setString(Glib::ustring const &pref_path, Glib::ustring const &value) { - Inkscape::XML::Node *node = _getNode(pref_key); - if (!node) return def; - gchar const *str = node->attribute(attr.data()); - if (!str) return def; - return g_ascii_strtod(str, NULL); + _setRawValue(pref_path, value.c_str()); } -double Preferences::getDoubleLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, double def, double min, double max) +void Preferences::setStyle(Glib::ustring const &pref_path, SPCSSAttr *style) { - double value = getDouble(pref_key, attr, def); - return ( value >= min && value <= max ? value : def); + gchar *css_str = sp_repr_css_write_string(style); + _setRawValue(pref_path, css_str); + g_free(css_str); } -/** - * @brief Get a string attribute of a preference - * @param pref_key Key of he preference to retrieve - * @param attr Attribute to retrieve - * @param def The default value to return if the preference is not set - * @return The retrieved value - */ -Glib::ustring Preferences::getString(Glib::ustring const &pref_key, Glib::ustring const &attr) +void Preferences::mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style) { - Inkscape::XML::Node *node = _getNode(pref_key); - if (!node) return ""; - gchar const *str = node->attribute(attr.data()); - if (!str) return ""; - return Glib::ustring(str); + SPCSSAttr *current = getStyle(pref_path); + sp_repr_css_merge(current, style); + gchar *css_str = sp_repr_css_write_string(current); + _setRawValue(pref_path, css_str); + g_free(css_str); + sp_repr_css_attr_unref(current); } -// setter methods +// Observer stuff +namespace { /** - * @brief Set a boolean attribute of a preference - * @param pref_key Key of the preference to modify - * @param attr Attribute to set - * @param value The new value of the pref attribute + * @brief Structure that holds additional information for registered Observers */ -void Preferences::setBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool value) +struct _ObserverData { + Inkscape::XML::Node *_node; ///< Node at which the wrapping PrefNodeObserver is registered + bool _is_attr; ///< Whether this Observer watches a single attribute +}; + +} // anonymous namespace + +Preferences::Observer::Observer(Glib::ustring const &path) : + observed_path(path) { - /// @todo Boolean values should be stored as "true" and "false", - /// but this is not possible ude to an interaction with event contexts. - /// Investigate this in depth. - Inkscape::XML::Node *node = _getNode(pref_key, true); - node->setAttribute(attr.data(), ( value ? "1" : "0" )); } -/** - * @brief Set an integer attribute of a preference - * @param pref_key Key of the preference to modify - * @param attr Attribute to set - * @param value The new value of the pref attribute - */ -void Preferences::setInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int value) +Preferences::Observer::~Observer() { - Inkscape::XML::Node *node = _getNode(pref_key, true); - gchar intstr[32]; - g_snprintf(intstr, 32, "%d", value); - node->setAttribute(attr.data(), intstr); + // on destruction remove observer to prevent invalid references + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->removeObserver(*this); } -/** - * @brief Set a floating point attribute of a preference - * @param pref_key Key of the preference to modify - * @param attr Attribute to set - * @param value The new value of the pref attribute - */ -void Preferences::setDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double value) +void Preferences::PrefNodeObserver::notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared, Util::ptr_shared new_value) { - Inkscape::XML::Node *node = _getNode(pref_key, true); - sp_repr_set_svg_double(node, attr.data(), value); - /* - gchar dblstr[32]; - g_snprintf(dblstr, 32, "%g", value); - node->setAttribute(attr, dblstr); - */ + // filter out attributes we don't watch + gchar const *attr_name = g_quark_to_string(name); + if ( _filter.empty() || (_filter == attr_name) ) { + _ObserverData *d = static_cast<_ObserverData*>(Preferences::_get_pref_observer_data(_observer)); + Glib::ustring notify_path = _observer.observed_path; + + if (!d->_is_attr) { + std::vector path_fragments; + notify_path.reserve(256); // this will make appending operations faster + + // walk the XML tree, saving each of the id attributes in a vector + // we terminate when we hit the observer's attachment node, because the path to this node + // is already stored in notify_path + for (XML::NodeParentIterator n = &node; static_cast(n) != d->_node; ++n) { + path_fragments.push_back(n->attribute("id")); + } + // assemble the elements into a path + for (std::vector::reverse_iterator i = path_fragments.rbegin(); i != path_fragments.rend(); ++i) { + notify_path.push_back('/'); + notify_path.append(*i); + } + + // append attribute name + notify_path.push_back('/'); + notify_path.append(attr_name); + } + + Entry const val = Preferences::_create_pref_value(notify_path, static_cast(new_value.pointer())); + _observer.notify(val); + } } /** - * @brief Set a string attribute of a preference - * @param pref_key Key of the preference to modify - * @param attr Attribute to set - * @param value The new value of the pref attribute + * @brief Find the XML node to observe */ -void Preferences::setString(Glib::ustring const &pref_key, Glib::ustring const &attr, Glib::ustring const &value) +XML::Node *Preferences::_findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create) { - Inkscape::XML::Node *node = _getNode(pref_key, true); - node->setAttribute(attr.data(), value.data()); + // first assume that the last path element is an entry. + _keySplit(pref_path, node_key, attr_key); + + // find the node corresponding to the "directory". + Inkscape::XML::Node *node = _getNode(node_key, create), *child; + for (child = node->firstChild(); child; child = child->next()) { + // If there is a node with id corresponding to the attr key, + // this means that the last part of the path is actually a key (folder). + // Change values accordingly. + if (attr_key == child->attribute("id")) { + node = child; + attr_key = ""; + node_key = pref_path; + break; + } + } + return node; } +void Preferences::addObserver(Observer &o) +{ + // prevent adding the same observer twice + if ( _observer_map.find(&o) == _observer_map.end() ) { + Glib::ustring node_key, attr_key; + Inkscape::XML::Node *node; + node = _findObserverNode(o.observed_path, node_key, attr_key, false); + if (node) { + // set additional data + _ObserverData *priv_data = new _ObserverData; + priv_data->_node = node; + priv_data->_is_attr = !attr_key.empty(); + o._data = static_cast(priv_data); + + _observer_map[&o] = new PrefNodeObserver(o, attr_key); + + // if we watch a single pref, we want to receive notifications only for a single node + if (priv_data->_is_attr) { + node->addObserver( *(_observer_map[&o]) ); + } else { + node->addSubtreeObserver( *(_observer_map[&o]) ); + } + } + } +} + +void Preferences::removeObserver(Observer &o) +{ + // prevent removing an observer which was not added + if ( _observer_map.find(&o) != _observer_map.end() ) { + Inkscape::XML::Node *node = static_cast<_ObserverData*>(o._data)->_node; + _ObserverData *priv_data = static_cast<_ObserverData*>(o._data); + o._data = NULL; + + if (priv_data->_is_attr) { + node->removeObserver( *(_observer_map[&o]) ); + } else { + node->removeSubtreeObserver( *(_observer_map[&o]) ); + } + + delete priv_data; + delete _observer_map[&o]; + _observer_map.erase(&o); + } +} + + /** * @brief Get the XML node corresponding to the given pref key * @param pref_key Preference key (path) to get * @param create Whether to create the corresponding node if it doesn't exist + * @param separator The character used to separate parts of the pref key * @return XML node corresponding to the specified key * - * The separator for key components is '.' (a dot). Derived from former - * inkscape_get_repr(). + * Derived from former inkscape_get_repr(). Private because it assumes that the backend is + * a flat XML file, which may not be the case e.g. if we are using GConf (in future). */ Inkscape::XML::Node *Preferences::_getNode(Glib::ustring const &pref_key, bool create) { - Inkscape::XML::Node *node = _prefs_doc->root(), *child = NULL; - gchar **splits = g_strsplit(pref_key.data(), ".", 0); - int part_i = 0; - - while(splits[part_i]) { - for (child = node->firstChild(); child; child = child->next()) - if (!strcmp(splits[part_i], child->attribute("id"))) break; - - // If the previous loop found a matching key, child now contains the node - // matching the processed key part. If no node was found then it is NULL. - if (!child) { - if (create) { - // create the rest of the key - while(splits[part_i]) { - child = node->document()->createElement("group"); - child->setAttribute("id", splits[part_i]); - node->appendChild(child); - - ++part_i; - node = child; + // verify path + g_assert( pref_key.at(0) == '/' ); + // No longer necessary, can cause problems with input devices which have a dot in the name + // g_assert( pref_key.find('.') == Glib::ustring::npos ); + + Inkscape::XML::Node *node = _prefs_doc->root(); + Inkscape::XML::Node *child = NULL; + gchar **splits = g_strsplit(pref_key.c_str(), "/", 0); + + if ( splits ) { + for (int part_i = 0; splits[part_i]; ++part_i) { + // skip empty path segments + if (!splits[part_i][0]) { + continue; + } + + for (child = node->firstChild(); child; child = child->next()) { + if (!strcmp(splits[part_i], child->attribute("id"))) { + break; + } + } + + // If the previous loop found a matching key, child now contains the node + // matching the processed key part. If no node was found then it is NULL. + if (!child) { + if (create) { + // create the rest of the key + while(splits[part_i]) { + child = node->document()->createElement("group"); + child->setAttribute("id", splits[part_i]); + node->appendChild(child); + + ++part_i; + node = child; + } + g_strfreev(splits); + return node; + } else { + return NULL; } - g_strfreev(splits); - return node; - } else { - return NULL; } + + node = child; } - - ++part_i; - node = child; + g_strfreev(splits); } - g_strfreev(splits); return node; } +void Preferences::_getRawValue(Glib::ustring const &path, gchar const *&result) +{ + // create node and attribute keys + Glib::ustring node_key, attr_key; + _keySplit(path, node_key, attr_key); + + // retrieve the attribute + Inkscape::XML::Node *node = _getNode(node_key, false); + if ( node == NULL ) { + result = NULL; + } else { + gchar const *attr = node->attribute(attr_key.c_str()); + if ( attr == NULL ) { + result = NULL; + } else { + result = attr; + } + } +} + +void Preferences::_setRawValue(Glib::ustring const &path, gchar const *value) +{ + // create node and attribute keys + Glib::ustring node_key, attr_key; + _keySplit(path, node_key, attr_key); + + // set the attribute + Inkscape::XML::Node *node = _getNode(node_key, true); + node->setAttribute(attr_key.c_str(), value); +} + +// The _extract* methods are where the actual wrok is done - they define how preferences are stored +// in the XML file. + +bool Preferences::_extractBool(Entry const &v) +{ + gchar const *s = static_cast(v._value); + if ( !s[0] || !strcmp(s, "0") || !strcmp(s, "false") ) { + return false; + } else { + return true; + } +} + +int Preferences::_extractInt(Entry const &v) +{ + gchar const *s = static_cast(v._value); + if ( !strcmp(s, "true") ) { + return true; + } else if ( !strcmp(s, "false") ) { + return false; + } else { + return atoi(s); + } +} + +double Preferences::_extractDouble(Entry const &v) +{ + gchar const *s = static_cast(v._value); + return g_ascii_strtod(s, NULL); +} + +Glib::ustring Preferences::_extractString(Entry const &v) +{ + return Glib::ustring(static_cast(v._value)); +} -void Preferences::_errorDialog(Glib::ustring const &msg, Glib::ustring const &secondary) +guint32 Preferences::_extractColor(Entry const &v) { - if (Preferences::use_gui) { - Gtk::MessageDialog err( - msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true); - err.set_secondary_text(secondary); - err.run(); + gchar const *s = static_cast(v._value); + std::istringstream hr(s); + guint32 color; + if (s[0] == '#') { + hr.ignore(1); + hr >> std::hex >> color; } else { - g_message("%s", msg.data()); - g_message("%s", secondary.data()); + hr >> color; + } + return color; +} + +SPCSSAttr *Preferences::_extractStyle(Entry const &v) +{ + SPCSSAttr *style = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string(style, static_cast(v._value)); + return style; +} + +SPCSSAttr *Preferences::_extractInheritedStyle(Entry const &v) +{ + // This is the dirtiest extraction method. Generally we ignore whatever was in v._value + // and just get the style using sp_repr_css_attr_inherited. To implement this in GConf, + // we'll have to walk up the tree and call sp_repr_css_attr_add_from_string + Glib::ustring node_key, attr_key; + _keySplit(v._pref_path, node_key, attr_key); + + Inkscape::XML::Node *node = _getNode(node_key, false); + return sp_repr_css_attr_inherited(node, attr_key.c_str()); +} + +// XML backend helper: Split the path into a node key and an attribute key. +void Preferences::_keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key) +{ + // everything after the last slash + attr_key = pref_path.substr(pref_path.rfind('/') + 1, Glib::ustring::npos); + // everything before the last slash + node_key = pref_path.substr(0, pref_path.rfind('/')); +} + +void Preferences::_reportError(Glib::ustring const &msg, Glib::ustring const &secondary) +{ + _hasError = true; + _lastErrPrimary = msg; + _lastErrSecondary = secondary; + if (_errorHandler) { + _errorHandler->handleError(msg, secondary); + } +} + +Preferences::Entry const Preferences::_create_pref_value(Glib::ustring const &path, void const *ptr) +{ + return Entry(path, ptr); +} + +void Preferences::setErrorHandler(ErrorReporter* handler) +{ + _errorHandler = handler; +} + +void Preferences::unload(bool save) +{ + if (_instance) + { + if (save) { + _instance->save(); + } + delete _instance; + _instance = NULL; } } -bool Preferences::use_gui = true; Preferences *Preferences::_instance = NULL; } // namespace Inkscape - + /* Local Variables: mode:c++ @@ -492,4 +828,4 @@ Preferences *Preferences::_instance = NULL; fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :