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>
13 
14 namespace QGit {
15 
WidgetItem()16 InputDialog::WidgetItem::WidgetItem() : widget(NULL)
17 {
18 }
19 
init(QWidget * w,const char * name)20 void InputDialog::WidgetItem::init(QWidget* w, const char *name) {
21 	widget = w;
22 	prop_name = name;
23 }
24 
parseString(const QString & value,const InputDialog::VariableMap & vars)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;
28 }
parseStringList(const QString & value,const InputDialog::VariableMap & vars)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);
35 	}
36 	return result;
37 }
38 
39 class RefNameValidator : public QValidator {
40 public:
RefNameValidator(bool allowEmpty=false,QObject * parent=0)41 	RefNameValidator(bool allowEmpty=false, QObject *parent=0)
42 	    : QValidator(parent)
43 	    , invalid("[ ~^:\?*[]")
44 	    , allowEmpty(allowEmpty)
45 	{}
46 
47 	void fixup(QString& input) const;
48 	State validate(QString & input, int & pos) const;
49 private:
50 	const QRegExp invalid;
51 	bool allowEmpty;
52 };
53 
fixup(QString & input) const54 void RefNameValidator::fixup(QString &input) const
55 {
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 @{
62 }
63 
validate(QString & input,int & pos) const64 QValidator::State RefNameValidator::validate(QString &input, int &pos) const
65 {
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();
73 
74 	QString fixed(input); fixup(fixed);
75 	if (fixed != input) return Invalid;
76 
77 	// empty string or single @ are not allowed
78 	if ((input.isEmpty() && !allowEmpty) || input == "@")
79 		return Intermediate;
80 	return Acceptable;
81 }
82 
83 
InputDialog(const QString & cmd,const VariableMap & variables,const QString & title,QWidget * parent,Qt::WindowFlags f)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)
88 {
89 	this->setWindowTitle(title);
90 	QGridLayout *layout = new QGridLayout(this);
91 
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(',', QString::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;
103 		}
104 
105 		WidgetItemPtr item (new WidgetItem());
106 		item->start = start;
107 		item->end = start = start + re.matchedLength();
108 
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()));
118 			}
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()));
134 			}
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;
143 		}
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);
150 		}
151 		++row;
152 	}
153 	QDialogButtonBox *buttons =	new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
154 	layout->addWidget(buttons, row, 0, 1, 2);
155 	okButton = buttons->button(QDialogButtonBox::Ok);
156 
157 	connect(okButton, SIGNAL(pressed()), this, SLOT(accept()));
158 	connect(buttons->button(QDialogButtonBox::Cancel), SIGNAL(pressed()), this, SLOT(reject()));
159 	validate();
160 }
161 
widget(const QString & token)162 QWidget *InputDialog::widget(const QString &token)
163 {
164 	WidgetItemPtr item = widgets.value(token);
165 	return item ? item->widget : NULL;
166 }
167 
value(const QString & token) const168 QVariant InputDialog::value(const QString &token) const
169 {
170 	WidgetItemPtr item = widgets.value(token);
171 	if (!item) {
172 		dbs("unknown token: " + token);
173 		return QString();
174 	}
175 	return item->widget->property(item->prop_name);
176 }
177 
validate()178 bool InputDialog::validate()
179 {
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;
187 	}
188 	okButton->setEnabled(result);
189 	return result;
190 }
191 
replace(const VariableMap & variables) const192 QString InputDialog::replace(const VariableMap &variables) const
193 {
194 	QString result = cmd;
195 	for (WidgetMap::const_iterator it = widgets.begin(), end = widgets.end(); it != end; ++it) {
196 		QString token = "%" + it.key() + "%";
197 		WidgetItemPtr item = it.value();
198 		QString value = item->widget->property(item->prop_name).toString();
199 		result.replace(item->start, item->end - item->start, value); // replace main token
200 		result.replace(token, value); // replace all other occurences of %name%
201 	}
202 	for (VariableMap::const_iterator it=variables.begin(), end=variables.end(); it != end; ++it) {
203 		QString token = "$" + it.key();
204 		QString val = it.value().type() == QVariant::StringList ? it.value().toStringList().join(" ")
205 		                                                        : it.value().toString();
206 		result.replace(token, val);
207 	}
208 	return result;
209 }
210 
211 } // namespace QGit
212