--- a
+++ b/cfgui/confmain.cpp
@@ -0,0 +1,342 @@
+/* Copyright (C) 2016 J.F.Dockes
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+#include <QApplication>
+#include <QString>
+#include <QCloseEvent>
+#include <QMenu>
+#include <QMenuBar>
+#include <QAction>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include "confgui.h"
+#include "conftree.h"
+#include "mainwindow.h"
+
+#include "libupnpp/upnpputils.hxx"
+
+using namespace std;
+using namespace confgui;
+
+#ifndef DATADIR
+#define DATADIR "/usr/share/upmpdcli"
+#endif
+string g_datadir(DATADIR "/");
+
+static ConfSimple g_csdef, g_csout;
+static string g_outfile;
+
+void confPourInto(ConfSimple& dest, const ConfSimple& src)
+{
+ for (const auto& key : src.getSubKeys()) {
+ for (const auto& nm : src.getNames(key)) {
+ string val;
+ src.get(nm, val, key);
+ dest.set(nm, val, key);
+ //cerr << "confPourInto: set " << key << ":" << nm << endl;
+ }
+ }
+}
+
+#if 0
+static string qs2utf8s(const QString& qs)
+{
+ return string((const char *)qs.toUtf8());
+}
+#endif
+
+static string qs2locals(const QString& qs)
+{
+ return string((const char *)qs.toLocal8Bit());
+}
+
+static QString u8s2qs(const string us)
+{
+ return QString::fromUtf8(us.c_str());
+}
+
+/**
+ * A Gui-to-Data link class for ConfTree
+ * Has a subkey pointer member which makes it easy to change the
+ * current subkey for a number at a time.
+ */
+class ConfLinkCS : public confgui::ConfLinkRep {
+public:
+ ConfLinkCS(ConfNull *conf, ConfNull *confdef, const std::string& nm,
+ std::string *sk = 0)
+ : m_conf(conf), m_confdef(confdef), m_nm(nm), m_sk(sk) {
+ }
+ virtual ~ConfLinkCS() {
+ }
+
+ virtual bool set(const std::string& val) {
+ if (!m_conf)
+ return false;
+ bool ret = m_conf->set(m_nm, val, m_sk ? *m_sk : "");
+ if (!ret) {
+ std::cerr << "ConfLinkCS::set: failed for " << m_nm << std::endl;
+ } else {
+ cerr << "ConfLinkCS::set: " << (m_sk?*m_sk:string()) <<
+ "[" << m_nm << "] = " << val << endl;
+ }
+ return ret;
+ }
+ virtual bool get(std::string& val) {
+ // cerr << "conflinkCS: get value for " << m_nm << endl;
+ if (!m_conf)
+ return false;
+ std::string sk = m_sk ? *m_sk : "";
+ bool ret = m_conf->get(m_nm, val, sk);
+ if (!ret && m_confdef) {
+ // cerr << " no value from conf. Trying default\n";
+ ret = m_confdef->get(m_nm, val, sk);
+ if (!ret) {
+ // cerr << " no value from default either\n";
+ }
+ }
+ std::string sv = ret ? val : "no value";
+ //std::cerr << "ConfLinkimpl::get: [" << m_nm << "] sk [" << sk <<
+ //"] -> [" << sv << "]\n";
+ return ret;
+ }
+private:
+ ConfNull *m_conf;
+ ConfNull *m_confdef;
+ const std::string m_nm;
+ const std::string *m_sk;
+};
+
+
+class MyConfLinkFactCS : public confgui::ConfLinkFact {
+public:
+ MyConfLinkFactCS(ConfSimple *cs, ConfSimple *csdef)
+ : m_cs(cs), m_csdef(csdef) {
+ }
+ virtual ConfLink operator()(const QString& nm) {
+ ConfLinkRep *lnk = new ConfLinkCS(m_cs, m_csdef,
+ (const char *)nm.toUtf8());
+ return ConfLink(lnk);
+ }
+ ConfSimple *m_cs;
+ ConfSimple *m_csdef;
+};
+
+MainWindow::MainWindow(ConfTabsW *w)
+ : m_tabs(w)
+{
+ setCentralWidget(w);
+
+ QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+
+ QAction *act;
+ act = new QAction(tr("&Open"), this);
+ act->setShortcuts(QKeySequence::Open);
+ connect(act, &QAction::triggered, this, &MainWindow::open);
+ fileMenu->addAction(act);
+
+ act = new QAction(tr("&Save"), this);
+ act->setShortcuts(QKeySequence::Save);
+ connect(act, &QAction::triggered, this, &MainWindow::save);
+ fileMenu->addAction(act);
+
+ act = new QAction(tr("Save &As"), this);
+ connect(act, &QAction::triggered, this, &MainWindow::saveAs);
+ fileMenu->addAction(act);
+
+ act = new QAction(tr("&Quit"), this);
+ act->setShortcuts(QKeySequence::Quit);
+ connect(act, &QAction::triggered, this, &QWidget::close);
+ fileMenu->addAction(act);
+}
+
+bool MainWindow::open()
+{
+ QFileDialog dialog(this);
+ dialog.setWindowModality(Qt::WindowModal);
+ if (dialog.exec() != QDialog::Accepted)
+ return false;
+ string f = qs2locals(dialog.selectedFiles().first());
+ ConfSimple cs(f.c_str(), 1);
+ if (cs.ok()) {
+ confPourInto(g_csout, cs);
+ m_tabs->reloadPanels();
+ return true;
+ } else {
+ QMessageBox::warning(0, "upmpdcli-config", tr("File parse failed"));
+ }
+
+ return false;
+}
+
+bool MainWindow::saveAs()
+{
+ QFileDialog dialog(this);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.setAcceptMode(QFileDialog::AcceptSave);
+ if (dialog.exec() != QDialog::Accepted)
+ return false;
+ g_outfile = qs2locals(dialog.selectedFiles().first());
+ m_tabs->acceptChanges();
+ return saveToFile();
+}
+
+bool MainWindow::save()
+{
+ cerr << "MainWindow::save: outfile: " << g_outfile << endl;
+
+ if (g_outfile.empty()) {
+ return saveAs();
+ } else {
+ m_tabs->acceptChanges();
+ return saveToFile();
+ }
+}
+
+bool MainWindow::saveToFile()
+{
+ ConfSimple fconf(g_outfile.c_str());
+ fconf.holdWrites(true);
+ confPourInto(fconf, g_csout);
+ return fconf.holdWrites(false);
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+ // Should test for unsaved mods here
+ if (true) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+
+static char *thisprog;
+
+void Usage()
+{
+ cerr << "Usage: " << thisprog << " [configfile]\n";
+ exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ QCoreApplication::setOrganizationName("Upmpd.org");
+ QCoreApplication::setApplicationName("upmpdcli-config");
+ app.setQuitOnLastWindowClosed(true);
+
+ thisprog = *argv++; argc--;
+ if (argc > 1)
+ Usage();
+ if (argc) {
+ g_outfile = *argv++;argc--;
+ }
+
+ // Note: there are ultimately 3 ConfSimple objects which are used here:
+ // - A ConfSimple built from the distributed config file, from
+ // which *only the XML-formatted comments are used*. Any actual
+ // assignement is ignored.
+ // - A ConfSimple which is built from the assignments defined in
+ // the top-level XML text from above (*Not* from any actual
+ // assignment), and which provides the initial values displayed
+ // in the data entry elements.
+ // - A ConfSimple which corresponds to the output file. If the
+ // file is not initially empty, any current assignement will
+ // override the default value and be displayed.
+ // When the values are accepted, any value changed by the user in
+ // the dialogs will be set in the output confsimple. Unchanged
+ // values will do nothing, which means that we don't output
+ // assignements for default values, and that assignements
+ // initially present in the output file but unchanged are left
+ // undisturbed.
+
+ // Configuration file from the distribution, with the
+ // XML-formatted comments, used to create the appropriate data
+ // input objects, and supply them with initial values.
+ string reffile(path_cat(g_datadir, "upmpdcli.conf-dist"));
+ ConfSimple sconf(reffile.c_str(), 1);
+ if (!sconf.ok()) {
+ cerr << "Could not parse reference configuration: " <<
+ reffile << "\n";
+ exit(1);
+ }
+ stringstream stream;
+ bool ok = sconf.commentsAsXML(stream);
+ if (!ok) {
+ cerr << "Could not extract xml comments\n";
+ exit(1);
+ }
+
+ // This is a bit tricky because we need the address of the
+ // ConfSimple which will hold the defaults while we build the GUI
+ // (because/so-that it can be stored in the conflink objects), but
+ // it's not built yet (because we use the top level assignement
+ // lines in the output of the XML parser to do it). So we'll
+ // copy-assign the real confsimple to the provisional one once the
+ // GUI is built, and reload the defaults. Slightly inefficient,
+ // but does the job.
+ string data;
+ g_csout = ConfSimple(data);
+ MyConfLinkFactCS fact(&g_csout, &g_csdef);
+
+ string toptext;
+ ConfTabsW *w = xmlToConfGUI(stream.str(), toptext, &fact, 0);
+ if (!w) {
+ cerr << "Could not parse xml / create GUI\n";
+ return 1;
+ }
+ // toptext contains the commented out variable assignements. Parse
+ // it for defaults, assign to the defaults confsimple, and reload
+ // the values.
+ g_csdef = ConfSimple(toptext, true);
+ if (!g_csdef.ok()) {
+ cerr << "default config parse failed\n";
+ return 1;
+ }
+ w->reloadPanels();
+
+ // Find the network interfaces choice and load the interfaces names
+ ConfParamW *pw = w->findParamW("upnpiface");
+ if (pw == 0) {
+ cerr << "upnpiface not found\n";
+ } else {
+ ConfParamCStrW *cpw = dynamic_cast<ConfParamCStrW*>(pw);
+ if (cpw) {
+ vector<string> adapters;
+ UPnPP::getAdapterNames(adapters);
+ QStringList qadapters;
+ qadapters.push_back("");
+ for (unsigned int i = 0; i < adapters.size(); i++) {
+ qadapters.push_back(u8s2qs(adapters[i]));
+ }
+ cpw->setList(qadapters);
+ } else {
+ cerr << "upnpiface not a cstrl?\n";
+ }
+ }
+ w->hideButtons();
+ MainWindow mainWin(w);
+ mainWin.show();
+ return app.exec();
+}