[dasher: 131/217] Add a shared XmlSettingsStore class and include support for it the Gtk version. When the --config=<f



commit 84bf9a4dfdd83ef1758cd2ae6bbf982712fb1c68
Author: lbaudoin <lbaudoin google com>
Date:   Mon Nov 23 19:52:59 2015 -0700

    Add a shared XmlSettingsStore class and include support for it the Gtk version.
    When the --config=<file> command line option is used the settings will be read from and saved to the XML 
file specified.
    If the file does not exist it will be created, if the '--config' option is not used the Gnome settings 
are used.

 Src/DasherCore/Makefile.am          |    4 +-
 Src/DasherCore/SettingsStore.h      |    3 +-
 Src/DasherCore/XmlSettingsStore.cpp |  202 +++++++++++++++++++++++++++++++++++
 Src/DasherCore/XmlSettingsStore.h   |   65 +++++++++++
 Src/Gtk2/DasherControl.cpp          |    5 +-
 Src/Gtk2/DasherControl.h            |    5 +-
 Src/Gtk2/GtkDasherControl.cpp       |   28 +++++-
 Src/Gtk2/dasher_main.h              |   10 +-
 Src/main.cc                         |   22 +++--
 9 files changed, 320 insertions(+), 24 deletions(-)
---
diff --git a/Src/DasherCore/Makefile.am b/Src/DasherCore/Makefile.am
index de21e23..5ce63c9 100644
--- a/Src/DasherCore/Makefile.am
+++ b/Src/DasherCore/Makefile.am
@@ -6,7 +6,9 @@ libdasherprefs_la_SOURCES = \
                Parameters.h \
                Parameters.cpp \
                SettingsStore.cpp \
-               SettingsStore.h 
+               SettingsStore.h \
+               XmlSettingsStore.h \
+               XmlSettingsStore.cpp
 
 libdashercore_la_SOURCES = \
                AbstractXMLParser.cpp \
diff --git a/Src/DasherCore/SettingsStore.h b/Src/DasherCore/SettingsStore.h
index 08d5e5d..69b0cd0 100644
--- a/Src/DasherCore/SettingsStore.h
+++ b/Src/DasherCore/SettingsStore.h
@@ -39,8 +39,7 @@ public:
 
   CSettingsStore();
 
-  virtual ~CSettingsStore() {
-  };
+  virtual ~CSettingsStore() = default;
 
   // New functions for event driven interface
 
