1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "jsonfieldpage.h"
27 #include "jsonfieldpage_p.h"
28 
29 #include "jsonwizard.h"
30 #include "jsonwizardfactory.h"
31 
32 #include "../project.h"
33 #include "../projecttree.h"
34 
35 #include <coreplugin/icore.h>
36 #include <coreplugin/locator/ilocatorfilter.h>
37 #include <utils/algorithm.h>
38 #include <utils/fancylineedit.h>
39 #include <utils/fileutils.h>
40 #include <utils/qtcassert.h>
41 #include <utils/runextensions.h>
42 #include <utils/stringutils.h>
43 #include <utils/theme/theme.h>
44 
45 #include <QApplication>
46 #include <QComboBox>
47 #include <QCheckBox>
48 #include <QCompleter>
49 #include <QDebug>
50 #include <QDir>
51 #include <QFormLayout>
52 #include <QFutureWatcher>
53 #include <QItemSelectionModel>
54 #include <QLabel>
55 #include <QListView>
56 #include <QRegularExpression>
57 #include <QStandardItem>
58 #include <QTextEdit>
59 #include <QVariant>
60 #include <QVariantMap>
61 #include <QVBoxLayout>
62 
63 using namespace Utils;
64 
65 const char NAME_KEY[] = "name";
66 const char DISPLAY_NAME_KEY[] = "trDisplayName";
67 const char TOOLTIP_KEY[] = "trToolTip";
68 const char MANDATORY_KEY[] = "mandatory";
69 const char PERSISTENCE_KEY_KEY[] = "persistenceKey";
70 const char VISIBLE_KEY[] = "visible";
71 const char ENABLED_KEY[] = "enabled";
72 const char SPAN_KEY[] = "span";
73 const char TYPE_KEY[] = "type";
74 const char DATA_KEY[] = "data";
75 const char IS_COMPLETE_KEY[] = "isComplete";
76 const char IS_COMPLETE_MESSAGE_KEY[] = "trIncompleteMessage";
77 
78 namespace {
consumeValue(QVariantMap & map,const QString & key,const QVariant & defaultValue=QVariant ())79 QVariant consumeValue(QVariantMap &map, const QString &key, const QVariant &defaultValue = QVariant())
80 {
81     QVariantMap::iterator i = map.find(key);
82     if (i != map.end()) {
83         QVariant value = i.value();
84         map.erase(i);
85         return value;
86     }
87     return defaultValue;
88 }
89 
warnAboutUnsupportedKeys(const QVariantMap & map,const QString & name,const QString & type=QString ())90 void warnAboutUnsupportedKeys(const QVariantMap &map, const QString &name, const QString &type = QString())
91 {
92     if (!map.isEmpty()) {
93 
94         QString typeAndName = name;
95         if (!type.isEmpty() && !name.isEmpty())
96             typeAndName = QString("%1 (\"%2\")").arg(type, name);
97 
98         qWarning().noquote() << QString("Field %1 has unsupported keys: %2").arg(typeAndName, map.keys().join(", "));
99     }
100 }
101 } // namespace
102 
103 namespace ProjectExplorer {
104 
105 // --------------------------------------------------------------------
106 // Helper:
107 // --------------------------------------------------------------------
108 
109 class LineEdit : public FancyLineEdit
110 {
111 public:
LineEdit(MacroExpander * expander,const QRegularExpression & pattern)112     LineEdit(MacroExpander *expander, const QRegularExpression &pattern)
113     {
114         if (pattern.pattern().isEmpty() || !pattern.isValid())
115             return;
116         m_expander.setDisplayName(JsonFieldPage::tr("Line Edit Validator Expander"));
117         m_expander.setAccumulating(true);
118         m_expander.registerVariable("INPUT", JsonFieldPage::tr("The text edit input to fix up."),
119                                     [this]() { return m_currentInput; });
120         m_expander.registerSubProvider([expander]() -> MacroExpander * { return expander; });
121         setValidationFunction([this, pattern](FancyLineEdit *, QString *) {
122             return pattern.match(text()).hasMatch();
123         });
124     }
125 
setFixupExpando(const QString & expando)126     void setFixupExpando(const QString &expando) { m_fixupExpando = expando; }
127 
128 private:
fixInputString(const QString & string)129     QString fixInputString(const QString &string) override
130     {
131         if (m_fixupExpando.isEmpty())
132             return string;
133         m_currentInput = string;
134         return m_expander.expand(m_fixupExpando);
135     }
136 
137 private:
138     MacroExpander m_expander;
139     QString m_fixupExpando;
140     mutable QString m_currentInput;
141 };
142 
143 // --------------------------------------------------------------------
144 // JsonFieldPage::FieldData:
145 // --------------------------------------------------------------------
146 
Field()147 JsonFieldPage::Field::Field() : d(std::make_unique<FieldPrivate>())
148 { }
149 
~Field()150 JsonFieldPage::Field::~Field()
151 {
152     delete d->m_widget;
153     delete d->m_label;
154 }
155 
type()156 QString JsonFieldPage::Field::type()
157 {
158     return d->m_type;
159 }
160 
setHasUserChanges()161 void JsonFieldPage::Field::setHasUserChanges()
162 {
163     d->m_hasUserChanges = true;
164 }
165 
fromSettings(const QVariant & value)166 void JsonFieldPage::Field::fromSettings(const QVariant &value)
167 {
168     Q_UNUSED(value);
169 }
170 
toSettings() const171 QVariant JsonFieldPage::Field::toSettings() const
172 {
173     return {};
174 }
175 
parse(const QVariant & input,QString * errorMessage)176 JsonFieldPage::Field *JsonFieldPage::Field::parse(const QVariant &input, QString *errorMessage)
177 {
178     if (input.type() != QVariant::Map) {
179         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
180                                                     "Field is not an object.");
181         return nullptr;
182     }
183 
184     QVariantMap tmp = input.toMap();
185     const QString name = consumeValue(tmp, NAME_KEY).toString();
186     if (name.isEmpty()) {
187         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
188                                                     "Field has no name.");
189         return nullptr;
190     }
191     const QString type = consumeValue(tmp, TYPE_KEY).toString();
192     if (type.isEmpty()) {
193         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
194                                                     "Field \"%1\" has no type.").arg(name);
195         return nullptr;
196     }
197 
198     Field *data = createFieldData(type);
199     if (!data) {
200         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
201                                                     "Field \"%1\" has unsupported type \"%2\".")
202                 .arg(name).arg(type);
203         return nullptr;
204     }
205     data->setTexts(name,
206                    JsonWizardFactory::localizedString(consumeValue(tmp, DISPLAY_NAME_KEY).toString()),
207                    consumeValue(tmp, TOOLTIP_KEY).toString());
208 
209     data->setVisibleExpression(consumeValue(tmp, VISIBLE_KEY, true));
210     data->setEnabledExpression(consumeValue(tmp, ENABLED_KEY, true));
211     data->setIsMandatory(consumeValue(tmp, MANDATORY_KEY, true).toBool());
212     data->setHasSpan(consumeValue(tmp, SPAN_KEY, false).toBool());
213     data->setIsCompleteExpando(consumeValue(tmp, IS_COMPLETE_KEY, true),
214                                consumeValue(tmp, IS_COMPLETE_MESSAGE_KEY).toString());
215     data->setPersistenceKey(consumeValue(tmp, PERSISTENCE_KEY_KEY).toString());
216 
217     QVariant dataVal = consumeValue(tmp, DATA_KEY);
218     if (!data->parseData(dataVal, errorMessage)) {
219         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
220                                                     "When parsing Field \"%1\": %2")
221                 .arg(name).arg(*errorMessage);
222         delete data;
223         return nullptr;
224     }
225 
226     warnAboutUnsupportedKeys(tmp, name);
227     return data;
228 }
229 
createWidget(JsonFieldPage * page)230 void JsonFieldPage::Field::createWidget(JsonFieldPage *page)
231 {
232     QWidget *w = widget(displayName(), page);
233     w->setObjectName(name());
234     QFormLayout *layout = page->layout();
235 
236     if (suppressName()) {
237         layout->addWidget(w);
238     } else if (hasSpan()) {
239         if (!suppressName()) {
240             d->m_label = new QLabel(displayName());
241             layout->addRow(d->m_label);
242         }
243 
244         layout->addRow(w);
245     } else {
246         d->m_label = new QLabel(displayName());
247         layout->addRow(d->m_label, w);
248     }
249 
250     setup(page, name());
251 }
252 
adjustState(MacroExpander * expander)253 void JsonFieldPage::Field::adjustState(MacroExpander *expander)
254 {
255     setVisible(JsonWizard::boolFromVariant(d->m_visibleExpression, expander));
256     setEnabled(JsonWizard::boolFromVariant(d->m_enabledExpression, expander));
257     QTC_ASSERT(d->m_widget, return);
258     d->m_widget->setToolTip(expander->expand(toolTip()));
259 }
260 
setEnabled(bool e)261 void JsonFieldPage::Field::setEnabled(bool e)
262 {
263     QTC_ASSERT(d->m_widget, return);
264     d->m_widget->setEnabled(e);
265 }
266 
setVisible(bool v)267 void JsonFieldPage::Field::setVisible(bool v)
268 {
269     QTC_ASSERT(d->m_widget, return);
270     if (d->m_label)
271         d->m_label->setVisible(v);
272     d->m_widget->setVisible(v);
273 }
274 
setType(const QString & type)275 void JsonFieldPage::Field::setType(const QString &type)
276 {
277     d->m_type = type;
278 }
279 
validate(MacroExpander * expander,QString * message)280 bool JsonFieldPage::Field::validate(MacroExpander *expander, QString *message)
281 {
282     if (!JsonWizard::boolFromVariant(d->m_isCompleteExpando, expander)) {
283         if (message)
284             *message = expander->expand(d->m_isCompleteExpandoMessage);
285         return false;
286     }
287     return true;
288 }
289 
initialize(MacroExpander * expander)290 void JsonFieldPage::Field::initialize(MacroExpander *expander)
291 {
292     adjustState(expander);
293     initializeData(expander);
294 }
295 
widget(const QString & displayName,JsonFieldPage * page)296 QWidget *JsonFieldPage::Field::widget(const QString &displayName, JsonFieldPage *page)
297 {
298     QTC_ASSERT(!d->m_widget, return d->m_widget);
299 
300     d->m_widget = createWidget(displayName, page);
301     return d->m_widget;
302 }
303 
name()304 QString JsonFieldPage::Field::name()
305 {
306     return d->m_name;
307 }
308 
displayName()309 QString JsonFieldPage::Field::displayName()
310 {
311     return d->m_displayName;
312 }
313 
toolTip()314 QString JsonFieldPage::Field::toolTip()
315 {
316     return d->m_toolTip;
317 }
318 
persistenceKey() const319 QString JsonFieldPage::Field::persistenceKey() const
320 {
321     return d->m_persistenceKey;
322 }
323 
isMandatory()324 bool JsonFieldPage::Field::isMandatory()
325 {
326     return d->m_isMandatory;
327 }
328 
hasSpan()329 bool JsonFieldPage::Field::hasSpan()
330 {
331     return d->m_hasSpan;
332 }
333 
hasUserChanges() const334 bool JsonFieldPage::Field::hasUserChanges() const
335 {
336     return d->m_hasUserChanges;
337 }
338 
value(const QString & key)339 QVariant JsonFieldPage::value(const QString &key)
340 {
341     QVariant v = property(key.toUtf8());
342     if (v.isValid())
343         return v;
344     auto w = qobject_cast<JsonWizard *>(wizard());
345     QTC_ASSERT(w, return QVariant());
346     return w->value(key);
347 }
348 
widget() const349 QWidget *JsonFieldPage::Field::widget() const
350 {
351     return d->m_widget;
352 }
353 
setTexts(const QString & n,const QString & dn,const QString & tt)354 void JsonFieldPage::Field::setTexts(const QString &n, const QString &dn, const QString &tt)
355 {
356     d->m_name = n;
357     d->m_displayName = dn;
358     d->m_toolTip = tt;
359 }
360 
setIsMandatory(bool b)361 void JsonFieldPage::Field::setIsMandatory(bool b)
362 {
363     d->m_isMandatory = b;
364 }
365 
setHasSpan(bool b)366 void JsonFieldPage::Field::setHasSpan(bool b)
367 {
368     d->m_hasSpan = b;
369 }
370 
setVisibleExpression(const QVariant & v)371 void JsonFieldPage::Field::setVisibleExpression(const QVariant &v)
372 {
373     d->m_visibleExpression = v;
374 }
375 
setEnabledExpression(const QVariant & v)376 void JsonFieldPage::Field::setEnabledExpression(const QVariant &v)
377 {
378     d->m_enabledExpression = v;
379 }
380 
setIsCompleteExpando(const QVariant & v,const QString & m)381 void JsonFieldPage::Field::setIsCompleteExpando(const QVariant &v, const QString &m)
382 {
383     d->m_isCompleteExpando = v;
384     d->m_isCompleteExpandoMessage = m;
385 }
386 
setPersistenceKey(const QString & key)387 void JsonFieldPage::Field::setPersistenceKey(const QString &key)
388 {
389     d->m_persistenceKey = key;
390 }
391 
392 // --------------------------------------------------------------------
393 // LabelFieldData:
394 // --------------------------------------------------------------------
395 
parseData(const QVariant & data,QString * errorMessage)396 bool LabelField::parseData(const QVariant &data, QString *errorMessage)
397 {
398     if (data.type() != QVariant::Map) {
399         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
400                                                     "Label (\"%1\") data is not an object.")
401                 .arg(name());
402         return false;
403     }
404 
405     QVariantMap tmp = data.toMap();
406 
407     m_wordWrap = consumeValue(tmp, "wordWrap", false).toBool();
408     m_text = JsonWizardFactory::localizedString(consumeValue(tmp, "trText"));
409 
410     if (m_text.isEmpty()) {
411         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
412                                                     "Label (\"%1\") has no trText.")
413                 .arg(name());
414         return false;
415     }
416     warnAboutUnsupportedKeys(tmp, name(), type());
417     return true;
418 }
419 
createWidget(const QString & displayName,JsonFieldPage * page)420 QWidget *LabelField::createWidget(const QString &displayName, JsonFieldPage *page)
421 {
422     Q_UNUSED(displayName)
423     Q_UNUSED(page)
424     auto w = new QLabel;
425     w->setWordWrap(m_wordWrap);
426     w->setText(m_text);
427     w->setSizePolicy(QSizePolicy::Expanding, w->sizePolicy().verticalPolicy());
428     return w;
429 }
430 
431 // --------------------------------------------------------------------
432 // SpacerFieldData:
433 // --------------------------------------------------------------------
434 
parseData(const QVariant & data,QString * errorMessage)435 bool SpacerField::parseData(const QVariant &data, QString *errorMessage)
436 {
437     if (data.isNull())
438         return true;
439 
440     if (data.type() != QVariant::Map) {
441         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
442                                                     "Spacer (\"%1\") data is not an object.")
443                 .arg(name());
444         return false;
445     }
446 
447     QVariantMap tmp = data.toMap();
448 
449     bool ok;
450     m_factor = consumeValue(tmp, "factor", 1).toInt(&ok);
451 
452     if (!ok) {
453         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
454                                                     "Spacer (\"%1\") property \"factor\" is no integer value.")
455                 .arg(name());
456         return false;
457     }
458     warnAboutUnsupportedKeys(tmp, name(), type());
459 
460     return true;
461 }
462 
createWidget(const QString & displayName,JsonFieldPage * page)463 QWidget *SpacerField::createWidget(const QString &displayName, JsonFieldPage *page)
464 {
465     Q_UNUSED(displayName)
466     Q_UNUSED(page)
467     int hspace = QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
468     int vspace = QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
469     int hsize = hspace * m_factor;
470     int vsize = vspace * m_factor;
471 
472     auto w = new QWidget();
473     w->setMinimumSize(hsize, vsize);
474     w->setMaximumSize(hsize, vsize);
475     w->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
476     return w;
477 }
478 
479 // --------------------------------------------------------------------
480 // LineEditFieldData:
481 // --------------------------------------------------------------------
482 
parseData(const QVariant & data,QString * errorMessage)483 bool LineEditField::parseData(const QVariant &data, QString *errorMessage)
484 {
485     if (data.isNull())
486         return true;
487 
488     if (data.type() != QVariant::Map) {
489         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
490                                                     "LineEdit (\"%1\") data is not an object.")
491                 .arg(name());
492         return false;
493     }
494 
495     QVariantMap tmp = data.toMap();
496 
497     m_isPassword = consumeValue(tmp, "isPassword", false).toBool();
498     m_defaultText = JsonWizardFactory::localizedString(consumeValue(tmp, "trText").toString());
499     m_disabledText = JsonWizardFactory::localizedString(consumeValue(tmp, "trDisabledText").toString());
500     m_placeholderText = JsonWizardFactory::localizedString(consumeValue(tmp, "trPlaceholder").toString());
501     m_historyId = consumeValue(tmp, "historyId").toString();
502     m_restoreLastHistoryItem = consumeValue(tmp, "restoreLastHistoryItem", false).toBool();
503     QString pattern = consumeValue(tmp, "validator").toString();
504     if (!pattern.isEmpty()) {
505         m_validatorRegExp = QRegularExpression('^' + pattern + '$');
506         if (!m_validatorRegExp.isValid()) {
507             *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
508                                                         "LineEdit (\"%1\") has an invalid regular expression \"%2\" in \"validator\".")
509                     .arg(name(), pattern);
510             m_validatorRegExp = QRegularExpression();
511             return false;
512         }
513     }
514     m_fixupExpando = consumeValue(tmp, "fixup").toString();
515 
516     const QString completion = consumeValue(tmp, "completion").toString();
517     if (completion == "classes") {
518         m_completion = Completion::Classes;
519     } else if (completion == "namespaces") {
520         m_completion = Completion::Namespaces;
521     } else if (!completion.isEmpty()) {
522         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
523                 "LineEdit (\"%1\") has an invalid value \"%2\" in \"completion\".")
524                 .arg(name(), completion);
525         return false;
526     }
527 
528     warnAboutUnsupportedKeys(tmp, name(), type());
529 
530     return true;
531 }
532 
createWidget(const QString & displayName,JsonFieldPage * page)533 QWidget *LineEditField::createWidget(const QString &displayName, JsonFieldPage *page)
534 {
535     Q_UNUSED(displayName)
536     const auto w = new LineEdit(page->expander(), m_validatorRegExp);
537     w->setFixupExpando(m_fixupExpando);
538 
539     if (!m_historyId.isEmpty())
540         w->setHistoryCompleter(m_historyId, m_restoreLastHistoryItem);
541 
542     w->setEchoMode(m_isPassword ? QLineEdit::Password : QLineEdit::Normal);
543     QObject::connect(w, &FancyLineEdit::textEdited, [this] { setHasUserChanges(); });
544     setupCompletion(w);
545 
546     return w;
547 }
548 
setup(JsonFieldPage * page,const QString & name)549 void LineEditField::setup(JsonFieldPage *page, const QString &name)
550 {
551     auto w = qobject_cast<FancyLineEdit *>(widget());
552     QTC_ASSERT(w, return);
553     page->registerFieldWithName(name, w);
554     QObject::connect(w, &FancyLineEdit::textChanged,
555                      page, [this, page]() -> void { m_isModified = true; emit page->completeChanged(); });
556 }
557 
validate(MacroExpander * expander,QString * message)558 bool LineEditField::validate(MacroExpander *expander, QString *message)
559 {
560     if (m_isValidating)
561         return true;
562     m_isValidating = true;
563 
564     auto w = qobject_cast<FancyLineEdit *>(widget());
565     QTC_ASSERT(w, return false);
566 
567     if (w->isEnabled()) {
568         if (m_isModified) {
569             if (!m_currentText.isNull()) {
570                 w->setText(m_currentText);
571                 m_currentText.clear();
572             }
573         } else {
574             w->setText(expander->expand(m_defaultText));
575             m_isModified = false;
576         }
577     } else {
578         if (!m_disabledText.isNull() && m_currentText.isNull())
579             m_currentText = w->text();
580     }
581 
582     const bool baseValid = JsonFieldPage::Field::validate(expander, message);
583     m_isValidating = false;
584     return baseValid && !w->text().isEmpty() && w->isValid();
585 }
586 
initializeData(MacroExpander * expander)587 void LineEditField::initializeData(MacroExpander *expander)
588 {
589     auto w = qobject_cast<FancyLineEdit *>(widget());
590     QTC_ASSERT(w, return);
591     m_isValidating = true;
592     w->setText(expander->expand(m_defaultText));
593     w->setPlaceholderText(m_placeholderText);
594     m_isModified = false;
595     m_isValidating = false;
596 }
597 
fromSettings(const QVariant & value)598 void LineEditField::fromSettings(const QVariant &value)
599 {
600     m_defaultText = value.toString();
601 }
602 
toSettings() const603 QVariant LineEditField::toSettings() const
604 {
605     return qobject_cast<FancyLineEdit *>(widget())->text();
606 }
607 
setupCompletion(FancyLineEdit * lineEdit)608 void LineEditField::setupCompletion(FancyLineEdit *lineEdit)
609 {
610     using namespace Core;
611     using namespace Utils;
612     if (m_completion == Completion::None)
613         return;
614     ILocatorFilter * const classesFilter = findOrDefault(
615                 ILocatorFilter::allLocatorFilters(),
616                 equal(&ILocatorFilter::id, Id("Classes")));
617     if (!classesFilter)
618         return;
619     classesFilter->prepareSearch({});
620     const auto watcher = new QFutureWatcher<LocatorFilterEntry>;
621     const auto handleResults = [this, lineEdit, watcher](int firstIndex, int endIndex) {
622         QSet<QString> namespaces;
623         QStringList classes;
624         Project * const project = ProjectTree::currentProject();
625         for (int i = firstIndex; i < endIndex; ++i) {
626             static const auto isReservedName = [](const QString &name) {
627                 static const QRegularExpression rx1("^_[A-Z].*");
628                 static const QRegularExpression rx2(".*::_[A-Z].*");
629                 return name.contains("__") || rx1.match(name).hasMatch()
630                         || rx2.match(name).hasMatch();
631             };
632             const LocatorFilterEntry &entry = watcher->resultAt(i);
633             const bool hasNamespace = !entry.extraInfo.isEmpty()
634                     && !entry.extraInfo.startsWith('<')  && !entry.extraInfo.contains("::<")
635                     && !isReservedName(entry.extraInfo)
636                     && !entry.extraInfo.startsWith('~')
637                     && !entry.extraInfo.contains("Anonymous:")
638                     && !FileUtils::isAbsolutePath(entry.extraInfo);
639             const bool isBaseClassCandidate = !isReservedName(entry.displayName)
640                     && !entry.displayName.startsWith("Anonymous:");
641             if (isBaseClassCandidate)
642                 classes << entry.displayName;
643             if (hasNamespace) {
644                 if (isBaseClassCandidate)
645                     classes << (entry.extraInfo + "::" + entry.displayName);
646                 if (m_completion == Completion::Namespaces) {
647                     if (!project
648                             || entry.filePath.startsWith(project->projectDirectory().toString())) {
649                         namespaces << entry.extraInfo;
650                     }
651                 }
652             }
653         }
654         QStringList completionList;
655         if (m_completion == Completion::Namespaces) {
656             completionList = toList(namespaces);
657             completionList = filtered(completionList, [&classes](const QString &ns) {
658                 return !classes.contains(ns);
659             });
660             completionList = transform(completionList, [](const QString &ns) {
661                 return QString(ns + "::");
662             });
663         } else {
664             completionList = classes;
665         }
666         completionList.sort();
667         lineEdit->setSpecialCompleter(new QCompleter(completionList, lineEdit));
668     };
669     QObject::connect(watcher, &QFutureWatcher<LocatorFilterEntry>::resultsReadyAt, lineEdit,
670                      handleResults);
671     QObject::connect(watcher, &QFutureWatcher<LocatorFilterEntry>::finished,
672                      watcher, &QFutureWatcher<LocatorFilterEntry>::deleteLater);
673     watcher->setFuture(runAsync([classesFilter](QFutureInterface<LocatorFilterEntry> &f) {
674         const QList<LocatorFilterEntry> matches = classesFilter->matchesFor(f, {});
675         if (!matches.isEmpty())
676             f.reportResults(QVector<LocatorFilterEntry>(matches.cbegin(), matches.cend()));
677         f.reportFinished();
678     }));
679 }
680 
681 // --------------------------------------------------------------------
682 // TextEditFieldData:
683 // --------------------------------------------------------------------
684 
685 
parseData(const QVariant & data,QString * errorMessage)686 bool TextEditField::parseData(const QVariant &data, QString *errorMessage)
687 {
688     if (data.isNull())
689         return true;
690 
691     if (data.type() != QVariant::Map) {
692         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
693                                                     "TextEdit (\"%1\") data is not an object.")
694                 .arg(name());
695         return false;
696     }
697 
698     QVariantMap tmp = data.toMap();
699 
700     m_defaultText = JsonWizardFactory::localizedString(consumeValue(tmp, "trText").toString());
701     m_disabledText = JsonWizardFactory::localizedString(consumeValue(tmp, "trDisabledText").toString());
702     m_acceptRichText = consumeValue(tmp, "richText", true).toBool();
703 
704     warnAboutUnsupportedKeys(tmp, name(), type());
705     return true;
706 }
707 
createWidget(const QString & displayName,JsonFieldPage * page)708 QWidget *TextEditField::createWidget(const QString &displayName, JsonFieldPage *page)
709 {
710     // TODO: Set up modification monitoring...
711     Q_UNUSED(displayName)
712     Q_UNUSED(page)
713     auto w = new QTextEdit;
714     w->setAcceptRichText(m_acceptRichText);
715     QObject::connect(w, &QTextEdit::textChanged, [this, w] {
716        if (w->toPlainText() != m_defaultText)
717            setHasUserChanges();
718     });
719     return w;
720 }
721 
setup(JsonFieldPage * page,const QString & name)722 void TextEditField::setup(JsonFieldPage *page, const QString &name)
723 {
724     auto w = qobject_cast<QTextEdit *>(widget());
725     QTC_ASSERT(w, return);
726     page->registerFieldWithName(name, w, "plainText", SIGNAL(textChanged()));
727     QObject::connect(w, &QTextEdit::textChanged, page, &QWizardPage::completeChanged);
728 }
729 
validate(MacroExpander * expander,QString * message)730 bool TextEditField::validate(MacroExpander *expander, QString *message)
731 {
732     if (!JsonFieldPage::Field::validate(expander, message))
733         return false;
734 
735     auto w = qobject_cast<QTextEdit *>(widget());
736     QTC_ASSERT(w, return false);
737 
738     if (!w->isEnabled() && !m_disabledText.isNull() && m_currentText.isNull()) {
739         m_currentText = w->toHtml();
740         w->setPlainText(expander->expand(m_disabledText));
741     } else if (w->isEnabled() && !m_currentText.isNull()) {
742         w->setText(m_currentText);
743         m_currentText.clear();
744     }
745 
746     return !w->toPlainText().isEmpty();
747 }
748 
initializeData(MacroExpander * expander)749 void TextEditField::initializeData(MacroExpander *expander)
750 {
751     auto w = qobject_cast<QTextEdit *>(widget());
752     QTC_ASSERT(w, return);
753     w->setPlainText(expander->expand(m_defaultText));
754 }
755 
fromSettings(const QVariant & value)756 void TextEditField::fromSettings(const QVariant &value)
757 {
758     m_defaultText = value.toString();
759 }
760 
toSettings() const761 QVariant TextEditField::toSettings() const
762 {
763     return qobject_cast<QTextEdit *>(widget())->toPlainText();
764 }
765 
766 // --------------------------------------------------------------------
767 // PathChooserFieldData:
768 // --------------------------------------------------------------------
769 
parseData(const QVariant & data,QString * errorMessage)770 bool PathChooserField::parseData(const QVariant &data, QString *errorMessage)
771 {
772     if (data.isNull())
773         return true;
774 
775     if (data.type() != QVariant::Map) {
776         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
777                                                     "PathChooser data is not an object.");
778         return false;
779     }
780 
781     QVariantMap tmp = data.toMap();
782 
783     m_path = consumeValue(tmp, "path").toString();
784     m_basePath = consumeValue(tmp, "basePath").toString();
785     m_historyId = consumeValue(tmp, "historyId").toString();
786 
787     QString kindStr = consumeValue(tmp, "kind", "existingDirectory").toString();
788     if (kindStr == "existingDirectory") {
789         m_kind = PathChooser::ExistingDirectory;
790     } else if (kindStr == "directory") {
791         m_kind = PathChooser::Directory;
792     } else if (kindStr == "file") {
793         m_kind = PathChooser::File;
794     } else if (kindStr == "saveFile") {
795         m_kind = PathChooser::SaveFile;
796     } else if (kindStr == "existingCommand") {
797         m_kind = PathChooser::ExistingCommand;
798     } else if (kindStr == "command") {
799         m_kind = PathChooser::Command;
800     } else if (kindStr == "any") {
801         m_kind = PathChooser::Any;
802     } else {
803         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
804                                                     "kind \"%1\" is not one of the supported \"existingDirectory\", "
805                                                     "\"directory\", \"file\", \"saveFile\", \"existingCommand\", "
806                                                     "\"command\", \"any\".")
807                 .arg(kindStr);
808         return false;
809     }
810 
811     warnAboutUnsupportedKeys(tmp, name(), type());
812     return true;
813 }
814 
createWidget(const QString & displayName,JsonFieldPage * page)815 QWidget *PathChooserField::createWidget(const QString &displayName, JsonFieldPage *page)
816 {
817     Q_UNUSED(displayName)
818     Q_UNUSED(page)
819     auto w = new PathChooser;
820     if (!m_historyId.isEmpty())
821         w->setHistoryCompleter(m_historyId);
822     QObject::connect(w, &PathChooser::pathChanged, [this, w] {
823         if (w->filePath().toString() != m_path)
824             setHasUserChanges();
825     });
826     return w;
827 }
828 
setEnabled(bool e)829 void PathChooserField::setEnabled(bool e)
830 {
831     auto w = qobject_cast<PathChooser *>(widget());
832     QTC_ASSERT(w, return);
833     w->setReadOnly(!e);
834 }
835 
setup(JsonFieldPage * page,const QString & name)836 void PathChooserField::setup(JsonFieldPage *page, const QString &name)
837 {
838     auto w = qobject_cast<PathChooser *>(widget());
839     QTC_ASSERT(w, return);
840     page->registerFieldWithName(name, w, "path", SIGNAL(rawPathChanged(QString)));
841     QObject::connect(w, &PathChooser::rawPathChanged,
842                      page, [page](QString) { emit page->completeChanged(); });
843 }
844 
validate(MacroExpander * expander,QString * message)845 bool PathChooserField::validate(MacroExpander *expander, QString *message)
846 {
847     if (!JsonFieldPage::Field::validate(expander, message))
848         return false;
849 
850     auto w = qobject_cast<PathChooser *>(widget());
851     QTC_ASSERT(w, return false);
852     return w->isValid();
853 }
854 
initializeData(MacroExpander * expander)855 void PathChooserField::initializeData(MacroExpander *expander)
856 {
857     auto w = qobject_cast<PathChooser *>(widget());
858     QTC_ASSERT(w, return);
859     w->setBaseDirectory(expander->expand(FilePath::fromString(m_basePath)));
860     w->setExpectedKind(m_kind);
861 
862     if (m_currentPath.isNull())
863         w->setPath(expander->expand(m_path));
864     else
865         w->setPath(m_currentPath);
866 }
867 
fromSettings(const QVariant & value)868 void PathChooserField::fromSettings(const QVariant &value)
869 {
870     m_path = value.toString();
871 }
872 
toSettings() const873 QVariant PathChooserField::toSettings() const
874 {
875     return qobject_cast<PathChooser *>(widget())->filePath().toString();
876 }
877 
878 // --------------------------------------------------------------------
879 // CheckBoxFieldData:
880 // --------------------------------------------------------------------
881 
parseData(const QVariant & data,QString * errorMessage)882 bool CheckBoxField::parseData(const QVariant &data, QString *errorMessage)
883 {
884     if (data.isNull())
885         return true;
886 
887     if (data.type() != QVariant::Map) {
888         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
889                                                     "CheckBox (\"%1\") data is not an object.")
890                 .arg(name());
891         return false;
892     }
893 
894     QVariantMap tmp = data.toMap();
895 
896     m_checkedValue = consumeValue(tmp, "checkedValue", true).toString();
897     m_uncheckedValue = consumeValue(tmp, "uncheckedValue", false).toString();
898     if (m_checkedValue == m_uncheckedValue) {
899         *errorMessage= QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
900                                                    "CheckBox (\"%1\") values for checked and unchecked state are identical.")
901                 .arg(name());
902        return false;
903     }
904     m_checkedExpression = consumeValue(tmp, "checked", false);
905 
906     warnAboutUnsupportedKeys(tmp, name(), type());
907     return true;
908 }
909 
createWidget(const QString & displayName,JsonFieldPage * page)910 QWidget *CheckBoxField::createWidget(const QString &displayName, JsonFieldPage *page)
911 {
912     Q_UNUSED(page)
913     return new QCheckBox(displayName);
914 }
915 
setup(JsonFieldPage * page,const QString & name)916 void CheckBoxField::setup(JsonFieldPage *page, const QString &name)
917 {
918     auto w = qobject_cast<QCheckBox *>(widget());
919     QTC_ASSERT(w, return);
920     page->registerObjectAsFieldWithName<QCheckBox>(name, w, &QCheckBox::stateChanged, [this, page, w] () -> QString {
921         if (w->checkState() == Qt::Checked)
922             return page->expander()->expand(m_checkedValue);
923         return page->expander()->expand(m_uncheckedValue);
924     });
925 
926     QObject::connect(w, &QCheckBox::clicked, page, [this, page]() {
927         m_isModified = true;
928         setHasUserChanges();
929         emit page->completeChanged();
930     });
931 }
932 
validate(MacroExpander * expander,QString * message)933 bool CheckBoxField::validate(MacroExpander *expander, QString *message)
934 {
935     if (!JsonFieldPage::Field::validate(expander, message))
936         return false;
937 
938     if (!m_isModified) {
939         auto w = qobject_cast<QCheckBox *>(widget());
940         QTC_ASSERT(w, return false);
941         w->setChecked(JsonWizard::boolFromVariant(m_checkedExpression, expander));
942     }
943     return true;
944 }
945 
initializeData(MacroExpander * expander)946 void CheckBoxField::initializeData(MacroExpander *expander)
947 {
948     auto w = qobject_cast<QCheckBox *>(widget());
949     QTC_ASSERT(widget(), return);
950 
951     w->setChecked(JsonWizard::boolFromVariant(m_checkedExpression, expander));
952 }
953 
fromSettings(const QVariant & value)954 void CheckBoxField::fromSettings(const QVariant &value)
955 {
956     m_checkedExpression = value;
957 }
958 
toSettings() const959 QVariant CheckBoxField::toSettings() const
960 {
961     return qobject_cast<QCheckBox *>(widget())->isChecked();
962 }
963 
964 // --------------------------------------------------------------------
965 // ListFieldData:
966 // --------------------------------------------------------------------
967 
createStandardItemFromListItem(const QVariant & item,QString * errorMessage)968 std::unique_ptr<QStandardItem> createStandardItemFromListItem(const QVariant &item, QString *errorMessage)
969 {
970     if (item.type() == QVariant::List) {
971         *errorMessage  = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
972                                                      "No JSON lists allowed inside List items.");
973         return {};
974     }
975     auto standardItem = std::make_unique<QStandardItem>();
976     if (item.type() == QVariant::Map) {
977         QVariantMap tmp = item.toMap();
978         const QString key = JsonWizardFactory::localizedString(consumeValue(tmp, "trKey", QString()).toString());
979         const QVariant value = consumeValue(tmp, "value", key);
980 
981         if (key.isNull() || key.isEmpty()) {
982             *errorMessage  = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
983                                                          "No \"key\" found in List items.");
984             return {};
985         }
986         standardItem->setText(key);
987         standardItem->setData(value, ListField::ValueRole);
988         standardItem->setData(consumeValue(tmp, "condition", true), ListField::ConditionRole);
989         standardItem->setData(consumeValue(tmp, "icon"), ListField::IconStringRole);
990         standardItem->setToolTip(JsonWizardFactory::localizedString(consumeValue(tmp, "trToolTip", QString()).toString()));
991         warnAboutUnsupportedKeys(tmp, QString(), "List");
992     } else {
993         const QString keyvalue = item.toString();
994         standardItem->setText(keyvalue);
995         standardItem->setData(keyvalue, ListField::ValueRole);
996         standardItem->setData(true, ListField::ConditionRole);
997     }
998     return standardItem;
999 }
1000 
1001 ListField::ListField() = default;
1002 
1003 ListField::~ListField() = default;
1004 
parseData(const QVariant & data,QString * errorMessage)1005 bool ListField::parseData(const QVariant &data, QString *errorMessage)
1006 {
1007     if (data.type() != QVariant::Map) {
1008         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
1009                                                     "%1 (\"%2\") data is not an object.")
1010                 .arg(type(), name());
1011         return false;
1012     }
1013 
1014     QVariantMap tmp = data.toMap();
1015 
1016     bool ok;
1017     m_index = consumeValue(tmp, "index", 0).toInt(&ok);
1018     if (!ok) {
1019         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
1020                                                     "%1 (\"%2\") \"index\" is not an integer value.")
1021                 .arg(type(), name());
1022         return false;
1023     }
1024     m_disabledIndex = consumeValue(tmp, "disabledIndex", -1).toInt(&ok);
1025     if (!ok) {
1026         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
1027                                                     "%1 (\"%2\") \"disabledIndex\" is not an integer value.")
1028                 .arg(type(), name());
1029         return false;
1030     }
1031 
1032     const QVariant value = consumeValue(tmp, "items");
1033     if (value.isNull()) {
1034         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
1035                                                     "%1 (\"%2\") \"items\" missing.")
1036                 .arg(type(), name());
1037         return false;
1038     }
1039     if (value.type() != QVariant::List) {
1040         *errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
1041                                                     "%1 (\"%2\") \"items\" is not a JSON list.")
1042                 .arg(type(), name());
1043         return false;
1044     }
1045 
1046     for (const QVariant &i : value.toList()) {
1047         std::unique_ptr<QStandardItem> item = createStandardItemFromListItem(i, errorMessage);
1048         QTC_ASSERT(!item || !item->text().isEmpty(), continue);
1049         m_itemList.emplace_back(std::move(item));
1050     }
1051 
1052     warnAboutUnsupportedKeys(tmp, name(), type());
1053     return true;
1054 }
1055 
1056 
validate(MacroExpander * expander,QString * message)1057 bool ListField::validate(MacroExpander *expander, QString *message)
1058 {
1059     if (!JsonFieldPage::Field::validate(expander, message))
1060         return false;
1061 
1062     updateIndex();
1063     return selectionModel()->hasSelection();
1064 }
1065 
initializeData(MacroExpander * expander)1066 void ListField::initializeData(MacroExpander *expander)
1067 {
1068     QTC_ASSERT(widget(), return);
1069 
1070     if (m_index >= int(m_itemList.size())) {
1071         qWarning().noquote() <<  QString("%1 (\"%2\") has an index of %3 which does not exist.").arg(type(), name(), QString::number(m_index));
1072         m_index = -1;
1073     }
1074 
1075     QStandardItem *currentItem = m_index >= 0 ? m_itemList[uint(m_index)].get() : nullptr;
1076     QList<QStandardItem*> expandedValuesItems;
1077     expandedValuesItems.reserve(int(m_itemList.size()));
1078 
1079     for (const std::unique_ptr<QStandardItem> &item : m_itemList) {
1080         bool condition = JsonWizard::boolFromVariant(item->data(ConditionRole), expander);
1081         if (!condition)
1082             continue;
1083         QStandardItem *expandedValuesItem = item->clone();
1084         if (item.get() == currentItem)
1085             currentItem = expandedValuesItem;
1086         expandedValuesItem->setText(expander->expand(item->text()));
1087         expandedValuesItem->setData(expander->expandVariant(item->data(ValueRole)), ValueRole);
1088         expandedValuesItem->setData(expander->expand(item->data(IconStringRole).toString()), IconStringRole);
1089         expandedValuesItem->setData(condition, ConditionRole);
1090 
1091         QString iconPath = expandedValuesItem->data(IconStringRole).toString();
1092         if (!iconPath.isEmpty()) {
1093             if (auto *page = qobject_cast<JsonFieldPage*>(widget()->parentWidget())) {
1094                 const QString wizardDirectory = page->value("WizardDir").toString();
1095                 iconPath = QDir::cleanPath(QDir(wizardDirectory).absoluteFilePath(iconPath));
1096                 if (QFileInfo::exists(iconPath)) {
1097                     QIcon icon(iconPath);
1098                     expandedValuesItem->setIcon(icon);
1099                     addPossibleIconSize(icon);
1100                 } else {
1101                     qWarning().noquote() << QString("Icon file \"%1\" not found.").arg(QDir::toNativeSeparators(iconPath));
1102                 }
1103             } else {
1104                 qWarning().noquote() <<  QString("%1 (\"%2\") has no parentWidget JsonFieldPage to get the icon path.").arg(type(), name());
1105             }
1106         }
1107         expandedValuesItems.append(expandedValuesItem);
1108     }
1109 
1110     itemModel()->clear();
1111     itemModel()->appendColumn(expandedValuesItems); // inserts the first column
1112 
1113     selectionModel()->setCurrentIndex(itemModel()->indexFromItem(currentItem), QItemSelectionModel::ClearAndSelect);
1114 
1115     updateIndex();
1116 }
1117 
itemModel()1118 QStandardItemModel *ListField::itemModel()
1119 {
1120     if (!m_itemModel)
1121         m_itemModel = new QStandardItemModel(widget());
1122     return m_itemModel;
1123 }
1124 
selectionModel() const1125 QItemSelectionModel *ListField::selectionModel() const
1126 {
1127     return m_selectionModel;
1128 }
1129 
setSelectionModel(QItemSelectionModel * selectionModel)1130 void ListField::setSelectionModel(QItemSelectionModel *selectionModel)
1131 {
1132     m_selectionModel = selectionModel;
1133 }
1134 
maxIconSize()1135 QSize ListField::maxIconSize()
1136 {
1137     return m_maxIconSize;
1138 }
1139 
addPossibleIconSize(const QIcon & icon)1140 void ListField::addPossibleIconSize(const QIcon &icon)
1141 {
1142     const QSize iconSize = icon.availableSizes().value(0);
1143     if (iconSize.height() > m_maxIconSize.height())
1144         m_maxIconSize = iconSize;
1145 }
1146 
updateIndex()1147 void ListField::updateIndex()
1148 {
1149     if (!widget()->isEnabled() && m_disabledIndex >= 0 && m_savedIndex < 0) {
1150         m_savedIndex = selectionModel()->currentIndex().row();
1151         selectionModel()->setCurrentIndex(itemModel()->index(m_disabledIndex, 0), QItemSelectionModel::ClearAndSelect);
1152     } else if (widget()->isEnabled() && m_savedIndex >= 0) {
1153         selectionModel()->setCurrentIndex(itemModel()->index(m_savedIndex, 0), QItemSelectionModel::ClearAndSelect);
1154         m_savedIndex = -1;
1155     }
1156 }
1157 
fromSettings(const QVariant & value)1158 void ListField::fromSettings(const QVariant &value)
1159 {
1160     for (decltype(m_itemList)::size_type i = 0; i < m_itemList.size(); ++i) {
1161         if (m_itemList.at(i)->data(ValueRole) == value) {
1162             m_index = int(i);
1163             break;
1164         }
1165     }
1166 }
1167 
toSettings() const1168 QVariant ListField::toSettings() const
1169 {
1170     const int idx = selectionModel()->currentIndex().row();
1171     return idx >= 0 ? m_itemList.at(idx)->data(ValueRole) : QVariant();
1172 }
1173 
setup(JsonFieldPage * page,const QString & name)1174 void ComboBoxField::setup(JsonFieldPage *page, const QString &name)
1175 {
1176     auto w = qobject_cast<QComboBox*>(widget());
1177     QTC_ASSERT(w, return);
1178     w->setModel(itemModel());
1179     w->setInsertPolicy(QComboBox::NoInsert);
1180 
1181     QSizePolicy s = w->sizePolicy();
1182     s.setHorizontalPolicy(QSizePolicy::Expanding);
1183     w->setSizePolicy(s);
1184 
1185     setSelectionModel(w->view()->selectionModel());
1186 
1187     // the selectionModel does not behave like expected and wanted - so we block signals here
1188     // (for example there was some losing focus thing when hovering over items, ...)
1189     selectionModel()->blockSignals(true);
1190     QObject::connect(w, QOverload<int>::of(&QComboBox::activated), [w, this](int index) {
1191         w->blockSignals(true);
1192         selectionModel()->clearSelection();
1193 
1194         selectionModel()->blockSignals(false);
1195         selectionModel()->setCurrentIndex(w->model()->index(index, 0),
1196             QItemSelectionModel::ClearAndSelect);
1197         selectionModel()->blockSignals(true);
1198         w->blockSignals(false);
1199     });
1200     page->registerObjectAsFieldWithName<QComboBox>(name, w, QOverload<int>::of(&QComboBox::activated), [w]() {
1201         return w->currentData(ValueRole);
1202     });
1203     QObject::connect(selectionModel(), &QItemSelectionModel::selectionChanged, page, [page]() {
1204         emit page->completeChanged();
1205     });
1206 }
1207 
createWidget(const QString &,JsonFieldPage *)1208 QWidget *ComboBoxField::createWidget(const QString & /*displayName*/, JsonFieldPage * /*page*/)
1209 {
1210     const auto comboBox = new QComboBox;
1211     QObject::connect(comboBox, QOverload<int>::of(&QComboBox::activated),
1212                      [this] { setHasUserChanges(); });
1213     return comboBox;
1214 }
1215 
initializeData(MacroExpander * expander)1216 void ComboBoxField::initializeData(MacroExpander *expander)
1217 {
1218     ListField::initializeData(expander);
1219     // refresh also the current text of the combobox
1220     auto w = qobject_cast<QComboBox*>(widget());
1221     w->setCurrentIndex(selectionModel()->currentIndex().row());
1222 }
1223 
toSettings() const1224 QVariant ComboBoxField::toSettings() const
1225 {
1226     if (auto w = qobject_cast<QComboBox*>(widget()))
1227         return w->currentData(ValueRole);
1228     return {};
1229 }
1230 
setup(JsonFieldPage * page,const QString & name)1231 void IconListField::setup(JsonFieldPage *page, const QString &name)
1232 {
1233     auto w = qobject_cast<QListView*>(widget());
1234     QTC_ASSERT(w, return);
1235 
1236     w->setViewMode(QListView::IconMode);
1237     w->setMovement(QListView::Static);
1238     w->setResizeMode(QListView::Adjust);
1239     w->setSelectionRectVisible(false);
1240     w->setWrapping(true);
1241     w->setWordWrap(true);
1242 
1243     w->setModel(itemModel());
1244     setSelectionModel(w->selectionModel());
1245     page->registerObjectAsFieldWithName<QItemSelectionModel>(name, selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
1246         const QModelIndex i = selectionModel()->currentIndex();
1247         if (i.isValid())
1248             return i.data(ValueRole);
1249         return QVariant();
1250     });
1251     QObject::connect(selectionModel(), &QItemSelectionModel::selectionChanged, page, [page]() {
1252         emit page->completeChanged();
1253     });
1254 }
1255 
createWidget(const QString &,JsonFieldPage *)1256 QWidget *IconListField::createWidget(const QString & /*displayName*/, JsonFieldPage * /*page*/)
1257 {
1258     const auto listView = new QListView;
1259     QObject::connect(listView->selectionModel(), &QItemSelectionModel::currentChanged,
1260                      [this] { setHasUserChanges(); });
1261     return listView;
1262 }
1263 
initializeData(MacroExpander * expander)1264 void IconListField::initializeData(MacroExpander *expander)
1265 {
1266     ListField::initializeData(expander);
1267     auto w = qobject_cast<QListView*>(widget());
1268     const int spacing = 4;
1269     w->setSpacing(spacing);
1270     w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1271 
1272     // adding a third hight of the icon to see following items if there are some
1273     w->setMinimumHeight(maxIconSize().height() + maxIconSize().height() / 3);
1274     w->setIconSize(maxIconSize());
1275 }
1276 
1277 // --------------------------------------------------------------------
1278 // JsonFieldPage:
1279 // --------------------------------------------------------------------
1280 
1281 QHash<QString, JsonFieldPage::FieldFactory> JsonFieldPage::m_factories;
1282 
JsonFieldPage(MacroExpander * expander,QWidget * parent)1283 JsonFieldPage::JsonFieldPage(MacroExpander *expander, QWidget *parent) :
1284     WizardPage(parent),
1285     m_formLayout(new QFormLayout),
1286     m_errorLabel(new QLabel),
1287     m_expander(expander)
1288 {
1289     QTC_CHECK(m_expander);
1290 
1291     auto vLayout = new QVBoxLayout;
1292     m_formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
1293     vLayout->addLayout(m_formLayout);
1294     m_errorLabel->setVisible(false);
1295     QPalette palette = m_errorLabel->palette();
1296     palette.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorError));
1297     m_errorLabel->setPalette(palette);
1298     vLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
1299     vLayout->addWidget(m_errorLabel);
1300     setLayout(vLayout);
1301 }
1302 
~JsonFieldPage()1303 JsonFieldPage::~JsonFieldPage()
1304 {
1305     // Do not delete m_expander, it belongs to the wizard!
1306     qDeleteAll(m_fields);
1307 }
1308 
registerFieldFactory(const QString & id,const JsonFieldPage::FieldFactory & ff)1309 void JsonFieldPage::registerFieldFactory(const QString &id, const JsonFieldPage::FieldFactory &ff)
1310 {
1311     QTC_ASSERT(!m_factories.contains(id), return);
1312     m_factories.insert(id, ff);
1313 }
1314 
setup(const QVariant & data)1315 bool JsonFieldPage::setup(const QVariant &data)
1316 {
1317     QString errorMessage;
1318     QList<QVariant> fieldList = JsonWizardFactory::objectOrList(data, &errorMessage);
1319     foreach (const QVariant &field, fieldList) {
1320         Field *f = JsonFieldPage::Field::parse(field, &errorMessage);
1321         if (!f)
1322             continue;
1323         f->createWidget(this);
1324         if (!f->persistenceKey().isEmpty()) {
1325             f->setPersistenceKey(m_expander->expand(f->persistenceKey()));
1326             const QVariant value = Core::ICore::settings()
1327                     ->value(fullSettingsKey(f->persistenceKey()));
1328             if (value.isValid())
1329                 f->fromSettings(value);
1330         }
1331         m_fields.append(f);
1332     }
1333     return true;
1334 }
1335 
isComplete() const1336 bool JsonFieldPage::isComplete() const
1337 {
1338     QString message;
1339 
1340     bool result = true;
1341     bool hasErrorMessage = false;
1342     foreach (Field *f, m_fields) {
1343         f->adjustState(m_expander);
1344         if (!f->validate(m_expander, &message)) {
1345             if (!message.isEmpty()) {
1346                 showError(message);
1347                 hasErrorMessage = true;
1348             }
1349             if (f->isMandatory() && !f->widget()->isHidden())
1350                 result = false;
1351         }
1352     }
1353 
1354     if (!hasErrorMessage)
1355         clearError();
1356 
1357     return result;
1358 }
1359 
initializePage()1360 void JsonFieldPage::initializePage()
1361 {
1362     foreach (Field *f, m_fields)
1363         f->initialize(m_expander);
1364 }
1365 
cleanupPage()1366 void JsonFieldPage::cleanupPage()
1367 {
1368     foreach (Field *f, m_fields)
1369         f->cleanup(m_expander);
1370 }
1371 
validatePage()1372 bool JsonFieldPage::validatePage()
1373 {
1374     for (Field * const f : qAsConst(m_fields))
1375         if (!f->persistenceKey().isEmpty() && f->hasUserChanges()) {
1376             const QVariant value = f->toSettings();
1377             if (value.isValid())
1378                 Core::ICore::settings()->setValue(fullSettingsKey(f->persistenceKey()), value);
1379     }
1380     return true;
1381 }
1382 
showError(const QString & m) const1383 void JsonFieldPage::showError(const QString &m) const
1384 {
1385     m_errorLabel->setText(m);
1386     m_errorLabel->setVisible(true);
1387 }
1388 
clearError() const1389 void JsonFieldPage::clearError() const
1390 {
1391     m_errorLabel->setText(QString());
1392     m_errorLabel->setVisible(false);
1393 }
1394 
expander()1395 MacroExpander *JsonFieldPage::expander()
1396 {
1397     return m_expander;
1398 }
1399 
createFieldData(const QString & type)1400 JsonFieldPage::Field *JsonFieldPage::createFieldData(const QString &type)
1401 {
1402     if (auto factory = m_factories.value(type)) {
1403         JsonFieldPage::Field *field = factory();
1404         field->setType(type);
1405         return field;
1406     }
1407     return nullptr;
1408 }
1409 
fullSettingsKey(const QString & fieldKey)1410 QString JsonFieldPage::fullSettingsKey(const QString &fieldKey)
1411 {
1412     return "Wizards/" + fieldKey;
1413 }
1414 
1415 } // namespace ProjectExplorer
1416