Switch to side-by-side view

--- a
+++ b/cfgui/confgui.cpp
@@ -0,0 +1,988 @@
+/* Copyright (C) 2005-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 "confgui.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+
+#include <qglobal.h>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QTabWidget>
+#include <QDialogButtonBox>
+#include <QFrame>
+#include <QListWidget>
+#include <QFileDialog>
+#include <QDebug>
+#include <QDir>
+#include <qobject.h>
+#include <qlayout.h>
+#include <qsize.h>
+#include <qsizepolicy.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qtooltip.h>
+#include <qlineedit.h>
+#include <qcheckbox.h>
+#include <qinputdialog.h>
+#include <qpushbutton.h>
+#include <qstringlist.h>
+#include <qcombobox.h>
+
+#include "smallut.h"
+
+#ifdef ENABLE_XMLCONF
+#include "picoxml.h"
+#endif
+
+using namespace std;
+
+namespace confgui {
+
+static const int spacing = 3;
+// left,top,right, bottom
+static QMargins margin(4,3,4,3);
+
+ConfTabsW::ConfTabsW(QWidget *parent, const QString& title,
+                     ConfLinkFact *fact)
+    : QDialog(parent), m_makelink(fact)
+{
+    setWindowTitle(title);
+    tabWidget = new QTabWidget;
+
+    buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
+                                     | QDialogButtonBox::Cancel);
+
+    QVBoxLayout *mainLayout = new QVBoxLayout;
+    mainLayout->setSpacing(spacing);
+    mainLayout->setContentsMargins(margin);
+    mainLayout->addWidget(tabWidget);
+    mainLayout->addWidget(buttonBox);
+    setLayout(mainLayout);
+
+    resize(QSize(500, 400).expandedTo(minimumSizeHint()));
+
+    connect(buttonBox, SIGNAL(accepted()), this, SLOT(acceptChanges()));
+    connect(buttonBox, SIGNAL(rejected()), this, SLOT(rejectChanges()));
+}
+
+void ConfTabsW::hideButtons()
+{
+    if (buttonBox)
+        buttonBox->hide();
+}
+
+void ConfTabsW::acceptChanges()
+{
+    cerr << "ConfTabsW::acceptChanges()\n";
+    for (auto& entry : m_panels) {
+        entry->storeValues();
+    }
+    for (auto& entry : m_widgets) {
+        entry->storeValues();
+    }
+    emit sig_prefsChanged();
+    if (!buttonBox->isHidden())
+        close();
+}
+
+void ConfTabsW::rejectChanges()
+{
+    cerr << "ConfTabsW::rejectChanges()\n";
+    reloadPanels();
+    if (!buttonBox->isHidden())
+        close();
+}
+
+void ConfTabsW::reloadPanels()
+{
+    for (auto& entry : m_panels) {
+        entry->loadValues();
+    }
+    for (auto& entry : m_widgets) {
+        entry->loadValues();
+    }
+}
+
+int ConfTabsW::addPanel(const QString& title)
+{
+    ConfPanelW *w = new ConfPanelW(this);
+    m_panels.push_back(w);
+    return tabWidget->addTab(w, title);
+}
+
+int ConfTabsW::addForeignPanel(ConfPanelWIF* w, const QString& title)
+{
+    m_widgets.push_back(w);
+    QWidget *qw = dynamic_cast<QWidget *>(w);
+    if (qw == 0) {
+        qDebug() << "Can't cast panel to QWidget";
+        abort();
+    }
+    return tabWidget->addTab(qw, title);
+}
+
+void ConfTabsW::setCurrentIndex(int idx)
+{
+    if (tabWidget) {
+        tabWidget->setCurrentIndex(idx);
+    }
+}
+
+ConfParamW *ConfTabsW::addParam(
+    int tabindex, ParamType tp, const QString& varname,
+    const QString& label, const QString& tooltip,
+    int ival, int maxval, const QStringList* sl)
+{
+    ConfLink lnk = (*m_makelink)(varname);
+
+    ConfPanelW *panel = (ConfPanelW*)tabWidget->widget(tabindex);
+    if (panel == 0) {
+        return 0;
+    }
+
+    ConfParamW *cp = 0;
+    switch (tp) {
+    case CFPT_BOOL:
+        cp = new ConfParamBoolW(varname, this, lnk, label, tooltip, ival);
+        break;
+    case CFPT_INT: {
+        size_t v = (size_t)sl;
+        int v1 = (v & 0xffffffff);
+        cp = new ConfParamIntW(varname, this, lnk, label, tooltip, ival,
+                               maxval, v1);
+        break;
+    }
+    case CFPT_STR:
+        cp = new ConfParamStrW(varname, this, lnk, label, tooltip);
+        break;
+    case CFPT_CSTR:
+        cp = new ConfParamCStrW(varname, this, lnk, label, tooltip, *sl);
+        break;
+    case CFPT_FN:
+        cp = new ConfParamFNW(varname, this, lnk, label, tooltip, ival);
+        break;
+    case CFPT_STRL:
+        cp = new ConfParamSLW(varname, this, lnk, label, tooltip);
+    case CFPT_DNL:
+        cp = new ConfParamDNLW(varname, this, lnk, label, tooltip);
+        break;
+    case CFPT_CSTRL:
+        cp = new ConfParamCSLW(varname, this, lnk, label, tooltip, *sl);
+        break;
+
+    }
+    panel->addWidget(cp);
+    m_params.push_back(cp);
+    return cp;
+}
+
+ConfParamW *ConfTabsW::findParamW(const QString& varname)
+{
+    for (vector<ConfParamW *>::iterator it = m_params.begin();
+            it != m_params.end(); it++) {
+        if (!varname.compare((*it)->getVarName())) {
+            return *it;
+        }
+    }
+    return 0;
+}
+void ConfTabsW::endOfList(int tabindex)
+{
+    ConfPanelW *panel = (ConfPanelW*)tabWidget->widget(tabindex);
+    if (panel == 0) 
+        return;
+    panel->endOfList();
+}
+
+bool ConfTabsW::enableLink(ConfParamW* boolw, ConfParamW* otherw, bool revert)
+{
+    if (std::find(m_params.begin(), m_params.end(), boolw) == m_params.end() ||
+            std::find(m_params.begin(), m_params.end(), otherw) ==
+        m_params.end()) {
+        cerr << "ConfTabsW::enableLink: param not found\n";
+        return false;
+    }
+    ConfParamBoolW *bw = dynamic_cast<ConfParamBoolW*>(boolw);
+    if (bw == 0) {
+        cerr << "ConfTabsW::enableLink: not a boolw\n";
+        return false;
+    }
+    otherw->setEnabled(revert ? !bw->m_cb->isChecked() : bw->m_cb->isChecked());
+    if (revert) {
+        connect(bw->m_cb, SIGNAL(toggled(bool)),
+                otherw, SLOT(setDisabled(bool)));
+    } else {
+        connect(bw->m_cb, SIGNAL(toggled(bool)),
+                otherw, SLOT(setEnabled(bool)));
+    }
+    return true;
+}
+
+ConfPanelW::ConfPanelW(QWidget *parent)
+    : QWidget(parent)
+{
+    m_vboxlayout = new QVBoxLayout(this);
+    m_vboxlayout->setSpacing(spacing);
+    m_vboxlayout->setAlignment(Qt::AlignTop);
+    m_vboxlayout->setContentsMargins(margin);
+}
+
+void ConfPanelW::addWidget(QWidget *w)
+{
+    m_vboxlayout->addWidget(w);
+    m_widgets.push_back(w);
+}
+
+void ConfPanelW::endOfList()
+{
+    m_vboxlayout->addStretch(2);
+}
+
+void ConfPanelW::storeValues()
+{
+    for (vector<QWidget *>::iterator it = m_widgets.begin();
+            it != m_widgets.end(); it++) {
+        ConfParamW *p = (ConfParamW*)*it;
+        p->storeValue();
+    }
+}
+
+void ConfPanelW::loadValues()
+{
+    for (vector<QWidget *>::iterator it = m_widgets.begin();
+            it != m_widgets.end(); it++) {
+        ConfParamW *p = (ConfParamW*)*it;
+        p->loadValue();
+    }
+}
+static QString myGetFileName(bool isdir, QString caption = QString(),
+                             bool filenosave = false);
+
+static QString myGetFileName(bool isdir, QString caption, bool filenosave)
+{
+    QFileDialog dialog(0, caption);
+
+    if (isdir) {
+        dialog.setFileMode(QFileDialog::Directory);
+        dialog.setOptions(QFileDialog::ShowDirsOnly);
+    } else {
+        dialog.setFileMode(QFileDialog::AnyFile);
+        if (filenosave) {
+            dialog.setAcceptMode(QFileDialog::AcceptOpen);
+        } else {
+            dialog.setAcceptMode(QFileDialog::AcceptSave);
+        }
+    }
+    dialog.setViewMode(QFileDialog::List);
+    QFlags<QDir::Filter> flags = QDir::NoDotAndDotDot | QDir::Hidden;
+    if (isdir) {
+        flags |= QDir::Dirs;
+    } else {
+        flags |= QDir::Dirs | QDir::Files;
+    }
+    dialog.setFilter(flags);
+
+    if (dialog.exec() == QDialog::Accepted) {
+        return dialog.selectedFiles().value(0);
+    }
+    return QString();
+}
+
+void ConfParamW::setValue(const QString& value)
+{
+    if (m_fsencoding) {
+        m_cflink->set(string((const char *)value.toLocal8Bit()));
+    } else {
+        m_cflink->set(string((const char *)value.toUtf8()));
+    }
+}
+
+void ConfParamW::setValue(int value)
+{
+    char buf[30];
+    sprintf(buf, "%d", value);
+    m_cflink->set(string(buf));
+}
+
+void ConfParamW::setValue(bool value)
+{
+    char buf[30];
+    sprintf(buf, "%d", value);
+    m_cflink->set(string(buf));
+}
+
+extern void setSzPol(QWidget *w, QSizePolicy::Policy hpol,
+                     QSizePolicy::Policy vpol,
+                     int hstretch, int vstretch);
+
+void setSzPol(QWidget *w, QSizePolicy::Policy hpol,
+              QSizePolicy::Policy vpol,
+              int hstretch, int vstretch)
+{
+    QSizePolicy policy(hpol, vpol);
+    policy.setHorizontalStretch(hstretch);
+    policy.setVerticalStretch(vstretch);
+    policy.setHeightForWidth(w->sizePolicy().hasHeightForWidth());
+    w->setSizePolicy(policy);
+}
+
+bool ConfParamW::createCommon(const QString& lbltxt, const QString& tltptxt)
+{
+    m_hl = new QHBoxLayout(this);
+    m_hl->setSpacing(spacing);
+    m_hl->setContentsMargins(margin);
+
+    QLabel *tl = new QLabel(this);
+    setSzPol(tl, QSizePolicy::Preferred, QSizePolicy::Fixed, 0, 0);
+    tl->setText(lbltxt);
+    tl->setToolTip(tltptxt);
+
+    m_hl->addWidget(tl);
+
+    return true;
+}
+
+ConfParamIntW::ConfParamIntW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt,
+    int minvalue, int maxvalue, int defaultvalue)
+    : ConfParamW(varnm, parent, cflink), m_defaultvalue(defaultvalue)
+{
+    if (!createCommon(lbltxt, tltptxt)) {
+        return;
+    }
+
+    m_sb = new QSpinBox(this);
+    m_sb->setMinimum(minvalue);
+    m_sb->setMaximum(maxvalue);
+    setSzPol(m_sb, QSizePolicy::Fixed, QSizePolicy::Fixed, 0, 0);
+    m_hl->addWidget(m_sb);
+
+    QFrame *fr = new QFrame(this);
+    setSzPol(fr, QSizePolicy::Preferred, QSizePolicy::Fixed, 0, 0);
+    m_hl->addWidget(fr);
+
+    loadValue();
+}
+
+void ConfParamIntW::storeValue()
+{
+    if (m_origvalue != m_sb->value()) {
+        setValue(m_sb->value());
+    }
+}
+
+void ConfParamIntW::loadValue()
+{
+    string s;
+    if (m_cflink->get(s)) {
+        m_sb->setValue(m_origvalue = atoi(s.c_str()));
+    } else {
+        m_sb->setValue(m_origvalue = m_defaultvalue);
+    }
+}
+
+ConfParamStrW::ConfParamStrW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt)
+    : ConfParamW(varnm, parent, cflink)
+{
+    if (!createCommon(lbltxt, tltptxt)) {
+        return;
+    }
+
+    m_le = new QLineEdit(this);
+    setSzPol(m_le, QSizePolicy::Preferred, QSizePolicy::Fixed, 1, 0);
+
+    m_hl->addWidget(m_le);
+
+    loadValue();
+}
+
+void ConfParamStrW::storeValue()
+{
+    if (m_origvalue.compare(m_le->text())) {
+        setValue(m_le->text());
+    }
+}
+
+void ConfParamStrW::loadValue()
+{
+    string s;
+    m_cflink->get(s);
+    if (m_fsencoding) {
+        m_le->setText(m_origvalue = QString::fromLocal8Bit(s.c_str()));
+    } else {
+        m_le->setText(m_origvalue = QString::fromUtf8(s.c_str()));
+    }
+}
+
+ConfParamCStrW::ConfParamCStrW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt, const QStringList& sl)
+    : ConfParamW(varnm, parent, cflink)
+{
+    if (!createCommon(lbltxt, tltptxt)) {
+        return;
+    }
+    m_cmb = new QComboBox(this);
+    m_cmb->setEditable(false);
+    m_cmb->insertItems(0, sl);
+
+    setSzPol(m_cmb, QSizePolicy::Preferred, QSizePolicy::Fixed, 1, 0);
+
+    m_hl->addWidget(m_cmb);
+
+    loadValue();
+}
+
+void ConfParamCStrW::setList(const QStringList& sl)
+{
+    m_cmb->clear();
+    m_cmb->insertItems(0, sl);
+    loadValue();
+}
+
+void ConfParamCStrW::storeValue()
+{
+    if (m_origvalue.compare(m_cmb->currentText())) {
+        setValue(m_cmb->currentText());
+    }
+}
+
+void ConfParamCStrW::loadValue()
+{
+    string s;
+    m_cflink->get(s);
+    QString cs;
+    if (m_fsencoding) {
+        cs = QString::fromLocal8Bit(s.c_str());
+    } else {
+        cs = QString::fromUtf8(s.c_str());
+    }
+
+    for (int i = 0; i < m_cmb->count(); i++) {
+        if (!cs.compare(m_cmb->itemText(i))) {
+            m_cmb->setCurrentIndex(i);
+            break;
+        }
+    }
+    m_origvalue = m_cmb->currentText();
+}
+
+ConfParamBoolW::ConfParamBoolW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt, bool deflt)
+    : ConfParamW(varnm, parent, cflink), m_dflt(deflt)
+{
+    // No createCommon because the checkbox has a label
+    m_hl = new QHBoxLayout(this);
+    m_hl->setSpacing(spacing);
+    m_hl->setContentsMargins(margin);
+
+    m_cb = new QCheckBox(lbltxt, this);
+    setSzPol(m_cb, QSizePolicy::Fixed, QSizePolicy::Fixed, 0, 0);
+    m_cb->setToolTip(tltptxt);
+    m_hl->addWidget(m_cb);
+
+    QFrame *fr = new QFrame(this);
+    setSzPol(fr, QSizePolicy::Preferred, QSizePolicy::Fixed, 1, 0);
+    m_hl->addWidget(fr);
+
+    loadValue();
+}
+
+void ConfParamBoolW::storeValue()
+{
+    if (m_origvalue != m_cb->isChecked()) {
+        setValue(m_cb->isChecked());
+    }
+}
+
+void ConfParamBoolW::loadValue()
+{
+    string s;
+    if (!m_cflink->get(s)) {
+        m_origvalue = m_dflt;
+    } else {
+        m_origvalue = stringToBool(s);
+    }
+    m_cb->setChecked(m_origvalue);
+}
+
+ConfParamFNW::ConfParamFNW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt, bool isdir)
+    : ConfParamW(varnm, parent, cflink), m_isdir(isdir)
+{
+    if (!createCommon(lbltxt, tltptxt)) {
+        return;
+    }
+
+    m_fsencoding = true;
+
+    m_le = new QLineEdit(this);
+    m_le->setMinimumSize(QSize(150, 0));
+    setSzPol(m_le, QSizePolicy::Preferred, QSizePolicy::Fixed, 1, 0);
+    m_hl->addWidget(m_le);
+
+    m_pb = new QPushButton(this);
+
+    QString text = tr("Choose");
+    m_pb->setText(text);
+    int width = m_pb->fontMetrics().boundingRect(text).width() + 15;
+    m_pb->setMaximumWidth(width);
+    setSzPol(m_pb, QSizePolicy::Minimum, QSizePolicy::Fixed, 0, 0);
+    m_hl->addWidget(m_pb);
+
+    loadValue();
+    QObject::connect(m_pb, SIGNAL(clicked()), this, SLOT(showBrowserDialog()));
+}
+
+void ConfParamFNW::storeValue()
+{
+    if (m_origvalue.compare(m_le->text())) {
+        setValue(m_le->text());
+    }
+}
+
+void ConfParamFNW::loadValue()
+{
+    string s;
+    m_cflink->get(s);
+    m_le->setText(m_origvalue = QString::fromLocal8Bit(s.c_str()));
+}
+
+void ConfParamFNW::showBrowserDialog()
+{
+    QString s = myGetFileName(m_isdir);
+    if (!s.isEmpty()) {
+        m_le->setText(s);
+    }
+}
+
+class SmallerListWidget: public QListWidget {
+public:
+    SmallerListWidget(QWidget *parent)
+        : QListWidget(parent) {}
+    virtual QSize sizeHint() const {
+        return QSize(150, 40);
+    }
+};
+
+ConfParamSLW::ConfParamSLW(
+    const QString& varnm, QWidget *parent, ConfLink cflink,
+    const QString& lbltxt, const QString& tltptxt)
+    : ConfParamW(varnm, parent, cflink)
+{
+    // Can't use createCommon here cause we want the buttons below the label
+    m_hl = new QHBoxLayout(this);
+    m_hl->setSpacing(spacing);
+    m_hl->setContentsMargins(margin);
+
+    QVBoxLayout *vl1 = new QVBoxLayout();
+    vl1->setSpacing(spacing);
+    vl1->setContentsMargins(margin);
+    QHBoxLayout *hl1 = new QHBoxLayout();
+    hl1->setSpacing(spacing);
+    hl1->setContentsMargins(margin);
+
+    QLabel *tl = new QLabel(this);
+    setSzPol(tl, QSizePolicy::Preferred, QSizePolicy::Fixed, 0, 0);
+    tl->setText(lbltxt);
+    tl->setToolTip(tltptxt);
+    vl1->addWidget(tl);
+
+    QPushButton *pbA = new QPushButton(this);
+    QString text = tr("+");
+    pbA->setText(text);
+    int width = pbA->fontMetrics().boundingRect(text).width() + 15;
+    pbA->setMaximumWidth(width);
+    setSzPol(pbA, QSizePolicy::Minimum, QSizePolicy::Fixed, 0, 0);
+    hl1->addWidget(pbA);
+
+    QPushButton *pbD = new QPushButton(this);
+    text = tr("-");
+    pbD->setText(text);
+    width = pbD->fontMetrics().boundingRect(text).width() + 15;
+    pbD->setMaximumWidth(width);
+    setSzPol(pbD, QSizePolicy::Minimum, QSizePolicy::Fixed, 0, 0);
+    hl1->addWidget(pbD);
+
+    vl1->addLayout(hl1);
+    m_hl->addLayout(vl1);
+
+    m_lb = new SmallerListWidget(this);
+    m_lb->setSelectionMode(QAbstractItemView::ExtendedSelection);
+    setSzPol(m_lb, QSizePolicy::Preferred, QSizePolicy::Preferred, 1, 1);
+    m_hl->addWidget(m_lb);
+
+    setSzPol(this, QSizePolicy::Preferred, QSizePolicy::Preferred, 1, 1);
+    loadValue();
+    QObject::connect(pbA, SIGNAL(clicked()), this, SLOT(showInputDialog()));
+    QObject::connect(pbD, SIGNAL(clicked()), this, SLOT(deleteSelected()));
+}
+
+void ConfParamSLW::storeValue()
+{
+    vector<string> ls;
+    for (int i = 0; i < m_lb->count(); i++) {
+        // General parameters are encoded as utf-8. File names as
+        // local8bit There is no hope for 8bit file names anyway
+        // except for luck: the original encoding is unknown.
+        QString text = m_lb->item(i)->text();
+        if (m_fsencoding) {
+            ls.push_back((const char *)(text.toLocal8Bit()));
+        } else {
+            ls.push_back((const char *)(text.toUtf8()));
+        }
+    }
+    string s;
+    stringsToString(ls, s);
+    if (s.compare(m_origvalue)) {
+        m_cflink->set(s);
+    }
+}
+
+void ConfParamSLW::loadValue()
+{
+    m_origvalue.clear();
+    m_cflink->get(m_origvalue);
+    vector<string> ls;
+    stringToStrings(m_origvalue, ls);
+    QStringList qls;
+    for (vector<string>::const_iterator it = ls.begin(); it != ls.end(); it++) {
+        if (m_fsencoding) {
+            qls.push_back(QString::fromLocal8Bit(it->c_str()));
+        } else {
+            qls.push_back(QString::fromUtf8(it->c_str()));
+        }
+    }
+    m_lb->clear();
+    m_lb->insertItems(0, qls);
+}
+
+void ConfParamSLW::showInputDialog()
+{
+    bool ok;
+    QString s = QInputDialog::getText(this,
+                                      "", // title
+                                      "", // label,
+                                      QLineEdit::Normal, // EchoMode mode
+                                      "", // const QString & text
+                                      &ok);
+
+    if (ok && !s.isEmpty()) {
+        QList<QListWidgetItem *>items =
+            m_lb->findItems(s, Qt::MatchFixedString | Qt::MatchCaseSensitive);
+        if (items.empty()) {
+            m_lb->insertItem(0, s);
+            m_lb->sortItems();
+        }
+    }
+}
+
+void ConfParamSLW::deleteSelected()
+{
+    // We used to repeatedly go through the list and delete the first
+    // found selected item (then restart from the beginning). But it
+    // seems (probably depends on the qt version), that, when deleting
+    // a selected item, qt will keep the selection active at the same
+    // index (now containing the next item), so that we'd end up
+    // deleting the whole list.
+    //
+    // Instead, we now build a list of indices, and delete it starting
+    // from the top so as not to invalidate lower indices
+
+    vector<int> idxes;
+    for (int i = 0; i < m_lb->count(); i++) {
+        if (m_lb->item(i)->isSelected()) {
+            idxes.push_back(i);
+        }
+    }
+    for (vector<int>::reverse_iterator it = idxes.rbegin();
+            it != idxes.rend(); it++) {
+        QListWidgetItem *item = m_lb->takeItem(*it);
+        emit entryDeleted(item->text());
+        delete item;
+    }
+}
+
+// "Add entry" dialog for a file name list
+void ConfParamDNLW::showInputDialog()
+{
+    QString s = myGetFileName(true);
+    if (!s.isEmpty()) {
+        QList<QListWidgetItem *>items =
+            m_lb->findItems(s, Qt::MatchFixedString | Qt::MatchCaseSensitive);
+        if (items.empty()) {
+            m_lb->insertItem(0, s);
+            m_lb->sortItems();
+            QList<QListWidgetItem *>items =
+                m_lb->findItems(s, Qt::MatchFixedString | Qt::MatchCaseSensitive);
+            if (m_lb->selectionMode() == QAbstractItemView::SingleSelection &&
+                    !items.empty()) {
+                m_lb->setCurrentItem(*items.begin());
+            }
+        }
+    }
+}
+
+// "Add entry" dialog for a constrained string list
+void ConfParamCSLW::showInputDialog()
+{
+    bool ok;
+    QString s = QInputDialog::getItem(this,  // parent
+                                      "", // title
+                                      "", // label,
+                                      m_sl, // items,
+                                      0, // current = 0
+                                      false, // editable = true,
+                                      &ok);
+
+    if (ok && !s.isEmpty()) {
+        QList<QListWidgetItem *>items =
+            m_lb->findItems(s, Qt::MatchFixedString | Qt::MatchCaseSensitive);
+        if (items.empty()) {
+            m_lb->insertItem(0, s);
+            m_lb->sortItems();
+        }
+    }
+}
+
+
+
+#ifdef ENABLE_XMLCONF
+
+static QString u8s2qs(const std::string us)
+{
+    return QString::fromUtf8(us.c_str());
+}
+
+static const string& mapfind(const string& nm, const map<string, string>& mp)
+{
+    static string strnull;
+    map<string, string>::const_iterator it;
+    it = mp.find(nm);
+    if (it == mp.end()) {
+        return strnull;
+    }
+    return it->second;
+}
+
+static string looksLikeAssign(const string& data)
+{
+    //LOGDEB("looksLikeAssign. data: [" << data << "]");
+    vector<string> toks;
+    stringToTokens(data, toks, "\n\r\t ");
+    if (toks.size() >= 2 && !toks[1].compare("=")) {
+        return toks[0];
+    }
+    return string();
+}
+
+ConfTabsW *xmlToConfGUI(const string& xml, string& toptext,
+                        ConfLinkFact* lnkf, QWidget *parent)
+{
+    //LOGDEB("xmlToConfGUI: [" << xml << "]");
+
+    class XMLToConfGUI : public PicoXMLParser {
+    public:
+        XMLToConfGUI(const string& x, ConfLinkFact *lnkf, QWidget *parent)
+            : PicoXMLParser(x), m_lnkfact(lnkf), m_parent(parent),
+              m_idx(0), m_hadTitle(false), m_hadGroup(false) {
+        }
+        virtual ~XMLToConfGUI() {}
+
+        virtual void startElement(const string& tagname,
+                                  const map<string, string>& attrs) {
+            if (!tagname.compare("var")) {
+                m_curvar = mapfind("name", attrs);
+                m_curvartp = mapfind("type", attrs);
+                m_curvarvals = mapfind("values", attrs);
+                //LOGDEB("Curvar: " << m_curvar);
+                if (m_curvar.empty() || m_curvartp.empty()) {
+                    throw std::runtime_error(
+                        "<var> with no name attribute or no type ! nm [" +
+                        m_curvar + "] tp [" + m_curvartp + "]");
+                } else {
+                    m_brief.clear();
+                    m_descr.clear();
+                }
+            } else if (!tagname.compare("filetitle") ||
+                       !tagname.compare("grouptitle")) {
+                m_other.clear();
+            }
+        }
+
+        virtual void endElement(const string& tagname) {
+            if (!tagname.compare("var")) {
+                if (!m_hadTitle) {
+                    m_w = new ConfTabsW(m_parent, "Teh title", m_lnkfact);
+                    m_hadTitle = true;
+                }
+                if (!m_hadGroup) {
+                    m_idx = m_w->addPanel("Group title");
+                    m_hadGroup = true;
+                }
+                ConfTabsW::ParamType paramtype;
+                if (!m_curvartp.compare("bool")) {
+                    paramtype = ConfTabsW::CFPT_BOOL;
+                } else if (!m_curvartp.compare("int")) {
+                    paramtype = ConfTabsW::CFPT_INT;
+                } else if (!m_curvartp.compare("string")) {
+                    paramtype = ConfTabsW::CFPT_STR;
+                } else if (!m_curvartp.compare("cstr")) {
+                    paramtype = ConfTabsW::CFPT_CSTR;
+                } else if (!m_curvartp.compare("cstrl")) {
+                    paramtype = ConfTabsW::CFPT_CSTRL;
+                } else if (!m_curvartp.compare("fn")) {
+                    paramtype = ConfTabsW::CFPT_FN;
+                } else if (!m_curvartp.compare("dfn")) {
+                    paramtype = ConfTabsW::CFPT_FN;
+                } else if (!m_curvartp.compare("strl")) {
+                    paramtype = ConfTabsW::CFPT_STRL;
+                } else if (!m_curvartp.compare("dnl")) {
+                    paramtype = ConfTabsW::CFPT_DNL;
+                } else {
+                    throw std::runtime_error("Bad type " + m_curvartp +
+                                             " for " + m_curvar);
+                }
+                rtrimstring(m_brief, " .");
+                switch (paramtype) {
+                case ConfTabsW::CFPT_BOOL: {
+                    int def = atoi(m_curvarvals.c_str());
+                    m_w->addParam(m_idx, paramtype, u8s2qs(m_curvar),
+                                  u8s2qs(m_brief), u8s2qs(m_descr), def);
+                    break;
+                }
+                case ConfTabsW::CFPT_INT: {
+                    vector<string> vals;
+                    stringToTokens(m_curvarvals, vals);
+                    int min = 0, max = 0, def = 0;
+                    if (vals.size() >= 3) {
+                        min = atoi(vals[0].c_str());
+                        max = atoi(vals[1].c_str());
+                        def = atoi(vals[2].c_str());
+                    }
+                    QStringList *sldef = 0;
+                    sldef = (QStringList*)(((char*)sldef) + def);
+                    m_w->addParam(m_idx, paramtype, u8s2qs(m_curvar),
+                                  u8s2qs(m_brief), u8s2qs(m_descr),
+                                  min, max, sldef);
+                    break;
+                }
+                case  ConfTabsW::CFPT_CSTR:
+                case ConfTabsW::CFPT_CSTRL: {
+                    vector<string> cstrl;
+                    stringToTokens(neutchars(m_curvarvals, "\n\r"), cstrl);
+                    QStringList qstrl;
+                    for (unsigned int i = 0; i < cstrl.size(); i++) {
+                        qstrl.push_back(u8s2qs(cstrl[i]));
+                    }
+                    m_w->addParam(m_idx, paramtype, u8s2qs(m_curvar),
+                                  u8s2qs(m_brief), u8s2qs(m_descr),
+                                  0, 0, &qstrl);
+                    break;
+                }
+                default:
+                    m_w->addParam(m_idx, paramtype, u8s2qs(m_curvar),
+                                  u8s2qs(m_brief), u8s2qs(m_descr));
+                }
+            } else if (!tagname.compare("filetitle")) {
+                m_w = new ConfTabsW(m_parent, u8s2qs(m_other), m_lnkfact);
+                m_hadTitle = true;
+                m_other.clear();
+            } else if (!tagname.compare("grouptitle")) {
+                if (!m_hadTitle) {
+                    m_w = new ConfTabsW(m_parent, "Teh title", m_lnkfact);
+                    m_hadTitle = true;
+                }
+                // Get rid of "parameters" in the title, it's not interesting
+                // and this makes our tab headers smaller.
+                string ps{"parameters"};
+                string::size_type pos = m_other.find(ps);
+                if (pos != string::npos) {
+                    m_other = m_other.replace(pos, ps.size(), "");
+                }
+                m_idx = m_w->addPanel(u8s2qs(m_other));
+                m_hadGroup = true;
+                m_other.clear();
+            } else if (!tagname.compare("descr")) {
+            } else if (!tagname.compare("brief")) {
+                m_brief = neutchars(m_brief, "\n\r");
+            }
+        }
+
+        virtual void characterData(const string& data) {
+            if (!tagStack().back().compare("brief")) {
+                m_brief += data;
+            } else if (!tagStack().back().compare("descr")) {
+                m_descr += data;
+            } else if (!tagStack().back().compare("filetitle") ||
+                       !tagStack().back().compare("grouptitle")) {
+                // We don't want \n in there
+                m_other += neutchars(data, "\n\r");
+                m_other += " ";
+            } else if (!tagStack().back().compare("confcomments")) {
+                string nvarname = looksLikeAssign(data);
+                if (!nvarname.empty() && nvarname.compare(m_curvar)) {
+                    cerr << "Var assigned [" << nvarname << "] mismatch "
+                         "with current variable [" << m_curvar << "]\n";
+                }
+                m_toptext += data;
+            }
+        }
+
+        ConfTabsW *m_w;
+
+        ConfLinkFact *m_lnkfact;
+        QWidget *m_parent;
+        int m_idx;
+        string m_curvar;
+        string m_curvartp;
+        string m_curvarvals;
+        string m_brief;
+        string m_descr;
+        string m_other;
+        string m_toptext;
+        bool m_hadTitle;
+        bool m_hadGroup;
+    };
+
+    XMLToConfGUI parser(xml, lnkf, parent);
+    try {
+        if (!parser.parse()) {
+            cerr << "Parse failed: " << parser.getReason() << endl;
+            return 0;
+        }
+    } catch (const std::runtime_error& e) {
+        cerr << e.what() << endl;
+        return 0;
+    }
+    toptext = parser.m_toptext;
+    return parser.m_w;
+}
+
+#endif /* ENABLE_XMLCONF */
+
+} // Namespace confgui