diff --git a/Src/DasherCore/XmlSettingsStore.cpp b/Src/DasherCore/XmlSettingsStore.cpp
new file mode 100644
index 0000000..f9a6914
--- /dev/null
+++ b/Src/DasherCore/XmlSettingsStore.cpp
@@ -0,0 +1,202 @@
+#include "XmlSettingsStore.h"
+
+#include <iostream>
+#include <fstream>
+#include <string.h>
+#include <algorithm>
+
+namespace Dasher {
+namespace {
+
+template <typename T>
+bool Read(const std::map<std::string, T> values, const std::string& key,
+          T* value) {
+  auto i = values.find(key);
+  if (i == values.end()) {
+    return false;
+  }
+  *value = i->second;
+  return true;
+}
+
+}  // namespace
+
+XmlSettingsStore::XmlSettingsStore(const std::string& filename,
+                                   CMessageDisplay* pDisplay)
+    : AbstractXMLParser(pDisplay), filename_(filename) {}
+
+bool XmlSettingsStore::Load() {
+  bool result = true;
+  std::ifstream f(filename_);
+  if (f.good()) {
+    f.close();
+    if (!ParseFile(filename_, true /* user */)) {
+      m_pMsgs->Message("Failed to load the XML settings", true /* interrupt */);
+      result = false;
+    }
+  } else {
+    m_pMsgs->FormatMessageWithString("XML File not found: ", filename_.c_str());
+    result = false;
+  }
+  // Load all the settings or create defaults for the ones that don't exist.
+  // The superclass 'ParseFile' saves default settings if not found.
+  mode_ = EXPLICIT_SAVE;
+  LoadPersistent();
+  mode_ = SAVE_IMMEDIATELY;
+  return result;
+}
+
+bool XmlSettingsStore::LoadSetting(const std::string& key, bool* value) {
+  return Read(boolean_settings_, key, value);
+}
+
+bool XmlSettingsStore::LoadSetting(const std::string& key, long* value) {
+  return Read(long_settings_, key, value);
+}
+
+bool XmlSettingsStore::LoadSetting(const std::string& key, std::string* value) {
+  return Read(string_settings_, key, value);
+}
+
+void XmlSettingsStore::SaveSetting(const std::string& key, bool value) {
+  boolean_settings_[key] = value;
+  SaveIfNeeded();
+}
+
+void XmlSettingsStore::SaveSetting(const std::string& key, long value) {
+  long_settings_[key] = value;
+  SaveIfNeeded();
+}
+
+void XmlSettingsStore::SaveSetting(const std::string& key,
+                                   const std::string& value) {
+  string_settings_[key] = value;
+  SaveIfNeeded();
+}
+
+void XmlSettingsStore::SaveIfNeeded() {
+  modified_ = true;
+  if (mode_ == SAVE_IMMEDIATELY) {
+    Save();
+  }
+}
+
+bool XmlSettingsStore::Save() {
+  if (!modified_) {
+    return true;
+  }
+  try {
+    modified_ = false;
+    std::ofstream out;
+    out.open(filename_, std::ios::out | std::ios::trunc);
+    out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
+    out << "<settings>\n";
+    for (const auto& p : long_settings_) {
+      out << "<long name=\"" << p.first << "\" value=\"" << p.second
+          << "\"/>\n";
+    }
+    for (const auto& p : boolean_settings_) {
+      std::string value = p.second ? "True" : "False";
+      out << "<bool name=\"" << p.first << "\" value=\"" << value << "\"/>\n";
+    }
+    for (const auto& p : string_settings_) {
+      std::string attribute = p.second;
+      XML_Escape(attribute, true /* attribute */);
+      out << "<string name=\"" << p.first << "\" value=\"" << attribute
+          << "\"/>\n";
+    }
+    out << "</settings>\n";
+    out.close();
+  } catch (...) {
+    // TODO(localize).
+    m_pMsgs->Message("Failed to save the settings", true /* interrupt */);
+    return false;
+  }
+  return true;
+}
+
+bool XmlSettingsStore::GetNameAndValue(const XML_Char** attributes,
+                                       std::string* name, std::string* value) {
+  bool found_name = false, found_value = false;
+  for (; *attributes != nullptr; attributes += 2) {
+    if (strcmp(attributes[0], "value") == 0) {
+      if (found_value) {
+        m_pMsgs->Message(
+            "XML configuration: the 'value' attribute can only be present "
+            "once in a tag",
+            true /* interrupt */);
+        return false;
+      }
+      *value = attributes[1];
+      found_value = true;
+    } else if (strcmp(attributes[0], "name") == 0) {
+      if (found_name) {
+        m_pMsgs->Message(
+            "XML configuration: the 'name' attribute can only be present "
+            "once in a tag",
+            true /* interrupt */);
+        return false;
+      }
+      *name = attributes[1];
+      if (name->empty()) {
+        m_pMsgs->Message(
+            "XML configuration: the 'name' attribute can not be empty.",
+            true /* interrupt */);
+        return false;
+      }
+      found_name = true;
+    } else {
+      m_pMsgs->FormatMessageWithString(
+          "XML configuration: invalid attribute: %s", *attributes);
+      return false;
+    }
+  }
+  if (!found_name || !found_value) {
+    m_pMsgs->Message("XML configuration: missing name or value in a tag.",
+                     true /* interrupt */);
+    return false;
+  }
+  return true;
+}
+
+void XmlSettingsStore::XmlStartHandler(const XML_Char* element_name,
+                                       const XML_Char** attributes) {
+  std::string element = element_name;
+  if (element == "settings") {
+    return;
+  }
+  std::string name, value;
+  if (!GetNameAndValue(attributes, &name, &value)) {
+    return;
+  }
+  if (element == "string") {
+    string_settings_[name] = value;
+  } else if (element == "long") {
+    errno = 0;
+    long v = std::strtol(value.c_str(), nullptr, 0 /* base */);
+    if (errno != 0) {
+      m_pMsgs->FormatMessageWith2Strings(
+          "XML configuration: invalid numeric value '%s' for '%s'",
+          value.c_str(), name.c_str());
+    }
+    long_settings_[name] = v;
+  } else if (element == "bool") {
+
+    if (strcasecmp(value.c_str(), "true") == 0) {
+      boolean_settings_[name] = true;
+    } else if (strcasecmp(value.c_str(), "false") == 0) {
+      boolean_settings_[name] = false;
+    } else {
+      m_pMsgs->FormatMessageWith2Strings(
+          "XML configuration: boolean value should be 'true' or 'false' found "
+          "%s = '%s'",
+          name.c_str(), value.c_str());
+    }
+  } else {
+    m_pMsgs->FormatMessageWithString("XML configuration: unknown tag '%s'",
+                                     element.c_str());
+  }
+}
+
+void XmlSettingsStore::XmlEndHandler(const XML_Char* ) {}
+}  // namespace Dasher
diff --git a/Src/DasherCore/XmlSettingsStore.h b/Src/DasherCore/XmlSettingsStore.h
new file mode 100644
index 0000000..37c892b
--- /dev/null
+++ b/Src/DasherCore/XmlSettingsStore.h
@@ -0,0 +1,65 @@
+#ifndef XML_SETTING_STORE_H_
+#define XML_SETTING_STORE_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string>
+#include <map>
+
+#include "SettingsStore.h"
+#include "AbstractXMLParser.h"
+
+namespace Dasher {
+
+// This class is not thread-safe.
+class XmlSettingsStore : public Dasher::CSettingsStore, AbstractXMLParser {
+ public:
+  XmlSettingsStore(const std::string& filename, CMessageDisplay* pDisplay);
+  ~XmlSettingsStore() override = default;
+  // Load the XML file and fills in the default values needed.
+  // Returns true on success.
+  bool Load();
+  // Saves the XML file, returns true on success.
+  bool Save();
+
+ private:
+  bool LoadSetting(const std::string& Key, bool* Value) override;
+  bool LoadSetting(const std::string& Key, long* Value) override;
+  bool LoadSetting(const std::string& Key, std::string* Value) override;
+
+  void SaveSetting(const std::string& Key, bool Value) override;
+  void SaveSetting(const std::string& Key, long Value) override;
+  void SaveSetting(const std::string& Key, const std::string& Value) override;
+
+  void XmlStartHandler(const XML_Char* name, const XML_Char** atts) override;
+  virtual void XmlEndHandler(const XML_Char* name) override;
+
+  // Parses the tag attributes expecting exactly one 'value' and one 'name'
+  // attribute.
+  bool GetNameAndValue(const XML_Char** attributes, std::string* name,
+                       std::string* value);
+
+  // Save if the mode is 'SAVE_IMMEDIATELY', otherwise just set 'modified_' to
+  // true.
+  void SaveIfNeeded();
+
+  enum Mode {
+    // Save each time 'SaveSetting' is called.
+    SAVE_IMMEDIATELY,
+    // Save only when 'Save' is called.
+    EXPLICIT_SAVE
+  };
+
+  Mode mode_ = EXPLICIT_SAVE;
+  std::string filename_;
+  bool modified_ = false;
+  std::map<std::string, bool> boolean_settings_;
+  std::map<std::string, long> long_settings_;
+  std::map<std::string, std::string> string_settings_;
+};
+
+}  // namespace Dasher
+
+#endif  // XML_SETTING_STORE_H_
diff --git a/Src/Gtk2/DasherControl.cpp b/Src/Gtk2/DasherControl.cpp
index 80d9074..d49daa3 100644
--- a/Src/Gtk2/DasherControl.cpp
+++ b/Src/Gtk2/DasherControl.cpp
@@ -39,8 +39,9 @@ extern "C" gint canvas_expose_event(GtkWidget *widget, GdkEventExpose *event, gp
 static bool g_iTimeoutID = 0;
 
 // CDasherControl class definitions
-CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl)
- : CDashIntfScreenMsgs(new CGnomeSettingsStore()) {
+CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl,
+                               CSettingsStore* settings)
+ : CDashIntfScreenMsgs(settings) {
   m_pScreen = NULL;
 
   m_pDasherControl = pDasherControl;
diff --git a/Src/Gtk2/DasherControl.h b/Src/Gtk2/DasherControl.h
index 7eb9b67..8279f7c 100644
--- a/Src/Gtk2/DasherControl.h
+++ b/Src/Gtk2/DasherControl.h
@@ -48,8 +48,9 @@ public:
   /// \param pDasherControl Pointer to the GObject wrapper. This is
   /// needed so that we can emit signals from the GObject.
   ///
-
-  CDasherControl(GtkVBox * pVbox, GtkDasherControl * pDasherControl);
+  // The CDasherControl object takes ownership of 'settings'.
+  CDasherControl(GtkVBox * pVbox, GtkDasherControl * pDasherControl,
+                 CSettingsStore* settings);
   ~CDasherControl();
 
   // Event handlers
diff --git a/Src/Gtk2/GtkDasherControl.cpp b/Src/Gtk2/GtkDasherControl.cpp
index 28bdd76..5dca279 100644
--- a/Src/Gtk2/GtkDasherControl.cpp
+++ b/Src/Gtk2/GtkDasherControl.cpp
@@ -18,6 +18,7 @@
 // along with Dasher; if not, write to the Free Software 
 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
+#include "../DasherCore/XmlSettingsStore.h"
 #include "../Common/Common.h"
 
 #include "DasherControl.h"
@@ -25,6 +26,10 @@
 #include "custom_marshal.h"
 #include "dasher_editor.h"
 
+// TODO: find a better way to get access to the command line arguments.
+extern gchar* g_xml_file_location;
+
+
 struct _GtkDasherControlPrivate {
   CDasherControl *pControl;
   DasherEditor *pEditor;
@@ -106,11 +111,30 @@ gtk_dasher_control_class_init(GtkDasherControlClass *pClass) {
   // pClass->key_release_event = gtk_dasher_control_default_key_release_handler;
 }
 
+class XmlErrorDisplay : public CMessageDisplay {
+ public:
+  void Message(const std::string &strText, bool bInterrupt) override {
+    // TODO: decide if a pop-up dialog should be shown instead.
+    fputs(strText.c_str(), stderr);
+    fputs("\n", stderr);
+  }
+};
+
 static void 
 gtk_dasher_control_init(GtkDasherControl *pDasherControl) {
   GtkDasherControlPrivate *pPrivate = GTK_DASHER_CONTROL_GET_PRIVATE(pDasherControl);
-
-  pPrivate->pControl = new CDasherControl(&(pDasherControl->box), pDasherControl);
+  static XmlErrorDisplay display;
+  Dasher::CSettingsStore* settings;
+  if (g_xml_file_location == nullptr) {
+    settings = new CGnomeSettingsStore();
+  } else {
+    auto xml_settings = new XmlSettingsStore(g_xml_file_location, &display);
+    xml_settings->Load();
+    // Save the defaults if needed.
+    xml_settings->Save();
+    settings = xml_settings;
+  }
+  pPrivate->pControl = new CDasherControl(&(pDasherControl->box), pDasherControl, settings);
 
 //   g_signal_connect(G_OBJECT(pDasherControl), "key-press-event", 
G_CALLBACK(gtk_dasher_control_default_key_press_handler), pPrivate->pControl);
 //   g_signal_connect(G_OBJECT(pDasherControl), "key-release-event", 
G_CALLBACK(gtk_dasher_control_default_key_release_handler), pPrivate->pControl);
diff --git a/Src/Gtk2/dasher_main.h b/Src/Gtk2/dasher_main.h
index 61d3de6..46099ef 100644
--- a/Src/Gtk2/dasher_main.h
+++ b/Src/Gtk2/dasher_main.h
@@ -30,12 +30,10 @@ struct _DasherMainClass {
   void (*realized)(DasherMain *pDasherMain);
 };
 
-typedef struct _SCommandLine SCommandLine;
-
-struct _SCommandLine {
-  gchar *szFilename;
-  gchar *szAppStyle;
-  gchar *szOptions;
+struct SCommandLine {
+  gchar *szFilename = nullptr;
+  gchar *szAppStyle = nullptr;
+  gchar *szOptions = nullptr;
 };
 
 DasherMain *dasher_main_new(int *argc, char ***argv, SCommandLine *pCommandLine);
diff --git a/Src/main.cc b/Src/main.cc
index 460a528..4c0fbe2 100644
--- a/Src/main.cc
+++ b/Src/main.cc
@@ -133,6 +133,7 @@ void clean_up();
 //   }
 // }
 
+gchar* g_xml_file_location = nullptr;
 
 extern "C" gint main_key_snooper(GtkWidget *pWidget, GdkEventKey *pEvent, gpointer pUserData);
 
@@ -152,10 +153,6 @@ int main(int argc, char *argv[]) {
 
   SCommandLine sCommandLine;
 
-  sCommandLine.szFilename = NULL;
-  sCommandLine.szAppStyle = NULL;
-  sCommandLine.szOptions = NULL;
-
   gboolean do_option_help = false;
   static const GOptionEntry options[] = {
     //   {"timedata", 'w', 0, G_OPTION_ARG_NONE, &timedata, "Write basic timing information to stdout", 
NULL},
@@ -166,18 +163,22 @@ int main(int argc, char *argv[]) {
     {"appstyle", 'a', 0, G_OPTION_ARG_STRING, &(sCommandLine.szAppStyle), N_("Application style 
(traditional, direct, compose or fullscreen)"), "traditional"},
     // Note to translators: This is the help string for "--options"
     {"options", 'o', 0, G_OPTION_ARG_STRING, &(sCommandLine.szOptions), N_("Override stored options"), NULL},
-    // Note to translators: This is the help string for "--help-options"
+    {"config", 'c', 0, G_OPTION_ARG_STRING, &g_xml_file_location, N_("XML configuration file name"), NULL},
+        // Note to translators: This is the help string for "--help-options"
     {"help-options", 0, 0, G_OPTION_ARG_NONE, &do_option_help, N_("Describe \"--options\"."), NULL},
     {NULL}
   };
 
   //parse command line options
-  GOptionContext *goptcontext;
   // Note to translators: This is the "--help" description of dasher.
-  goptcontext = g_option_context_new(_("- A text input application honouring accessibility"));
+  GOptionContext *goptcontext = g_option_context_new(_("- A text input application honouring 
accessibility"));
   g_option_context_add_main_entries(goptcontext, options, GETTEXT_PACKAGE);
-  g_option_context_add_group(goptcontext, gtk_get_option_group (TRUE));
-  g_option_context_parse(goptcontext, &argc, &argv, NULL);
+  g_option_context_add_group(goptcontext, gtk_get_option_group(TRUE));
+  GError *error = nullptr;
+  if (!g_option_context_parse(goptcontext, &argc, &argv, &error)) {
+    g_print("option parsing failed: %s\n", error->message);
+    exit (1);
+  }
 
   // TODO: Check what happens here when goption has done its stuff
 
@@ -236,6 +237,9 @@ int main(int argc, char *argv[]) {
   // 11.
   clean_up();
 
+  if (g_xml_file_location != nullptr)
+    g_free(g_xml_file_location);
+
   return 0;
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]