forked from qt-creator/qt-creator
QmlDesigner: Add front end for style merger
Change-Id: I2bd1ac525ac866d23a4398e6214d3e8aec52eb7f Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -72,6 +72,7 @@ const char layoutGridLayoutCommandId[] = "LayoutGridLayout";
|
|||||||
const char layoutFillWidthCommandId[] = "LayoutFillWidth";
|
const char layoutFillWidthCommandId[] = "LayoutFillWidth";
|
||||||
const char layoutFillHeightCommandId[] = "LayoutFillHeight";
|
const char layoutFillHeightCommandId[] = "LayoutFillHeight";
|
||||||
const char goIntoComponentCommandId[] = "GoIntoComponent";
|
const char goIntoComponentCommandId[] = "GoIntoComponent";
|
||||||
|
const char mergeTemplateCommandId[] = "MergeTemplate";
|
||||||
const char goToImplementationCommandId[] = "GoToImplementation";
|
const char goToImplementationCommandId[] = "GoToImplementation";
|
||||||
const char addSignalHandlerCommandId[] = "AddSignalHandler";
|
const char addSignalHandlerCommandId[] = "AddSignalHandler";
|
||||||
const char moveToComponentCommandId[] = "MoveToComponent";
|
const char moveToComponentCommandId[] = "MoveToComponent";
|
||||||
@@ -116,6 +117,7 @@ const char resetSizeDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu",
|
|||||||
const char resetPositionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset Position");
|
const char resetPositionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset Position");
|
||||||
|
|
||||||
const char goIntoComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go into Component");
|
const char goIntoComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go into Component");
|
||||||
|
const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge File With Template");
|
||||||
const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
|
const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
|
||||||
const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler");
|
const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler");
|
||||||
const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File");
|
const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File");
|
||||||
|
@@ -1180,6 +1180,16 @@ void DesignerActionManager::createDefaultDesignerActions()
|
|||||||
&singleSelection,
|
&singleSelection,
|
||||||
&singleSelection));
|
&singleSelection));
|
||||||
|
|
||||||
|
addDesignerAction(new ModelNodeContextMenuAction(
|
||||||
|
mergeTemplateCommandId,
|
||||||
|
mergeTemplateDisplayName,
|
||||||
|
{},
|
||||||
|
rootCategory,
|
||||||
|
{},
|
||||||
|
30,
|
||||||
|
&mergeWithTemplate,
|
||||||
|
&SelectionContextFunctors::always));
|
||||||
|
|
||||||
addDesignerAction(new ActionGroup(
|
addDesignerAction(new ActionGroup(
|
||||||
"",
|
"",
|
||||||
genericToolBarCategory,
|
genericToolBarCategory,
|
||||||
|
@@ -48,6 +48,7 @@
|
|||||||
#include <signalhandlerproperty.h>
|
#include <signalhandlerproperty.h>
|
||||||
|
|
||||||
#include <componentcore_constants.h>
|
#include <componentcore_constants.h>
|
||||||
|
#include <stylesheetmerger.h>
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <qmldesignerplugin.h>
|
#include <qmldesignerplugin.h>
|
||||||
@@ -66,11 +67,15 @@
|
|||||||
#include <projectexplorer/projecttree.h>
|
#include <projectexplorer/projecttree.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/fileutils.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -1305,6 +1310,176 @@ void addCustomFlowEffect(const SelectionContext &selectionContext)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QString fromCamelCase(const QString &s)
|
||||||
|
{
|
||||||
|
static QRegularExpression regExp1 {"(.)([A-Z][a-z]+)"};
|
||||||
|
static QRegularExpression regExp2 {"([a-z0-9])([A-Z])"};
|
||||||
|
|
||||||
|
QString result = s;
|
||||||
|
result.replace(regExp1, "\\1 \\2");
|
||||||
|
result.replace(regExp2, "\\1 \\2");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getTemplateDialog(const Utils::FilePath &projectPath)
|
||||||
|
{
|
||||||
|
|
||||||
|
const Utils::FilePath templatesPath = projectPath.pathAppended("templates");
|
||||||
|
|
||||||
|
const QStringList templateFiles = QDir(templatesPath.toString()).entryList({"*.qml"});
|
||||||
|
|
||||||
|
QStringList names;
|
||||||
|
|
||||||
|
for (const QString &name : templateFiles) {
|
||||||
|
QString cleanS = name;
|
||||||
|
cleanS.remove(".qml");
|
||||||
|
names.append(fromCamelCase(cleanS));
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog *dialog = new QDialog(Core::ICore::dialogParent());
|
||||||
|
dialog->setMinimumWidth(480);
|
||||||
|
dialog->setModal(true);
|
||||||
|
|
||||||
|
dialog->setWindowTitle(QCoreApplication::translate("TemplateMerge","Merge With Template"));
|
||||||
|
|
||||||
|
auto mainLayout = new QGridLayout(dialog);
|
||||||
|
|
||||||
|
auto comboBox = new QComboBox;
|
||||||
|
|
||||||
|
comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
for (const QString &templateName : names)
|
||||||
|
comboBox->addItem(templateName);
|
||||||
|
|
||||||
|
QString templateFile;
|
||||||
|
|
||||||
|
auto setTemplate = [comboBox, &templateFile](const QString &newFile) {
|
||||||
|
if (comboBox->findText(newFile) < 0)
|
||||||
|
comboBox->addItem(newFile);
|
||||||
|
|
||||||
|
comboBox->setCurrentText(newFile);
|
||||||
|
templateFile = newFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
QPushButton *browseButton = new QPushButton(QCoreApplication::translate("TemplateMerge", "&Browse..."), dialog);
|
||||||
|
|
||||||
|
mainLayout->addWidget(new QLabel(QCoreApplication::translate("TemplateMerge", "Template:")), 0, 0);
|
||||||
|
mainLayout->addWidget(comboBox, 1, 0, 1, 3);
|
||||||
|
mainLayout->addWidget(browseButton, 1, 3, 1 , 1);
|
||||||
|
|
||||||
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
|
||||||
|
| QDialogButtonBox::Cancel);
|
||||||
|
mainLayout->addWidget(buttonBox, 2, 2, 1, 2);
|
||||||
|
|
||||||
|
QObject::connect(browseButton, &QPushButton::clicked, dialog, [setTemplate, &projectPath]() {
|
||||||
|
|
||||||
|
const QString newFile = QFileDialog::getOpenFileName(Core::ICore::dialogParent(),
|
||||||
|
QCoreApplication::translate("TemplateMerge", "Browse Template"),
|
||||||
|
projectPath.toString(),
|
||||||
|
"*.qml");
|
||||||
|
if (!newFile.isEmpty())
|
||||||
|
setTemplate(newFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, [dialog](){
|
||||||
|
dialog->accept();
|
||||||
|
dialog->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
QString result;
|
||||||
|
|
||||||
|
QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, [dialog](){
|
||||||
|
dialog->reject();
|
||||||
|
dialog->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(dialog, &QDialog::accepted, [&result, comboBox](){
|
||||||
|
result = comboBox->currentText();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog->exec();
|
||||||
|
|
||||||
|
if (!result.isEmpty() && !QFileInfo(result).exists()) {
|
||||||
|
result = templateFiles.at(names.indexOf(result));
|
||||||
|
result = templatesPath.pathAppended(result).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void styleMerge(const SelectionContext &selectionContext, const QString &templateFile)
|
||||||
|
{
|
||||||
|
Model *parentModel = selectionContext.view()->model();
|
||||||
|
|
||||||
|
QTC_ASSERT(parentModel, return);
|
||||||
|
|
||||||
|
QScopedPointer<Model> templateModel(Model::create("QtQuick.Item", 2, 1, parentModel));
|
||||||
|
Q_ASSERT(templateModel.data());
|
||||||
|
|
||||||
|
templateModel->setFileUrl(QUrl::fromLocalFile(templateFile));
|
||||||
|
|
||||||
|
QPlainTextEdit textEditTemplate;
|
||||||
|
Utils::FileReader reader;
|
||||||
|
|
||||||
|
QTC_ASSERT(reader.fetch(templateFile), return);
|
||||||
|
QString qmlTemplateString = QString::fromUtf8(reader.data());
|
||||||
|
QString imports;
|
||||||
|
|
||||||
|
for (const Import &import : parentModel->imports())
|
||||||
|
imports += QStringLiteral("import ") + import.toString(true) + QLatin1Char(';') + QLatin1Char('\n');
|
||||||
|
|
||||||
|
textEditTemplate.setPlainText(imports + qmlTemplateString);
|
||||||
|
NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate);
|
||||||
|
|
||||||
|
QScopedPointer<RewriterView> templateRewriterView(new RewriterView(RewriterView::Amend, nullptr));
|
||||||
|
templateRewriterView->setTextModifier(&textModifierTemplate);
|
||||||
|
templateModel->attachView(templateRewriterView.data());
|
||||||
|
templateRewriterView->setCheckSemanticErrors(false);
|
||||||
|
|
||||||
|
ModelNode templateRootNode = templateRewriterView->rootModelNode();
|
||||||
|
QTC_ASSERT(templateRootNode.isValid(), return);
|
||||||
|
|
||||||
|
QScopedPointer<Model> styleModel(Model::create("QtQuick.Item", 2, 1, parentModel));
|
||||||
|
Q_ASSERT(styleModel.data());
|
||||||
|
|
||||||
|
styleModel->setFileUrl(QUrl::fromLocalFile(templateFile));
|
||||||
|
|
||||||
|
QPlainTextEdit textEditStyle;
|
||||||
|
RewriterView *parentRewriterView = selectionContext.view()->model()->rewriterView();
|
||||||
|
QTC_ASSERT(parentRewriterView, return);
|
||||||
|
textEditStyle.setPlainText(parentRewriterView->textModifierContent());
|
||||||
|
NotIndentingTextEditModifier textModifierStyle(&textEditStyle);
|
||||||
|
|
||||||
|
QScopedPointer<RewriterView> styleRewriterView(new RewriterView(RewriterView::Amend, nullptr));
|
||||||
|
styleRewriterView->setTextModifier(&textModifierStyle);
|
||||||
|
styleModel->attachView(styleRewriterView.data());
|
||||||
|
|
||||||
|
StylesheetMerger merger(templateRewriterView.data(), styleRewriterView.data());
|
||||||
|
|
||||||
|
try {
|
||||||
|
merger.merge();
|
||||||
|
} catch (Exception &e) {
|
||||||
|
e.showException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parentRewriterView->textModifier()->textDocument()->setPlainText(templateRewriterView->textModifierContent());
|
||||||
|
} catch (Exception &e) {
|
||||||
|
e.showException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mergeWithTemplate(const SelectionContext &selectionContext)
|
||||||
|
{
|
||||||
|
const Utils::FilePath projectPath = Utils::FilePath::fromString(baseDirectory(selectionContext.view()->model()->fileUrl()));
|
||||||
|
|
||||||
|
const QString templateFile = getTemplateDialog(projectPath);
|
||||||
|
|
||||||
|
if (QFileInfo(templateFile).exists())
|
||||||
|
styleMerge(selectionContext, templateFile);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Mode
|
} // namespace Mode
|
||||||
|
|
||||||
} //QmlDesigner
|
} //QmlDesigner
|
||||||
|
@@ -81,6 +81,7 @@ void addCustomFlowEffect(const SelectionContext &selectionState);
|
|||||||
void setFlowStartItem(const SelectionContext &selectionContext);
|
void setFlowStartItem(const SelectionContext &selectionContext);
|
||||||
void addToGroupItem(const SelectionContext &selectionContext);
|
void addToGroupItem(const SelectionContext &selectionContext);
|
||||||
void selectFlowEffect(const SelectionContext &selectionContext);
|
void selectFlowEffect(const SelectionContext &selectionContext);
|
||||||
|
void mergeWithTemplate(const SelectionContext &selectionContext);
|
||||||
|
|
||||||
} // namespace ModelNodeOperationso
|
} // namespace ModelNodeOperationso
|
||||||
} //QmlDesigner
|
} //QmlDesigner
|
||||||
|
Reference in New Issue
Block a user