FIX issue #19 where multiple widget values led to bad replacements in the command
[qgit4/redivivus.git] / src / inputdialog.cpp
blob5244e46befeb57fc1b33b716b6c6e1ab1f15a03e
1 #include "inputdialog.h"
2 #include "common.h"
3 #include <QLineEdit>
4 #include <QTextEdit>
5 #include <QComboBox>
6 #include <QGridLayout>
7 #include <QLabel>
8 #include <QDialogButtonBox>
9 #include <QPushButton>
10 #include <QCompleter>
11 #include <QListView>
12 #include <QStringListModel>
14 namespace QGit {
16 InputDialog::WidgetItem::WidgetItem() : widget(NULL)
20 void InputDialog::WidgetItem::init(QWidget* w, const char *name) {
21 widget = w;
22 prop_name = name;
25 QString parseString(const QString &value, const InputDialog::VariableMap &vars) {
26 if (value.startsWith('$')) return vars.value(value.mid(1), QString()).toString();
27 else return value;
29 QStringList parseStringList(const QString &value, const InputDialog::VariableMap &vars) {
30 QStringList values = value.split(',');
31 QStringList result;
32 for (QStringList::iterator it=values.begin(), end=values.end(); it!=end; ++it) {
33 if (it->startsWith('$')) result.append(vars.value(value.mid(1), QStringList()).toStringList());
34 else result.append(*it);
36 return result;
39 class RefNameValidator : public QValidator {
40 public:
41 RefNameValidator(bool allowEmpty=false, QObject *parent=0)
42 : QValidator(parent)
43 , invalid("[ ~^:\?*[]")
44 , allowEmpty(allowEmpty)
47 void fixup(QString& input) const;
48 State validate(QString & input, int & pos) const;
49 private:
50 const QRegExp invalid;
51 bool allowEmpty;
54 void RefNameValidator::fixup(QString &input) const
56 // remove invalid chars
57 input.replace(invalid, "");
58 input.replace("/.","/"); // no dot after slash
59 input.replace("..","."); // no two dots in a row
60 input.replace("//","/"); // no two slashes in a row
61 input.replace("@{", "@"); // no sequence @{
64 QValidator::State RefNameValidator::validate(QString &input, int &pos) const
66 // https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
67 // automatically remove invalid chars
68 QString front = input.left(pos); fixup(front);
69 QString rear = input.mid(pos); fixup(rear);
70 input = front + rear;
71 // keep cursor were it was
72 pos = front.length();
74 QString fixed(input); fixup(fixed);
75 if (fixed != input) return Invalid;
77 // empty string or single @ are not allowed
78 if ((input.isEmpty() && !allowEmpty) || input == "@")
79 return Intermediate;
80 return Acceptable;
84 InputDialog::InputDialog(const QString &cmd, const VariableMap &variables,
85 const QString &title, QWidget *parent, Qt::WindowFlags f)
86 : QDialog(parent, f)
87 , cmd(cmd)
89 this->setWindowTitle(title);
90 QGridLayout *layout = new QGridLayout(this);
92 QRegExp re("%(([a-z_]+)([[]([a-z ,]+)[]])?:)?([^%=]+)(=[^%]+)?%");
93 int start = 0;
94 int row = 0;
95 while ((start = re.indexIn(cmd, start)) != -1) {
96 const QString type = re.cap(2);
97 const QStringList opts = re.cap(4).split(',', QGIT_SPLITBEHAVIOR(SkipEmptyParts));
98 const QString name = re.cap(5);
99 const QString value = re.cap(6).mid(1);
100 if (widgets.count(name)) { // widget already created
101 if (!type.isEmpty()) dbs("token must not be redefined: " + name);
102 continue;
105 WidgetItemPtr item (new WidgetItem());
106 item->start = start;
107 item->end = start = start + re.matchedLength();
109 if (type == "combobox") {
110 QComboBox *w = new QComboBox(this);
111 w->addItems(parseStringList(value, variables));
112 if (opts.contains("editable")) w->setEditable(true);
113 w->setMinimumWidth(100);
114 if (opts.contains("ref")) {
115 w->setValidator(new RefNameValidator(opts.contains("empty")));
116 validators.insert(name, w->validator());
117 connect(w, SIGNAL(editTextChanged(QString)), this, SLOT(validate()));
119 item->init(w, "currentText");
120 } else if (type == "listbox") {
121 QListView *w = new QListView(this);
122 w->setModel(new QStringListModel(parseStringList(value, variables)));
123 item->init(w, NULL);
124 } else if (type == "lineedit" || type == "") {
125 QLineEdit *w = new QLineEdit(this);
126 w->setText(parseString(value, variables));
127 QStringList values = parseStringList(value, variables);
128 if (!values.isEmpty()) // use default string list as
129 w->setCompleter(new QCompleter(values));
130 if (opts.contains("ref")) {
131 w->setValidator(new RefNameValidator(opts.contains("empty")));
132 validators.insert(name, w->validator());
133 connect(w, SIGNAL(textEdited(QString)), this, SLOT(validate()));
135 item->init(w, "text");
136 } else if (type == "textedit") {
137 QTextEdit *w = new QTextEdit(this);
138 w->setText(parseString(value, variables));
139 item->init(w, "plainText");
140 } else {
141 dbs("unknown widget type: " + type);
142 continue;
144 widgets.insert(name, item);
145 if (name.startsWith('_')) { // _name triggers hiding of label
146 layout->addWidget(item->widget, row, 1);
147 } else {
148 layout->addWidget(new QLabel(name + ":"), row, 0);
149 layout->addWidget(item->widget, row, 1);
151 ++row;
153 QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
154 layout->addWidget(buttons, row, 0, 1, 2);
155 okButton = buttons->button(QDialogButtonBox::Ok);
157 connect(okButton, SIGNAL(pressed()), this, SLOT(accept()));
158 connect(buttons->button(QDialogButtonBox::Cancel), SIGNAL(pressed()), this, SLOT(reject()));
159 validate();
162 QWidget *InputDialog::widget(const QString &token)
164 WidgetItemPtr item = widgets.value(token);
165 return item ? item->widget : NULL;
168 QVariant InputDialog::value(const QString &token) const
170 WidgetItemPtr item = widgets.value(token);
171 if (!item) {
172 dbs("unknown token: " + token);
173 return QString();
175 return item->widget->property(item->prop_name);
178 bool InputDialog::validate()
180 bool result=true;
181 for (QMap<QString, const QValidator*>::const_iterator
182 it=validators.begin(), end=validators.end(); result && it != end; ++it) {
183 QString val = value(it.key()).toString();
184 int pos=0;
185 if (it.value()->validate(val, pos) != QValidator::Acceptable)
186 result=false;
188 okButton->setEnabled(result);
189 return result;
192 QString InputDialog::replace(const VariableMap &variables) const
194 QString result = cmd;
195 int shift = 0, start = 0, len = 0; // will keep track of position shifts during replacements
196 for (WidgetMap::const_iterator it = widgets.begin(), end = widgets.end(); it != end; ++it) {
197 QString token = "%" + it.key() + "%";
198 WidgetItemPtr item = it.value();
199 start = item->start - shift;
200 len = item->end - item->start;
201 QString value = item->widget->property(item->prop_name).toString();
202 result.replace(start, len, value); // replace main token
203 shift += len - value.length();
204 result.replace(token, value); // replace all other occurences of %name%
206 for (VariableMap::const_iterator it=variables.begin(), end=variables.end(); it != end; ++it) {
207 QString token = "$" + it.key();
208 QString val = it.value().type() == QVariant::StringList ? it.value().toStringList().join(" ")
209 : it.value().toString();
210 result.replace(token, val);
212 return result;
215 } // namespace QGit