From 8f8c817294e8229a5fd58d034bbf3814f93fb949 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 2 Jul 2020 09:10:24 +0200 Subject: [PATCH 01/42] Utils: Fix small string ostream operator << It was used for test printing so we could see '\n' printed. I have to find a better way todo it only for tests. Task-number: QDS-2459 Change-Id: I10b38645bfdb8160cb9aeccd62c50a81fe953345 Reviewed-by: Tim Jenssen --- src/libs/utils/smallstringio.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/utils/smallstringio.h b/src/libs/utils/smallstringio.h index 233b9b5d97f..308b44f1551 100644 --- a/src/libs/utils/smallstringio.h +++ b/src/libs/utils/smallstringio.h @@ -78,12 +78,7 @@ QDebug &operator<<(QDebug &debug, const String &string) template std::ostream &operator<<(std::ostream &out, const BasicSmallString &string) { - BasicSmallString formatedString = string.clone(); - - formatedString.replace("\n", "\\n"); - formatedString.replace("\t", "\\t"); - - out.write(formatedString.data(), std::streamsize(formatedString.size())); + out.write(string.data(), std::streamsize(string.size())); return out; } From 33671897bdb508fd46ac9e2e48b67d000f3e15bd Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 2 Jul 2020 13:45:34 +0200 Subject: [PATCH 02/42] sqlite: exceptions are necessary for sqlite Qt itself is using no exceptions but if we want to use this sqlite access code we need to enable it. Change-Id: Id69b8527c612e06a534bc100e5339cddf5471917 Reviewed-by: Marco Bubke --- src/libs/sqlite/sqlite-lib.pri | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/sqlite/sqlite-lib.pri b/src/libs/sqlite/sqlite-lib.pri index 1ab76313875..8ded8ca1067 100644 --- a/src/libs/sqlite/sqlite-lib.pri +++ b/src/libs/sqlite/sqlite-lib.pri @@ -67,3 +67,5 @@ CONFIG(debug, debug|release): DEFINES += SQLITE_ENABLE_API_ARMOR OTHER_FILES += README.md contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols + +CONFIG += exceptions From 9bfb608986f332aaeb9cfae88d5f66c451dd2002 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 6 Jul 2020 12:51:22 +0200 Subject: [PATCH 03/42] QmlDesigner: Add adjustable frame color Task-number: QDS-2238 Change-Id: I6f43a7c87ae4c8daaf41c08ad2960502770fba58 Reviewed-by: Thomas Hartmann --- .../components/formeditor/formeditoritem.cpp | 14 +++++++++++++- .../components/formeditor/formeditoritem.h | 3 +++ .../components/formeditor/formeditorview.cpp | 5 +++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 3fc654ce861..63205e03031 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -283,6 +283,11 @@ bool FormEditorItem::flowHitTest(const QPointF & ) const return false; } +void FormEditorItem::setFrameColor(const QColor &color) +{ + m_frameColor = color; +} + FormEditorItem::~FormEditorItem() { scene()->removeItemFromHash(this); @@ -313,14 +318,21 @@ void FormEditorItem::paintBoundingRect(QPainter *painter) const pen.setJoinStyle(Qt::MiterJoin); const QColor frameColor(0xaa, 0xaa, 0xaa); - static const QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static const QColor selectionColor = Utils::creatorTheme()->color( + Utils::Theme::QmlDesigner_FormEditorSelectionColor); if (scene()->showBoundingRects()) { pen.setColor(frameColor.darker(150)); pen.setStyle(Qt::DotLine); painter->setPen(pen); painter->drawRect(m_boundingRect.adjusted(0., 0., -1., -1.)); + } + if (m_frameColor.isValid()) { + pen.setColor(m_frameColor); + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); + painter->drawRect(m_boundingRect.adjusted(0., 0., -1., -1.)); } if (m_highlightBoundingRect) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index 61ade499164..37e34fc4f78 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -117,6 +117,8 @@ public: virtual bool flowHitTest(const QPointF &point) const; + void setFrameColor(const QColor &color); + protected: AbstractFormEditorTool* tool() const; void paintBoundingRect(QPainter *painter) const; @@ -129,6 +131,7 @@ protected: QRectF m_boundingRect; QRectF m_paintedBoundingRect; QRectF m_selectionBoundingRect; + QColor m_frameColor{0xaa, 0xaa, 0xaa}; private: // functions void setup(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index f64b45f3517..da088876904 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -624,6 +624,11 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN editorItem->update(); } } + + if (name == "FrameColor@Internal") { + if (FormEditorItem *editorItem = scene()->itemForQmlItemNode(item)) + editorItem->setFrameColor(data.value()); + } } void FormEditorView::instancesCompleted(const QVector &completedNodeList) From 7504c966bf64a4b38b8e9ff200c93b2e8470acad Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 2 Jul 2020 14:49:26 +0200 Subject: [PATCH 04/42] QmlDesigner: Move code from list editor dialog to model And add tests to the code. Change-Id: I9fb183729c716a50bbab861d207a212ff704ee7b Reviewed-by: Tim Jenssen --- .../listmodeleditor/listmodeleditordialog.cpp | 32 +--- .../listmodeleditor/listmodeleditormodel.cpp | 54 +++++++ .../listmodeleditor/listmodeleditormodel.h | 11 +- tests/unit/unittest/listmodeleditor-test.cpp | 148 ++++++++++++++---- 4 files changed, 181 insertions(+), 64 deletions(-) diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp index 283acab78bb..968c719af08 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp @@ -114,40 +114,12 @@ void ListModelEditorDialog::openColumnDialog() void ListModelEditorDialog::removeRows() { - const QList indices = m_tableView->selectionModel()->selectedRows(); - std::vector rows; - rows.reserve(indices.size()); - - for (QModelIndex index : indices) - rows.push_back(index.row()); - - std::sort(rows.begin(), rows.end()); - - rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); - - std::reverse(rows.begin(), rows.end()); - - for (int row : rows) - m_model->removeRow(row); + m_model->removeRows(m_tableView->selectionModel()->selectedRows()); } void ListModelEditorDialog::removeColumns() { - const QList indices = m_tableView->selectionModel()->selectedColumns(); - std::vector columns; - columns.reserve(indices.size()); - - for (QModelIndex index : indices) - columns.push_back(index.column()); - - std::sort(columns.begin(), columns.end()); - - columns.erase(std::unique(columns.begin(), columns.end()), columns.end()); - - std::reverse(columns.begin(), columns.end()); - - for (int row : columns) - m_model->removeColumn(row); + m_model->removeColumns(m_tableView->selectionModel()->selectedColumns()); } void ListModelEditorDialog::changeHeader(int column) diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 98722c3e8fb..066af6e3451 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -260,6 +260,26 @@ void ListModelEditorModel::removeColumn(int column) } } +void ListModelEditorModel::removeColumns(const QList &indices) +{ + std::vector columns = filterColumns(indices); + + std::reverse(columns.begin(), columns.end()); + + for (int column : columns) + removeColumn(column); +} + +void ListModelEditorModel::removeRows(const QList &indices) +{ + std::vector rows = filterRows(indices); + + std::reverse(rows.begin(), rows.end()); + + for (int row : rows) + removeRow(row); +} + void ListModelEditorModel::removeRow(int row) { QList rowItems = QStandardItemModel::takeRow(row); @@ -299,4 +319,38 @@ void ListModelEditorModel::renameColumn(int oldColumn, const QString &newColumnN setHorizontalHeaderLabels(convertToStringList(m_propertyNames)); } +std::vector ListModelEditorModel::filterColumns(const QList &indices) +{ + std::vector columns; + columns.reserve(indices.size()); + + for (QModelIndex index : indices) { + if (index.column() >= 0) + columns.push_back(index.column()); + } + + std::sort(columns.begin(), columns.end()); + + columns.erase(std::unique(columns.begin(), columns.end()), columns.end()); + + return columns; +} + +std::vector ListModelEditorModel::filterRows(const QList &indices) +{ + std::vector rows; + rows.reserve(indices.size()); + + for (QModelIndex index : indices) { + if (index.row() >= 0) + rows.push_back(index.row()); + } + + std::sort(rows.begin(), rows.end()); + + rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); + + return rows; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h index 35d41bee68b..3976b34a9d8 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h @@ -33,6 +33,8 @@ namespace QmlDesigner { class ListModelEditorModel : public QStandardItemModel { + using QStandardItemModel::removeColumns; + using QStandardItemModel::removeRows; public: void setListModel(ModelNode node) @@ -48,11 +50,16 @@ public: bool setValue(int row, int column, QVariant value, Qt::ItemDataRole role = Qt::EditRole); - void removeColumn(int column); - void removeRow(int row); + void removeColumns(const QList &indices); + void removeRows(const QList &indices); void renameColumn(int column, const QString &newColumnName); + static std::vector filterColumns(const QList &indices); + static std::vector filterRows(const QList &indices); + private: + void removeRow(int row); + void removeColumn(int column); void populateModel(); void createItems(const QList &listElementNodes); void appendItems(const ModelNode &listElementNode); diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index ca0913f8656..1dcd54c2868 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -39,6 +39,7 @@ namespace { using QmlDesigner::AbstractProperty; using QmlDesigner::AbstractView; +using QmlDesigner::ListModelEditorModel; using QmlDesigner::ModelNode; MATCHER_P2(HasItem, @@ -172,6 +173,8 @@ public: return properties; } + QModelIndex index(int row, int column) const { return model.index(row, column); } + protected: std::unique_ptr designerModel{QmlDesigner::Model::create("QtQuick.Item", 1, 1)}; NiceMock mockView; @@ -427,7 +430,7 @@ TEST_F(ListModelEditor, RemoveColumnRemovesDisplayValues) { model.setListModel(listModelNode); - model.removeColumn(2); + model.removeColumns({index(0, 2)}); ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), "foo", 42), @@ -442,14 +445,14 @@ TEST_F(ListModelEditor, RemoveColumnRemovesProperties) EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element2, "image")))); EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element3, "image")))); - model.removeColumn(0); + model.removeColumns({index(0, 0)}); } TEST_F(ListModelEditor, RemoveColumnRemovesPropertyName) { model.setListModel(listModelNode); - model.removeColumn(1); + model.removeColumns({index(0, 1)}); ASSERT_THAT(model.propertyNames(), ElementsAre("image", "value", "value2")); } @@ -458,7 +461,7 @@ TEST_F(ListModelEditor, RemoveRowRemovesDisplayValues) { model.setListModel(listModelNode); - model.removeRow(1); + model.removeRows({index(1, 0)}); ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), @@ -471,7 +474,7 @@ TEST_F(ListModelEditor, RemoveRowRemovesElementInListModel) EXPECT_CALL(mockView, nodeRemoved(Eq(element2), _, _)); - model.removeRow(1); + model.removeRows({index(1, 0)}); } TEST_F(ListModelEditor, ConvertStringFloatToFloat) @@ -721,7 +724,7 @@ TEST_F(ListModelEditor, RemoveColumnAfterRenameColumn) model.setListModel(listModelNode); model.renameColumn(1, "mood"); - model.removeColumn(1); + model.removeColumns({index(0, 1)}); ASSERT_THAT(properties(), ElementsAre(UnorderedElementsAre(IsVariantProperty("value", 1), @@ -909,30 +912,7 @@ TEST_F(ListModelEditor, RemoveLastRow) model.addColumn("mood"); model.addRow(); - model.removeRow(0); - - ASSERT_THAT(displayValues(), IsEmpty()); -} - -TEST_F(ListModelEditor, RemoveLastColumn) -{ - model.setListModel(emptyListModelNode); - model.addColumn("mood"); - model.addRow(); - - model.removeColumn(0); - - ASSERT_THAT(displayValues(), ElementsAre(IsEmpty())); -} - -TEST_F(ListModelEditor, RemoveLastEmptyColumn) -{ - model.setListModel(emptyListModelNode); - model.addColumn("mood"); - model.addRow(); - model.removeRow(0); - - model.removeColumn(0); + model.removeRows({index(0, 0)}); ASSERT_THAT(displayValues(), IsEmpty()); } @@ -942,11 +922,115 @@ TEST_F(ListModelEditor, RemoveLastEmptyRow) model.setListModel(emptyListModelNode); model.addColumn("mood"); model.addRow(); - model.removeColumn(0); + model.removeColumns({index(0, 0)}); - model.removeRow(0); + model.removeRows({index(0, 0)}); + + ASSERT_THAT(displayValues(), ElementsAre(IsEmpty())); +} + +TEST_F(ListModelEditor, RemoveLastColumn) +{ + model.setListModel(emptyListModelNode); + model.addColumn("mood"); + model.addRow(); + + model.removeColumns({index(0, 0)}); + + ASSERT_THAT(displayValues(), ElementsAre(IsEmpty())); +} + +TEST_F(ListModelEditor, RemoveLastEmptyColumn) +{ + model.setListModel(emptyListModelNode); + model.addColumn("mood"); + model.addRow(); + model.removeRows({index(0, 0)}); + + model.removeColumns({index(0, 0)}); ASSERT_THAT(displayValues(), IsEmpty()); } +TEST_F(ListModelEditor, RemoveColumns) +{ + model.setListModel(listModelNode); + model.removeColumns({index(0, 1), index(0, 3), index(1, 1), index(0, 4)}); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("value", 1)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, RemoveRows) +{ + model.setListModel(listModelNode); + + model.removeRows({index(1, 0), index(2, 0), index(3, 0), index(2, 0)}); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)))); +} + +TEST_F(ListModelEditor, FilterColumns) +{ + model.setListModel(listModelNode); + QList indices = {index(0, 0), index(1, 1), index(0, 2), index(0, 1)}; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, ElementsAre(0, 1, 2)); +} + +TEST_F(ListModelEditor, FilterColumnsInvalidColumns) +{ + QList indices = {index(0, 0), index(1, 1), index(0, 2), index(0, 1)}; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterColumnsEmptyInput) +{ + QList indices; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterRows) +{ + model.setListModel(listModelNode); + QList indices = {index(0, 0), index(1, 1), index(2, 2), index(0, 1)}; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, ElementsAre(0, 1, 2)); +} + +TEST_F(ListModelEditor, FilterRowsInvalidColumns) +{ + QList indices = {index(0, 0), index(1, 1), index(2, 2), index(0, 1)}; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterRowsEmptyInput) +{ + QList indices; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, IsEmpty()); +} + } // namespace From b73ce9eccfc5f3e06fa92efb53bd8c341c13f3e7 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 2 Jul 2020 19:19:52 +0200 Subject: [PATCH 05/42] QmlDesigner: Add row move up and move down buttons Task-number: QDS-2294 Change-Id: Ia1e64d0811f55151dfe529db4868821840a8fba9 Reviewed-by: Tim Jenssen --- .../listmodeleditor/listmodeleditordialog.cpp | 19 ++ .../listmodeleditor/listmodeleditordialog.h | 4 + .../listmodeleditor/listmodeleditormodel.cpp | 36 +++ .../listmodeleditor/listmodeleditormodel.h | 3 + tests/unit/unittest/listmodeleditor-test.cpp | 239 ++++++++++++++++++ 5 files changed, 301 insertions(+) diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp index 968c719af08..0cf73976a84 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp @@ -27,6 +27,7 @@ #include "listmodeleditormodel.h" #include +#include #include #include @@ -71,6 +72,10 @@ ListModelEditorDialog::ListModelEditorDialog(QWidget *parent) m_addColumnAction = toolBar->addAction(getIcon(Theme::Icon::addColumnAfter), tr("Add Column")); m_removeColumnsAction = toolBar->addAction(getIcon(Theme::Icon::deleteColumn), tr("Remove Columns")); + m_moveDownAction = toolBar->addAction(Icons::ARROW_DOWN.icon(), tr("Move down (CTRL + Down).")); + m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Down | Qt::CTRL)); + m_moveUpAction = toolBar->addAction(Icons::ARROW_UP.icon(), tr("Move up (CTRL + Up).")); + m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Up | Qt::CTRL)); } ListModelEditorDialog::~ListModelEditorDialog() = default; @@ -83,6 +88,8 @@ void ListModelEditorDialog::setModel(ListModelEditorModel *model) connect(m_addColumnAction, &QAction::triggered, this, &ListModelEditorDialog::openColumnDialog); connect(m_removeRowsAction, &QAction::triggered, this, &ListModelEditorDialog::removeRows); connect(m_removeColumnsAction, &QAction::triggered, this, &ListModelEditorDialog::removeColumns); + connect(m_moveDownAction, &QAction::triggered, this, &ListModelEditorDialog::moveRowsDown); + connect(m_moveUpAction, &QAction::triggered, this, &ListModelEditorDialog::moveRowsUp); connect(m_tableView->horizontalHeader(), &QHeaderView::sectionDoubleClicked, this, @@ -134,4 +141,16 @@ void ListModelEditorDialog::changeHeader(int column) m_model->renameColumn(column, newPropertyName); } +void ListModelEditorDialog::moveRowsDown() +{ + QItemSelection selection = m_model->moveRowsDown(m_tableView->selectionModel()->selectedRows()); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); +} + +void ListModelEditorDialog::moveRowsUp() +{ + QItemSelection selection = m_model->moveRowsUp(m_tableView->selectionModel()->selectedRows()); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h index 519d0869fae..24e19c8ff97 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h @@ -59,6 +59,8 @@ private: void removeRows(); void removeColumns(); void changeHeader(int column); + void moveRowsDown(); + void moveRowsUp(); private: ListModelEditorModel *m_model{}; @@ -66,6 +68,8 @@ private: QAction *m_removeRowsAction{}; QAction *m_addColumnAction{}; QAction *m_removeColumnsAction{}; + QAction *m_moveUpAction{}; + QAction *m_moveDownAction{}; QTableView *m_tableView{}; }; diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 066af6e3451..0aeabb8b895 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -319,6 +319,42 @@ void ListModelEditorModel::renameColumn(int oldColumn, const QString &newColumnN setHorizontalHeaderLabels(convertToStringList(m_propertyNames)); } +QItemSelection ListModelEditorModel::moveRowsUp(const QList &indices) +{ + std::vector rows = filterRows(indices); + + if (rows.empty() || rows.front() < 1) + return {}; + + auto nodeListProperty = m_listModelNode.defaultNodeListProperty(); + + for (int row : rows) { + insertRow(row - 1, takeRow(row)); + nodeListProperty.slide(row, row - 1); + } + + return {index(rows.front() - 1, 0), index(rows.back() - 1, columnCount() - 1)}; +} + +QItemSelection ListModelEditorModel::moveRowsDown(const QList &indices) +{ + std::vector rows = filterRows(indices); + + if (rows.empty() || rows.back() >= (rowCount() - 1)) + return {}; + + auto nodeListProperty = m_listModelNode.defaultNodeListProperty(); + + std::reverse(rows.begin(), rows.end()); + + for (int row : rows) { + insertRow(row + 1, takeRow(row)); + nodeListProperty.slide(row, row + 1); + } + + return {index(rows.front() + 1, 0), index(rows.back() + 1, columnCount() - 1)}; +} + std::vector ListModelEditorModel::filterColumns(const QList &indices) { std::vector columns; diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h index 3976b34a9d8..3056d32dbb0 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h @@ -27,6 +27,7 @@ #include +#include #include namespace QmlDesigner { @@ -53,6 +54,8 @@ public: void removeColumns(const QList &indices); void removeRows(const QList &indices); void renameColumn(int column, const QString &newColumnName); + QItemSelection moveRowsUp(const QList &indices); + QItemSelection moveRowsDown(const QList &indices); static std::vector filterColumns(const QList &indices); static std::vector filterRows(const QList &indices); diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index 1dcd54c2868..af4009ae9b0 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -175,6 +175,11 @@ public: QModelIndex index(int row, int column) const { return model.index(row, column); } + QList elements(const ModelNode &node) const + { + return node.defaultNodeListProperty().toModelNodeList(); + } + protected: std::unique_ptr designerModel{QmlDesigner::Model::create("QtQuick.Item", 1, 1)}; NiceMock mockView; @@ -1033,4 +1038,238 @@ TEST_F(ListModelEditor, FilterRowsEmptyInput) ASSERT_THAT(rows, IsEmpty()); } +TEST_F(ListModelEditor, CannotMoveEmptyRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, MoveRowUp) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(1, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element2, element1, element3)); +} + +TEST_F(ListModelEditor, MoveRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(2, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element2, element3, element1)); +} + +TEST_F(ListModelEditor, CannotMoveFirstRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(0, 1), index(1, 2), index(0, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsUpDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, CannotMoveFirstRowUpDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(0, 1), index(1, 2), index(0, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, MoveRowsUpDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(2, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()), + ElementsAre(IsInvalid(), "foo", 1, 42))); +} + +TEST_F(ListModelEditor, NoSelectionAfterCannotMoveLastRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(0, 1), index(1, 2), index(0, 0)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, NoSelectionAfterMoveEmptyRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, SelectionAfterMoveRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(2, 2), index(1, 0)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), + ElementsAre(index(0, 0), + index(0, 1), + index(0, 2), + index(0, 3), + index(1, 0), + index(1, 1), + index(1, 2), + index(1, 3))); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, MoveRowDown) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(1, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element3, element2)); +} + +TEST_F(ListModelEditor, MoveRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(0, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element3, element1, element2)); +} + +TEST_F(ListModelEditor, CannotMoveLastRowsDown) +{ + model.setListModel(listModelNode); + QList indices = {index(2, 1), index(1, 2), index(2, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsDownDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, CannotMoveLastRowDownDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(2, 1), index(1, 2), index(2, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, MoveRowsDownDisplayValues) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(0, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre("pic.png", "poo", 111, IsInvalid()), + ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()))); +} + +TEST_F(ListModelEditor, NoSelectionAfterCannotMoveLastRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(2, 1), index(1, 2), index(2, 0)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, NoSelectionAfterMoveEmptyRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(-1, 1)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, SelectionAfterMoveRowsUp) +{ + model.setListModel(listModelNode); + QList indices = {index(1, 1), index(0, 2), index(1, 0)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), + ElementsAre(index(1, 0), + index(1, 1), + index(1, 2), + index(1, 3), + index(2, 0), + index(2, 1), + index(2, 2), + index(2, 3))); +} + } // namespace From 42d0c2b9f402fab26e88f2e404b422a23d75d740 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Fri, 10 Jul 2020 00:23:52 +0200 Subject: [PATCH 06/42] add litehtml to dev package Change-Id: If33500eda8d095d1d9d63afe4ad8331109a22871 Reviewed-by: Eike Ziller --- scripts/createDevPackage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/createDevPackage.py b/scripts/createDevPackage.py index 1c8b85d1437..f6613b38302 100755 --- a/scripts/createDevPackage.py +++ b/scripts/createDevPackage.py @@ -59,6 +59,7 @@ source_include_patterns = [ # directories r"^(?!(share|tests)/.*$)(.*/)?$", # look into all directories except under share/ and tests/ r"^share/(qtcreator/(qml/(qmlpuppet/(.*/)?)?)?)?$", # for shared headers for qt quick designer plugins + r"^src/plugins/help/qlitehtml/.*\.(h|pri|cpp|c|txt|md)$", # litehtml is used by extra plugins # files r"^HACKING$", r"^LICENSE.*$", From f1d5e56f09cbfbc4520b865adaa14625db055dc3 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Tue, 14 Jul 2020 11:14:36 +0200 Subject: [PATCH 07/42] AssetExport: Fix metadata schema Task-number: QDS-1556 Change-Id: I13aef8af5094cf6a5c3f53f92643fd23492922d8 Reviewed-by: Tim Jenssen --- .../assetexportpluginconstants.h | 2 +- .../parsers/assetnodeparser.cpp | 5 ++++- .../parsers/modelitemnodeparser.cpp | 14 ++++++++------ .../assetexporterplugin/parsers/textnodeparser.cpp | 4 +++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index a1c0e2181ca..87024355274 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -50,7 +50,7 @@ const char YPosTag[] = "y"; const char WidthTag[] = "width"; const char HeightTag[] = "height"; - +const char MetadataTag[] = "metadata"; const char QmlIdTag[] = "qmlId"; const char ExportTypeTag[] = "exportType"; const char QmlPropertiesTag[] = "qmlProperties"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp index a42b7300624..adc46678aed 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp @@ -58,7 +58,10 @@ QJsonObject AssetNodeParser::json(Component &component) const Utils::FilePath assetPath = component.exporter().exportAsset(objectNode(), uuid()); QJsonObject assetData; assetData.insert(AssetPathTag, assetPath.toString()); - jsonObject.insert(AssetDataTag, assetData); + + QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); + metadata.insert(AssetDataTag, assetData); + jsonObject.insert(MetadataTag, metadata); return jsonObject; } } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp index 5104732e1c0..5a236a366c7 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp @@ -47,10 +47,9 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) Q_UNUSED(component); const QmlObjectNode &qmlObjectNode = objectNode(); QJsonObject jsonObject; - jsonObject.insert(QmlIdTag, qmlObjectNode.id()); - QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); // Position relative to parent + QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); QPointF pos = itemNode.instancePosition(); jsonObject.insert(XPosTag, pos.x()); jsonObject.insert(YPosTag, pos.y()); @@ -60,10 +59,13 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) jsonObject.insert(WidthTag, size.width()); jsonObject.insert(HeightTag, size.height()); - jsonObject.insert(UuidTag, uuid()); - jsonObject.insert(ExportTypeTag, "child"); - jsonObject.insert(TypeNameTag, QString::fromLatin1(m_node.type())); + QJsonObject metadata; + metadata.insert(QmlIdTag, qmlObjectNode.id()); + metadata.insert(UuidTag, uuid()); + metadata.insert(ExportTypeTag, "child"); + metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); - return jsonObject; + jsonObject.insert(MetadataTag, metadata); + return jsonObject; } } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp index bffe5ed8d5c..9b797ad77db 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp @@ -81,7 +81,9 @@ QJsonObject TextNodeParser::json(Component &component) const textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0); - jsonObject.insert(TextDetailsTag, textDetails); + QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); + metadata.insert(TextDetailsTag, textDetails); + jsonObject.insert(MetadataTag, metadata); return jsonObject; } } From f2ea02561eaa99b468d169bb29796e9131a39569 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Tue, 14 Jul 2020 11:29:06 +0200 Subject: [PATCH 08/42] AssetExport: Assign export type component to QML components Task-number: QDS-1556 Change-Id: I99c0e0219aa040b74794ab28cf0da7970a81663a Reviewed-by: Tim Jenssen --- .../assetexporterplugin/assetexportpluginconstants.h | 3 +++ .../qmldesigner/assetexporterplugin/componentexporter.cpp | 7 ++++++- .../assetexporterplugin/parsers/modelitemnodeparser.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index 87024355274..063a4a6e8c3 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -51,8 +51,11 @@ const char WidthTag[] = "width"; const char HeightTag[] = "height"; const char MetadataTag[] = "metadata"; +const char ChildrenTag[] = "children"; const char QmlIdTag[] = "qmlId"; const char ExportTypeTag[] = "exportType"; +const char ExportTypeComponent[] = "component"; +const char ExportTypeChild[] = "child"; const char QmlPropertiesTag[] = "qmlProperties"; const char ImportsTag[] = "extraImports"; const char UuidTag[] = "uuid"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp index 059b6ecb161..973cb6e013a 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp @@ -53,6 +53,7 @@ static void populateLineage(const QmlDesigner::ModelNode &node, QByteArrayList & } namespace QmlDesigner { +using namespace Constants; std::vector> Component::m_readers; Component::Component(AssetExporter &exporter, const ModelNode &rootNode): @@ -76,6 +77,10 @@ void Component::exportComponent() { QTC_ASSERT(m_rootNode.isValid(), return); m_json = nodeToJson(m_rootNode); + // Change the export type to component + QJsonObject metadata = m_json.value(MetadataTag).toObject(); + metadata.insert(ExportTypeTag, ExportTypeComponent); + m_json.insert(MetadataTag, metadata); addImports(); } @@ -124,7 +129,7 @@ QJsonObject Component::nodeToJson(const ModelNode &node) children.append(nodeToJson(childnode)); if (!children.isEmpty()) - jsonObject.insert("children", children); + jsonObject.insert(ChildrenTag, children); return jsonObject; } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp index 5a236a366c7..c8439c9ac16 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp @@ -62,7 +62,7 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) QJsonObject metadata; metadata.insert(QmlIdTag, qmlObjectNode.id()); metadata.insert(UuidTag, uuid()); - metadata.insert(ExportTypeTag, "child"); + metadata.insert(ExportTypeTag, ExportTypeChild); metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); jsonObject.insert(MetadataTag, metadata); From 443f9d1619c86774699322bbcb873d4cce1805ec Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Wed, 15 Jul 2020 11:08:05 +0200 Subject: [PATCH 09/42] AssetExporter: Add display name to the exported json This shall be used for layer names Task-number: QDS-1556 Change-Id: I3ffce208d830f291de48105ec9cf92e76692f8bd Reviewed-by: Tim Jenssen --- .../assetexportpluginconstants.h | 2 ++ .../parsers/modelitemnodeparser.cpp | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index 063a4a6e8c3..5f09b748603 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -45,6 +45,8 @@ const char DocumentNameTag[] = "name"; // Layer data tags const char ArtboardListTag[] = "artboards"; +const char NameTag[] = "name"; + const char XPosTag[] = "x"; const char YPosTag[] = "y"; const char WidthTag[] = "width"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp index c8439c9ac16..43963aa8b26 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp @@ -28,6 +28,17 @@ #include "qmlitemnode.h" +namespace { +static QString capitalize(const QString &str) +{ + if (str.isEmpty()) + return {}; + QString tmp = str; + tmp[0] = QChar(str[0]).toUpper().toLatin1(); + return tmp; +} +} + namespace QmlDesigner { using namespace Constants; ItemNodeParser::ItemNodeParser(const QByteArrayList &lineage, @@ -48,6 +59,13 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) const QmlObjectNode &qmlObjectNode = objectNode(); QJsonObject jsonObject; + const QString qmlId = qmlObjectNode.id(); + QString name = m_node.simplifiedTypeName(); + if (!qmlId.isEmpty()) + name.append("_" + capitalize(qmlId)); + + jsonObject.insert(NameTag, name); + // Position relative to parent QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); QPointF pos = itemNode.instancePosition(); @@ -60,7 +78,7 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) jsonObject.insert(HeightTag, size.height()); QJsonObject metadata; - metadata.insert(QmlIdTag, qmlObjectNode.id()); + metadata.insert(QmlIdTag, qmlId); metadata.insert(UuidTag, uuid()); metadata.insert(ExportTypeTag, ExportTypeChild); metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); From 1cb1298a6d336810cfda86c97b4a785e37466dbb Mon Sep 17 00:00:00 2001 From: Knud Dollereder Date: Wed, 15 Jul 2020 14:17:34 +0200 Subject: [PATCH 10/42] Fix a crash in the curve editor when ICore::dialogParent returns an unrelated dialog Change-Id: Ifd0facec3b8b97f6fba61904c2a729e46622e515 Reviewed-by: Vikas Pachdha --- .../qmldesigner/components/timelineeditor/timelinetoolbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index 39874f51b06..7d11f57cb39 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp @@ -103,7 +103,7 @@ static QAction *createAction(const Core::Id &id, TimelineToolBar::TimelineToolBar(QWidget *parent) : QToolBar(parent) , m_grp() - , m_dialog(new AnimationCurveDialog(Core::ICore::dialogParent())) + , m_dialog(new AnimationCurveDialog(this)) , m_curveModel(new AnimationCurveEditorModel(0., 500.)) { m_dialog->setModel(m_curveModel); From e95a95cdfeffb63ea73acd63c7f5b1743df76ed0 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 22 Jun 2020 19:38:36 +0200 Subject: [PATCH 11/42] projectexplorer: add availableQmlPreviewTranslations() Preparation for a test translations feature. Change-Id: I1a7ccecab803f5838cd765b7dca99bcf5bb9e8a1 Reviewed-by: Tim Jenssen --- src/plugins/projectexplorer/project.cpp | 15 +++++++++ src/plugins/projectexplorer/project.h | 2 ++ .../qmlpreviewplugin/qmlpreviewactions.cpp | 32 ++++++------------- .../qmlpreviewplugin/qmlpreviewactions.h | 3 +- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index e7e6599ed91..254ac123bfa 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -983,6 +983,21 @@ QVariant Project::extraData(const QString &key) const return d->m_extraData.value(key); } +QStringList Project::availableQmlPreviewTranslations(QString *errorMessage) +{ + const auto projectDirectory = rootProjectDirectory().toFileInfo().absoluteFilePath(); + const QDir languageDirectory(projectDirectory + "/i18n"); + const auto qmFiles = languageDirectory.entryList({"qml_*.qm"}); + if (qmFiles.isEmpty() && errorMessage) + errorMessage->append(tr("Could not find any qml_*.qm file at '%1'").arg(languageDirectory.absolutePath())); + return Utils::transform(qmFiles, [](const QString &qmFile) { + const int localeStartPosition = qmFile.lastIndexOf("_") + 1; + const int localeEndPosition = qmFile.size() - QString(".qm").size(); + const QString locale = qmFile.left(localeEndPosition).mid(localeStartPosition); + return locale; + }); +} + #if defined(WITH_TESTS) } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index b6b65e68893..58d6a914b86 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -173,6 +173,8 @@ public: void setExtraData(const QString &key, const QVariant &data); QVariant extraData(const QString &key) const; + QStringList availableQmlPreviewTranslations(QString *errorMessage); + signals: void projectFileIsDirty(const Utils::FilePath &path); diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 190d52b349d..966466e3b05 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -221,15 +221,21 @@ SwitchLanguageComboboxAction::SwitchLanguageComboboxAction(QObject *parent) QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) { QPointer comboBox = new QComboBox(parent); - comboBox->setToolTip(tr("Switch the language used by preview.")); + const QString toolTip(tr("Switch the language used by preview.")); + comboBox->setToolTip(toolTip); comboBox->addItem(tr("Default")); - auto refreshComboBoxFunction = [this, comboBox] (ProjectExplorer::Project *project) { + auto refreshComboBoxFunction = [this, comboBox, toolTip] (ProjectExplorer::Project *project) { if (comboBox) { - if (updateProjectLocales(project)) { + QString errorMessage; + auto locales = project->availableQmlPreviewTranslations(&errorMessage); + if (!errorMessage.isEmpty()) + comboBox->setToolTip(QString("%1
(%2)").arg(toolTip, errorMessage)); + if (m_previousLocales != locales) { comboBox->clear(); comboBox->addItem(tr("Default")); - comboBox->addItems(m_localeStrings); + comboBox->addItems(locales); + m_previousLocales = locales; } } }; @@ -250,24 +256,6 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) return comboBox; } -bool SwitchLanguageComboboxAction::updateProjectLocales(Project *project) -{ - if (!project) - return false; - auto previousLocales = m_localeStrings; - m_localeStrings.clear(); - const auto projectDirectory = project->rootProjectDirectory().toFileInfo().absoluteFilePath(); - const QDir languageDirectory(projectDirectory + "/i18n"); - const auto qmFiles = languageDirectory.entryList({"qml_*.qm"}); - m_localeStrings = Utils::transform(qmFiles, [](const QString &qmFile) { - const int localeStartPosition = qmFile.lastIndexOf("_") + 1; - const int localeEndPosition = qmFile.size() - QString(".qm").size(); - const QString locale = qmFile.left(localeEndPosition).mid(localeStartPosition); - return locale; - }); - return previousLocales != m_localeStrings; -} - SwitchLanguageAction::SwitchLanguageAction() : m_switchLanguageAction(new SwitchLanguageComboboxAction(nullptr)) { diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h index a23f125ca14..8bbbc453d6b 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h @@ -113,8 +113,7 @@ signals: protected: QWidget *createWidget(QWidget *parent) override; private: - bool updateProjectLocales(ProjectExplorer::Project *project); - QStringList m_localeStrings; + QStringList m_previousLocales; }; class SwitchLanguageAction : public ActionInterface From 2a0ca04be0a3d85869afe2c87efb1669d1209284 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Wed, 24 Jun 2020 03:50:51 +0200 Subject: [PATCH 12/42] qmlpreview: remove unnecessary connect Change-Id: Ib846256c0eef2d2cccfd46a9ac27dbbf312eef8b Reviewed-by: Tim Jenssen --- .../qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 966466e3b05..95fa7a5164a 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -212,10 +212,6 @@ void FpsAction::currentContextChanged(const SelectionContext &) SwitchLanguageComboboxAction::SwitchLanguageComboboxAction(QObject *parent) : QWidgetAction(parent) { - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, - this, - &SwitchLanguageComboboxAction::updateProjectLocales); } QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) From 5b63599473ead47a44d7f1a35634d08aab57d2f7 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Fri, 17 Jul 2020 11:16:04 +0200 Subject: [PATCH 13/42] QmlDesigner: Bump up database version to 2 Change-Id: I4b4c4c46c3bd17d2b4f4f80b8fb97f5716261f77 Reviewed-by: Tim Jenssen --- src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 64dd8a93d60..4159d633ddc 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -46,7 +46,8 @@ static bool isMultilanguagePresent() static Utils::FilePath getMultilanguageDatabaseFilePath(ProjectExplorer::Target *target) { if (target) { - auto filePath = target->project()->projectDirectory().pathAppended("/multilanguage-experimental-v1.db"); + auto filePath = target->project()->projectDirectory().pathAppended( + "/multilanguage-experimental-v2.db"); if (filePath.exists()) return filePath; } From da29ff04334f490ff5daf102d81d77f044992e6c Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Fri, 17 Jul 2020 11:17:17 +0200 Subject: [PATCH 14/42] QmlDesigner: Fix prefiew size Change-Id: I126c50209f2849d0212f0d295b35cba0f25f2728 Reviewed-by: Tim Jenssen --- .../qml2puppet/instances/qt5previewnodeinstanceserver.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp index 6e500120634..50db9b99719 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp @@ -104,7 +104,7 @@ QImage Qt5PreviewNodeInstanceServer::renderPreviewImage() QSize previewImageSize = boundingRect.size().toSize(); - if (!m_previewSize.isNull()) + if (m_previewSize.isValid() && !m_previewSize.isNull()) previewImageSize.scale(m_previewSize, Qt::KeepAspectRatio); QImage previewImage = rootNodeInstance().renderPreviewImage(previewImageSize); @@ -123,9 +123,6 @@ void Qt5PreviewNodeInstanceServer::changePreviewImageSize( { m_previewSize = command.size; - if (!command.size.isValid()) - m_previewSize = {160, 160}; - collectItemChangesAndSendChangeCommands(); } From 2fc89375c6f79731e72d7012dc8bc179470cc572 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 10:28:16 +0200 Subject: [PATCH 15/42] qmlproject: act on disabling multiLanguageAspect Change-Id: I2f13090f8708a5cb46bf21fb0e855d8ca9d906e7 Reviewed-by: Marco Bubke --- src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index 8ec0c29246f..a0bfa812116 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -118,6 +118,9 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id) if (m_multiLanguageAspect && m_multiLanguageAspect->value() && !m_multiLanguageAspect->databaseFilePath().isEmpty()) { env.set("QT_MULTILANGUAGE_DATABASE", m_multiLanguageAspect->databaseFilePath().toString()); env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->lastUsedLanguage()); + } else { + env.unset("QT_MULTILANGUAGE_DATABASE"); + env.unset("QT_MULTILANGUAGE_LANGUAGE"); } return env; }; From f323a80d57ddb986f2d1ec39f5c97195c8e11211 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 16:25:53 +0200 Subject: [PATCH 16/42] qmlproject: no need to use raw pointer here Change-Id: Idbd97a7ab4a78d3e56b3f444b7b48111d7823dd6 Reviewed-by: Marco Bubke --- src/plugins/qmlprojectmanager/qmlproject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index fe7542e8cbe..68d6c8f6fd4 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -371,7 +371,7 @@ void QmlBuildSystem::generateProjectTree() auto newRoot = std::make_unique(project()); - for (const QString &f : m_projectItem.data()->files()) { + for (const QString &f : m_projectItem->files()) { const Utils::FilePath fileName = Utils::FilePath::fromString(f); const FileType fileType = (fileName == projectFilePath()) ? FileType::Project : FileNode::fileTypeForFileName(fileName); From d628a7c6cd5ca6f7eb8689c14d42a2db3817ad3a Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 16:26:49 +0200 Subject: [PATCH 17/42] qmlproject: remove unnecessary separator results in two separators which looks wrong Change-Id: Ie715e1b504303a9ab819ede08b1e69095c5d870d Reviewed-by: Marco Bubke --- src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 4159d633ddc..39f79339640 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -47,7 +47,7 @@ static Utils::FilePath getMultilanguageDatabaseFilePath(ProjectExplorer::Target { if (target) { auto filePath = target->project()->projectDirectory().pathAppended( - "/multilanguage-experimental-v2.db"); + "multilanguage-experimental-v2.db"); if (filePath.exists()) return filePath; } From cb13e84656d84548692d76218d53985ff536ee7f Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 16:29:45 +0200 Subject: [PATCH 18/42] qmlpreview: remove redundant set QT_MULTILANGUAGE_DATABASE already done in the environment modifier method at qmlprojectrunconfiguration.cpp Change-Id: Ibd1e2ffb94bec23e709d9155b032bb358b7f4a0c Reviewed-by: Marco Bubke --- src/plugins/qmlpreview/qmlpreviewruncontrol.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index f1caffa1a7a..71c88087da3 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -153,11 +153,6 @@ LocalQmlPreviewSupport::LocalQmlPreviewSupport(ProjectExplorer::RunControl *runC } } - if (auto multiLanguageAspect = runControl->aspect()) { - if (!multiLanguageAspect->databaseFilePath().isEmpty()) - runnable.environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); - } - Utils::QtcProcess::addArg(&runnable.commandLineArguments, QmlDebug::qmlDebugLocalArguments(QmlDebug::QmlPreviewServices, serverUrl.path())); From 714431235850521a56ad4d3da44bceef70c09762 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 16:31:14 +0200 Subject: [PATCH 19/42] qmlpreview: fix crash while closing Change-Id: Iee1083ff7c3e2e6dd511f1d54d72681e402d6c24 Reviewed-by: Marco Bubke --- src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 95fa7a5164a..09650746ea9 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -222,7 +222,7 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) comboBox->addItem(tr("Default")); auto refreshComboBoxFunction = [this, comboBox, toolTip] (ProjectExplorer::Project *project) { - if (comboBox) { + if (comboBox && project) { QString errorMessage; auto locales = project->availableQmlPreviewTranslations(&errorMessage); if (!errorMessage.isEmpty()) From 00b54f7faf33d679d62d309c9ccaf7ef60087483 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 16:32:02 +0200 Subject: [PATCH 20/42] qmlpuppet: do not set an empty QT_MULTILANGUAGE_DATABASE Change-Id: Ib64a2192692a2675852fd3071295c0ed8dc29d4d Reviewed-by: Marco Bubke --- .../qmldesigner/designercore/instances/puppetcreator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 943e21e6f7f..c82495437a5 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -514,7 +514,7 @@ QProcessEnvironment PuppetCreator::processEnvironment() const if (auto *rc = m_target->activeRunConfiguration()) { if (auto multiLanguageAspect = rc->aspect()) { - if (!multiLanguageAspect->databaseFilePath().isEmpty()) + if (multiLanguageAspect->value() && !multiLanguageAspect->databaseFilePath().isEmpty()) environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); } } From 69b0a42ac14c0f754bf9f594c74d2073e1754abb Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 18:28:02 +0200 Subject: [PATCH 21/42] qmlpreview: remove unnecessary include Change-Id: Ia2ffd868bcb1cb1b09aafa1f63132308bf829062 Reviewed-by: Marco Bubke --- src/plugins/qmlpreview/qmlpreviewruncontrol.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index 71c88087da3..c513a580f31 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include From 6e6b2aa855c30f264a38c9499e4c5183b5aedd0b Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 20:01:16 +0200 Subject: [PATCH 22/42] Revert "qmlpuppet: do not set an empty QT_MULTILANGUAGE_DATABASE" This reverts commit 00b54f7faf33d679d62d309c9ccaf7ef60087483. Not sure why this was necessary - but it introduced a problem which needs a restart of the puppet - so it is easier to revert it. Change-Id: I4664b57eefc961ac814e4594b28bbd155be21042 Reviewed-by: Marco Bubke --- .../qmldesigner/designercore/instances/puppetcreator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index c82495437a5..943e21e6f7f 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -514,7 +514,7 @@ QProcessEnvironment PuppetCreator::processEnvironment() const if (auto *rc = m_target->activeRunConfiguration()) { if (auto multiLanguageAspect = rc->aspect()) { - if (multiLanguageAspect->value() && !multiLanguageAspect->databaseFilePath().isEmpty()) + if (!multiLanguageAspect->databaseFilePath().isEmpty()) environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); } } From 3659f5b41d3cf533b7cd7aec67281200a0c5d73f Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 20:43:46 +0200 Subject: [PATCH 23/42] qmlpreview: stop previews if the language backend is changed Change-Id: Iadf0712ea429f3bdb8c4109dfb558466fa2743f2 Reviewed-by: Marco Bubke --- src/plugins/qmlpreview/qmlpreviewplugin.cpp | 5 +++++ src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index 6c92c12b0a1..f0643955922 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -53,6 +53,7 @@ #include #include +#include #include using namespace ProjectExplorer; @@ -412,6 +413,10 @@ void QmlPreviewPluginPrivate::setDirty() void QmlPreviewPluginPrivate::addPreview(ProjectExplorer::RunControl *preview) { m_runningPreviews.append(preview); + if (auto multiLanguageAspect = preview->aspect()) { + connect(multiLanguageAspect, &QmlProjectManager::QmlMultiLanguageAspect::changed, + preview, &ProjectExplorer::RunControl::initiateStop); + } emit q->runningPreviewsChanged(m_runningPreviews); } diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 39f79339640..94e36919fe6 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -97,7 +97,6 @@ void QmlMultiLanguageAspect::setLastUsedLanguage(const QString &language) previewPlugin->setProperty("locale", language); if (m_lastUsedLanguage != language) { m_lastUsedLanguage = language; - emit changed(); } } From b45b771581860f26a99950954e5c47e614f36943 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 21 Jul 2020 13:47:14 +0200 Subject: [PATCH 24/42] qmlproject: add convenience method to QmlMultiLanguageAspect Change-Id: I88799aa1c5caa5c967b7c680ef9ddcbdd4b01bf5 Reviewed-by: Marco Bubke --- .../instances/nodeinstanceview.cpp | 16 ++++--------- .../designercore/instances/puppetcreator.cpp | 8 +++---- .../qmlmultilanguageaspect.cpp | 24 +++++++++++++++++++ .../qmlmultilanguageaspect.h | 4 ++++ 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index d87bc128d2b..2f491e8f6be 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -546,12 +546,8 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, } } else if (node.isRootNode() && name == "language@Internal") { const QString languageAsString = value.toString(); - if (m_currentTarget) { - if (auto rc = m_currentTarget->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect()) - multiLanguageAspect->setLastUsedLanguage(languageAsString); - } - } + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) + multiLanguageAspect->setLastUsedLanguage(languageAsString); nodeInstanceServer()->changeLanguage({languageAsString}); } else if (node.isRootNode() && name == "previewSize@Internal") { nodeInstanceServer()->changePreviewImageSize(value.toSize()); @@ -994,12 +990,8 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() } QString lastUsedLanguage; - if (m_currentTarget) { - if (auto rc = m_currentTarget->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect()) - lastUsedLanguage = multiLanguageAspect->lastUsedLanguage(); - } - } + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) + lastUsedLanguage = multiLanguageAspect->lastUsedLanguage(); return CreateSceneCommand( instanceContainerList, diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 943e21e6f7f..7eefa7f1bca 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -512,11 +512,9 @@ QProcessEnvironment PuppetCreator::processEnvironment() const customFileSelectors = m_target->additionalData("CustomFileSelectorsData").toStringList(); - if (auto *rc = m_target->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect()) { - if (!multiLanguageAspect->databaseFilePath().isEmpty()) - environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); - } + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_target)) { + if (!multiLanguageAspect->databaseFilePath().isEmpty()) + environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); } } diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 94e36919fe6..623e51c66e9 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -30,6 +30,7 @@ #include #include +#include #include static bool isMultilanguagePresent() @@ -125,4 +126,27 @@ void QmlMultiLanguageAspect::fromMap(const QVariantMap &map) setLastUsedLanguage(map.value(Constants::LAST_USED_LANGUAGE, "en").toString()); } +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current() +{ + if (auto project = ProjectExplorer::SessionManager::startupProject()) + return current(project); + return {}; +} + +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current(ProjectExplorer::Project *project) +{ + if (auto target = project->activeTarget()) + return current(target); + return {}; +} + +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current(ProjectExplorer::Target *target) +{ + if (auto runConfiguration = target->activeRunConfiguration()) { + if (auto multiLanguageAspect = runConfiguration->aspect()) + return multiLanguageAspect; + } + return {}; +} + } // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h index 163552caf0d..20db028ed2a 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h @@ -45,6 +45,10 @@ public: void toMap(QVariantMap &map) const final; void fromMap(const QVariantMap &map) final; + static QmlMultiLanguageAspect *current(); + static QmlMultiLanguageAspect *current(ProjectExplorer::Project *project); + static QmlMultiLanguageAspect *current(ProjectExplorer::Target *target); + public slots: void setLastUsedLanguage(const QString &language); From b1fc2cdbb41a3dd6222619a495ac98292458135d Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 21 Jul 2020 11:53:00 +0200 Subject: [PATCH 25/42] qmlpreview: fix translation for qml files in subdirectories Found path was ignored - so the location was wrong Keeping the kind of ugly in findValidI18nDirectoryAsUrl(const QString &locale) for now - to not change too much in that area. Change-Id: I491df1f928868a8d9afbbb7d25c8102bbe9b51a7 Reviewed-by: Marco Bubke --- .../qmlpreview/qmlpreviewconnectionmanager.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp index 0fcf6a36b89..69a33cfa58c 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp @@ -76,17 +76,22 @@ void QmlPreviewConnectionManager::createClients() QUrl QmlPreviewConnectionManager::findValidI18nDirectoryAsUrl(const QString &locale) { + QTC_ASSERT(!m_lastLoadedUrl.isEmpty(), return {};); + const QString shortLocale = locale.left(locale.indexOf("_")); QString path = m_lastLoadedUrl.path(); + QString foundPath; while (!path.isEmpty()) { path = path.left(qMax(0, path.lastIndexOf("/"))); QUrl url = m_lastLoadedUrl; + auto tryPath = [&](const QString &postfix) { url.setPath(path + "/i18n/qml_" + postfix); bool success = false; - m_projectFileFinder.findFile(url, &success); + foundPath = m_projectFileFinder.findFile(url, &success).first().toString(); + foundPath = foundPath.left(qMax(0, foundPath.lastIndexOf("/i18n"))); return success; }; @@ -101,7 +106,10 @@ QUrl QmlPreviewConnectionManager::findValidI18nDirectoryAsUrl(const QString &loc } QUrl url = m_lastLoadedUrl; - url.setPath(path); + if (foundPath.isEmpty()) + url.setPath(path); + else + url.setPath(foundPath); return url; } From ce926844d0a5b64abca2f4fd172f8c84aa9ccd54 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 21 Jul 2020 13:57:29 +0200 Subject: [PATCH 26/42] qmlpreview: fix init locale issue The init language was never found, because the findValidI18nDirectoryAsUrl() uses the m_lastLoadedUrl to find the translation file path. Change-Id: I6e9b62f3d846795d68ddef5e3a4caf3e3d953c7c Reviewed-by: Marco Bubke --- .../qmlpreviewconnectionmanager.cpp | 26 ++++++++++++++----- .../qmlpreview/qmlpreviewconnectionmanager.h | 1 + src/plugins/qmlpreview/qmlpreviewplugin.cpp | 2 +- .../qmlpreview/qmlpreviewruncontrol.cpp | 7 ++--- src/plugins/qmlpreview/qmlpreviewruncontrol.h | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp index 69a33cfa58c..7d16f1a5420 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp @@ -119,9 +119,14 @@ void QmlPreviewConnectionManager::createDebugTranslationClient() QObject::connect(this, &QmlPreviewConnectionManager::language, m_qmlDebugTranslationClient.data(), [this](const QString &locale) { - // service expects a context URL. - // Search the parent directories of the last loaded URL for i18n files. - m_qmlDebugTranslationClient->changeLanguage(findValidI18nDirectoryAsUrl(locale), locale); + if (m_lastLoadedUrl.isEmpty()) { + // findValidI18nDirectoryAsUrl does not work if we didn't load any file + m_initLocale = locale; + } else { + // service expects a context URL. + // Search the parent directories of the last loaded URL for i18n files. + m_qmlDebugTranslationClient->changeLanguage(findValidI18nDirectoryAsUrl(locale), locale); + } }); QObject::connect(m_qmlDebugTranslationClient.data(), &QmlDebugTranslationClient::debugServiceUnavailable, this, []() { @@ -152,6 +157,10 @@ void QmlPreviewConnectionManager::createPreviewClient() m_lastLoadedUrl = m_targetFileFinder.findUrl(filename); m_qmlPreviewClient->loadUrl(m_lastLoadedUrl); + if (!m_initLocale.isEmpty()) { + emit language(m_initLocale); + m_initLocale.clear(); + } }); QObject::connect(this, &QmlPreviewConnectionManager::rerun, @@ -163,9 +172,14 @@ void QmlPreviewConnectionManager::createPreviewClient() QObject::connect(this, &QmlPreviewConnectionManager::language, m_qmlPreviewClient.data(), [this](const QString &locale) { - // service expects a context URL. - // Search the parent directories of the last loaded URL for i18n files. - m_qmlPreviewClient->language(findValidI18nDirectoryAsUrl(locale), locale); + if (m_lastLoadedUrl.isEmpty()) { + // findValidI18nDirectoryAsUrl does not work if we didn't load any file + m_initLocale = locale; + } else { + // service expects a context URL. + // Search the parent directories of the last loaded URL for i18n files. + m_qmlPreviewClient->language(findValidI18nDirectoryAsUrl(locale), locale); + } }); QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::pathRequested, diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h index 7d87ca79f3f..788df212b55 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h @@ -78,6 +78,7 @@ private: QmlPreviewFileLoader m_fileLoader = nullptr; QmlPreviewFileClassifier m_fileClassifier = nullptr; QmlPreviewFpsHandler m_fpsHandler = nullptr; + QString m_initLocale; }; } // namespace Internal diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index f0643955922..b578be4ac0d 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -154,7 +154,7 @@ public: RunWorkerFactory runWorkerFactory{ [this](RunControl *runControl) { QmlPreviewRunner *runner = new QmlPreviewRunner(runControl, m_fileLoader, m_fileClassifer, - m_fpsHandler, m_zoomFactor, m_locale); + m_fpsHandler, m_zoomFactor); connect(q, &QmlPreviewPlugin::updatePreviews, runner, &QmlPreviewRunner::loadFile); connect(q, &QmlPreviewPlugin::rerunPreviews, diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index c513a580f31..2966cf6e2c8 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -49,8 +49,7 @@ QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl, QmlPreviewFileLoader fileLoader, QmlPreviewFileClassifier fileClassifier, QmlPreviewFpsHandler fpsHandler, - float initialZoom, - const QString &initialLocale) + float initialZoom) : RunWorker(runControl) { setId("QmlPreviewRunner"); @@ -68,11 +67,9 @@ QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl, connect(this, &QmlPreviewRunner::language, &m_connectionManager, &Internal::QmlPreviewConnectionManager::language); connect(&m_connectionManager, &Internal::QmlPreviewConnectionManager::connectionOpened, - this, [this, initialZoom, initialLocale]() { + this, [this, initialZoom]() { if (initialZoom > 0) emit zoom(initialZoom); - if (!initialLocale.isEmpty()) - emit language(initialLocale); emit ready(); }); diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.h b/src/plugins/qmlpreview/qmlpreviewruncontrol.h index 7a25a62c109..7108b8f8f2c 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.h +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.h @@ -39,7 +39,7 @@ class QmlPreviewRunner : public ProjectExplorer::RunWorker public: QmlPreviewRunner(ProjectExplorer::RunControl *runControl, QmlPreviewFileLoader fileLoader, QmlPreviewFileClassifier fileClassifier, QmlPreviewFpsHandler fpsHandler, - float initialZoom, const QString &initialLocale); + float initialZoom); void setServerUrl(const QUrl &serverUrl); QUrl serverUrl() const; From a7c14b54931c3efa4cac255670f9fc9528891e0b Mon Sep 17 00:00:00 2001 From: Lukasz Ornatek Date: Mon, 20 Jul 2020 12:09:18 +0200 Subject: [PATCH 27/42] Support multiline text Use rich text editor widget as dialog for multiline text input Change-Id: I13147e776867032fe1145d6a8a37fcd6976399e4 Task-number: QDS-2229 Reviewed-by: Marco Bubke --- .../HelperWidgets/StandardTextSection.qml | 62 ++++++++++++- .../quick2propertyeditorview.cpp | 2 + .../richtexteditor/richtexteditor.pri | 6 +- .../richtexteditor/richtexteditorproxy.cpp | 87 +++++++++++++++++++ .../richtexteditor/richtexteditorproxy.h | 65 ++++++++++++++ 5 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp create mode 100644 src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml index 6c5e6fde867..777fb0adc41 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml @@ -26,6 +26,8 @@ import QtQuick 2.1 import HelperWidgets 2.0 import QtQuick.Layouts 1.0 +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme Section { anchors.left: parent.left @@ -46,9 +48,29 @@ Section { Label { text: qsTr("Text") } - LineEdit { - backendValue: backendValues.text - Layout.fillWidth: true + + RowLayout { + LineEdit { + backendValue: backendValues.text + Layout.fillWidth: true + } + + StudioControls.AbstractButton { + id: richTextEditorButton + buttonIcon: StudioTheme.Constants.textAlignTop + onClicked: { + richTextDialogLoader.show() + } + } + + RichTextEditor{ + onRejected: { + hideWidget() + } + onAccepted: { + hideWidget() + } + } } Label { @@ -219,4 +241,38 @@ Section { Layout.fillWidth: true } } + + Loader { + id: richTextDialogLoader + + visible: false + active: visible + + function show() { + richTextDialogLoader.visible = true + } + + sourceComponent: Item { + id: richTextEditorParent + + Component.onCompleted: { + richTextEditor.showWidget() + richTextEditor.richText = backendValues.text.value + } + + RichTextEditor { + id: richTextEditor + + onRejected: { + hideWidget() + richTextDialogLoader.visible = false + } + onAccepted: { + backendValues.text.value = richTextEditor.richText + hideWidget() + richTextDialogLoader.visible = false + } + } + } + } } diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index e8c85bc1b6c..7032a26ef76 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -40,6 +40,7 @@ #include "aligndistribute.h" #include "propertyeditorcontextobject.h" #include "tooltip.h" +#include "richtexteditor/richtexteditorproxy.h" namespace QmlDesigner { @@ -69,6 +70,7 @@ void Quick2PropertyEditorView::registerQmlTypes() AlignDistribute::registerDeclarativeType(); Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); + RichTextEditorProxy::registerDeclarativeType(); } } diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri index 68b6dbe0268..b71be2268d5 100644 --- a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri @@ -1,7 +1,9 @@ -HEADERS += $$PWD/richtexteditor.h +HEADERS += $$PWD/richtexteditor.h \ + $$PWD/richtexteditorproxy.h HEADERS += $$PWD/hyperlinkdialog.h -SOURCES += $$PWD/richtexteditor.cpp +SOURCES += $$PWD/richtexteditor.cpp \ + $$PWD/richtexteditorproxy.cpp SOURCES += $$PWD/hyperlinkdialog.cpp FORMS += $$PWD/richtexteditor.ui diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp new file mode 100644 index 00000000000..d628add1ecf --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "richtexteditorproxy.h" + +#include +#include + +#include "richtexteditor.h" + +namespace QmlDesigner { + +RichTextEditorProxy::RichTextEditorProxy(QObject *parent) + : QObject(parent) + , m_dialog(new QDialog{}) + , m_widget(new RichTextEditor{}) +{ + QGridLayout *layout = new QGridLayout{}; + + layout->addWidget(m_widget); + QDialogButtonBox *standardButtons = new QDialogButtonBox{QDialogButtonBox::Ok + | QDialogButtonBox::Cancel}; + + connect(standardButtons, &QDialogButtonBox::accepted, m_dialog, &QDialog::accept); + connect(standardButtons, &QDialogButtonBox::rejected, m_dialog, &QDialog::reject); + + layout->addWidget(standardButtons); + + m_dialog->setLayout(layout); + + connect(m_dialog, &QDialog::accepted, [this]() { emit accepted(); }); + connect(m_dialog, &QDialog::rejected, [this]() { emit rejected(); }); +} + +RichTextEditorProxy::~RichTextEditorProxy() +{ + delete m_dialog; +} + +void RichTextEditorProxy::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "RichTextEditor"); +} + +void RichTextEditorProxy::showWidget() +{ + m_dialog->show(); +} + +void RichTextEditorProxy::hideWidget() +{ + m_dialog->hide(); +} + +QString RichTextEditorProxy::richText() const +{ + return m_widget->richText(); +} + +void RichTextEditorProxy::setRichText(const QString &text) +{ + m_widget->setRichText(text); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h new file mode 100644 index 00000000000..7e62a0e3882 --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +QT_BEGIN_NAMESPACE +class QDialog; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class RichTextEditor; + +class RichTextEditorProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString richText READ richText WRITE setRichText) +public: + explicit RichTextEditorProxy(QObject *parent = nullptr); + ~RichTextEditorProxy(); + + static void registerDeclarativeType(); + + Q_INVOKABLE void showWidget(); + Q_INVOKABLE void hideWidget(); + + QString richText() const; + void setRichText(const QString &text); + +signals: + void accepted(); + void rejected(); + +private: + QDialog *m_dialog; + RichTextEditor *m_widget; +}; + +} // namespace QmlDesigner From f074e205a08bb62fe5201270c5aad6308460ec35 Mon Sep 17 00:00:00 2001 From: Lukasz Ornatek Date: Wed, 22 Jul 2020 16:20:02 +0200 Subject: [PATCH 28/42] Support multiline text, fix text format Explicitly set TextFormat to "RichText" when using rich text editor Task-number: QDS-2229 Change-Id: I8f0110415b8125465779231426e66ecc53eae5fe Reviewed-by: Marco Bubke --- .../imports/HelperWidgets/StandardTextSection.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml index 777fb0adc41..6132cfd2619 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml @@ -269,6 +269,7 @@ Section { } onAccepted: { backendValues.text.value = richTextEditor.richText + backendValues.textFormat.setEnumeration("Text", "RichText") hideWidget() richTextDialogLoader.visible = false } From 03307d8cb4ecc15f4bf1ac5c7424b35ef7b1a7cf Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Wed, 22 Jul 2020 16:40:18 +0200 Subject: [PATCH 29/42] qmldesigner: fix none pre compiled header builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ic8c588596292a466368aad2f8a00a0c02049e1af Reviewed-by: Marco Bubke Reviewed-by: Łukasz Ornatek --- .../components/richtexteditor/richtexteditorproxy.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp index d628add1ecf..ba914724db0 100644 --- a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp @@ -27,6 +27,7 @@ #include #include +#include #include "richtexteditor.h" From bde420f7b7373ad5602ce80383cfe4db6602aa6d Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 20 Jul 2020 20:45:08 +0200 Subject: [PATCH 30/42] qmlproject: rename lastUsedLanguage -> currentLocale removed the connection from plugin to QmlMultiLanguageAspect::setCurrentLocale but calling it directly in QmlPreviewPlugin::setLocale Does not matter which method is called it will change the right thing only once because it is checking if it already have the set locale. Change-Id: I6cb4b115788adc103481dcda45e3214a0cd73401 Reviewed-by: Marco Bubke --- .../instances/nodeinstanceview.cpp | 4 ++-- src/plugins/qmlpreview/qmlpreviewplugin.cpp | 7 +++++- .../qmlmultilanguageaspect.cpp | 23 ++++++++----------- .../qmlmultilanguageaspect.h | 9 ++++---- .../qmlprojectrunconfiguration.cpp | 2 +- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 2f491e8f6be..063a2d1e7d0 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -547,7 +547,7 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, } else if (node.isRootNode() && name == "language@Internal") { const QString languageAsString = value.toString(); if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) - multiLanguageAspect->setLastUsedLanguage(languageAsString); + multiLanguageAspect->setCurrentLocale(languageAsString); nodeInstanceServer()->changeLanguage({languageAsString}); } else if (node.isRootNode() && name == "previewSize@Internal") { nodeInstanceServer()->changePreviewImageSize(value.toSize()); @@ -991,7 +991,7 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() QString lastUsedLanguage; if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) - lastUsedLanguage = multiLanguageAspect->lastUsedLanguage(); + lastUsedLanguage = multiLanguageAspect->currentLocale(); return CreateSceneCommand( instanceContainerList, diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index b578be4ac0d..72927f8cdf3 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -193,7 +193,10 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) action->setEnabled(SessionManager::startupProject() != nullptr); connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action, &QAction::setEnabled); - connect(action, &QAction::triggered, this, []() { + connect(action, &QAction::triggered, this, [this]() { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) + m_locale = multiLanguageAspect->currentLocale(); + ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE); }); menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Internal"), @@ -334,6 +337,8 @@ QString QmlPreviewPlugin::locale() const void QmlPreviewPlugin::setLocale(const QString &locale) { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) + multiLanguageAspect->setCurrentLocale(locale); if (d->m_locale == locale) return; diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 623e51c66e9..1e5e88712eb 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -83,27 +83,24 @@ QmlMultiLanguageAspect::QmlMultiLanguageAspect(ProjectExplorer::Target *target) setDefaultValue(!databaseFilePath().isEmpty()); QVariantMap getDefaultValues; fromMap(getDefaultValues); - - if (auto previewPlugin = getPreviewPlugin()) - connect(previewPlugin, SIGNAL(localeChanged(QString)), this, SLOT(setLastUsedLanguage(QString))); } QmlMultiLanguageAspect::~QmlMultiLanguageAspect() { } -void QmlMultiLanguageAspect::setLastUsedLanguage(const QString &language) +void QmlMultiLanguageAspect::setCurrentLocale(const QString &locale) { + if (m_currentLocale == locale) + return; + m_currentLocale = locale; if (auto previewPlugin = getPreviewPlugin()) - previewPlugin->setProperty("locale", language); - if (m_lastUsedLanguage != language) { - m_lastUsedLanguage = language; - } + previewPlugin->setProperty("locale", locale); } -QString QmlMultiLanguageAspect::lastUsedLanguage() const +QString QmlMultiLanguageAspect::currentLocale() const { - return m_lastUsedLanguage; + return m_currentLocale; } Utils::FilePath QmlMultiLanguageAspect::databaseFilePath() const @@ -116,14 +113,14 @@ Utils::FilePath QmlMultiLanguageAspect::databaseFilePath() const void QmlMultiLanguageAspect::toMap(QVariantMap &map) const { BaseBoolAspect::toMap(map); - if (!m_lastUsedLanguage.isEmpty()) - map.insert(Constants::LAST_USED_LANGUAGE, m_lastUsedLanguage); + if (!m_currentLocale.isEmpty()) + map.insert(Constants::LAST_USED_LANGUAGE, m_currentLocale); } void QmlMultiLanguageAspect::fromMap(const QVariantMap &map) { BaseBoolAspect::fromMap(map); - setLastUsedLanguage(map.value(Constants::LAST_USED_LANGUAGE, "en").toString()); + setCurrentLocale(map.value(Constants::LAST_USED_LANGUAGE, "en").toString()); } QmlMultiLanguageAspect *QmlMultiLanguageAspect::current() diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h index 20db028ed2a..c98c5e2aec4 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h @@ -40,7 +40,8 @@ public: explicit QmlMultiLanguageAspect(ProjectExplorer::Target *target); ~QmlMultiLanguageAspect() override; - QString lastUsedLanguage() const; + QString currentLocale() const; + void setCurrentLocale(const QString &locale); Utils::FilePath databaseFilePath() const; void toMap(QVariantMap &map) const final; void fromMap(const QVariantMap &map) final; @@ -49,13 +50,13 @@ public: static QmlMultiLanguageAspect *current(ProjectExplorer::Project *project); static QmlMultiLanguageAspect *current(ProjectExplorer::Target *target); -public slots: - void setLastUsedLanguage(const QString &language); +signals: + void currentLocaleChanged(const QString &locale); private: ProjectExplorer::Target *m_target = nullptr; mutable Utils::FilePath m_databaseFilePath; - QString m_lastUsedLanguage; + QString m_currentLocale; }; } // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index a0bfa812116..f7bce125a68 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -117,7 +117,7 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id) if (m_multiLanguageAspect && m_multiLanguageAspect->value() && !m_multiLanguageAspect->databaseFilePath().isEmpty()) { env.set("QT_MULTILANGUAGE_DATABASE", m_multiLanguageAspect->databaseFilePath().toString()); - env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->lastUsedLanguage()); + env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->currentLocale()); } else { env.unset("QT_MULTILANGUAGE_DATABASE"); env.unset("QT_MULTILANGUAGE_LANGUAGE"); From 1f702edfd3b62977211e8969b06698ccb17ba2a9 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 23 Jul 2020 08:37:37 +0200 Subject: [PATCH 31/42] qmlpreview: add debugtranslation ui action in the menu will only be shown if a QtStudio Qt is found qtversion.xml QtStudio - still have some issues with multiple file test runs so disable it for now - elideWarning is not tested Change-Id: I68c9f774a980b84cd4eea1595775fd01afa6f3cf Reviewed-by: Marco Bubke --- src/plugins/qmlpreview/CMakeLists.txt | 1 + .../projectfileselectionswidget.cpp | 156 ++++++ .../qmlpreview/projectfileselectionswidget.h | 50 ++ .../qmlpreview/qmldebugtranslationclient.cpp | 8 + .../qmlpreview/qmldebugtranslationclient.h | 1 + .../qmlpreview/qmldebugtranslationwidget.cpp | 454 ++++++++++++++++++ .../qmlpreview/qmldebugtranslationwidget.h | 95 ++++ src/plugins/qmlpreview/qmlpreview.pro | 8 +- src/plugins/qmlpreview/qmlpreview.qbs | 4 + .../qmlpreviewconnectionmanager.cpp | 26 +- .../qmlpreview/qmlpreviewconnectionmanager.h | 1 + src/plugins/qmlpreview/qmlpreviewplugin.cpp | 79 ++- src/plugins/qmlpreview/qmlpreviewplugin.h | 5 + .../qmlpreview/qmlpreviewruncontrol.cpp | 3 + src/plugins/qmlpreview/qmlpreviewruncontrol.h | 2 +- 15 files changed, 872 insertions(+), 21 deletions(-) create mode 100644 src/plugins/qmlpreview/projectfileselectionswidget.cpp create mode 100644 src/plugins/qmlpreview/projectfileselectionswidget.h create mode 100644 src/plugins/qmlpreview/qmldebugtranslationwidget.cpp create mode 100644 src/plugins/qmlpreview/qmldebugtranslationwidget.h diff --git a/src/plugins/qmlpreview/CMakeLists.txt b/src/plugins/qmlpreview/CMakeLists.txt index 345f429e81a..ab5024660cf 100644 --- a/src/plugins/qmlpreview/CMakeLists.txt +++ b/src/plugins/qmlpreview/CMakeLists.txt @@ -9,6 +9,7 @@ add_qtc_plugin(QmlPreview qmlpreviewruncontrol.cpp qmlpreviewruncontrol.h qmldebugtranslationclient.cpp qmldebugtranslationclient.h qmlpreview_global.h + projectfileselectionswidget.cpp projectfileselectionswidget.h ) extend_qtc_plugin(QmlPreview diff --git a/src/plugins/qmlpreview/projectfileselectionswidget.cpp b/src/plugins/qmlpreview/projectfileselectionswidget.cpp new file mode 100644 index 00000000000..ac098886196 --- /dev/null +++ b/src/plugins/qmlpreview/projectfileselectionswidget.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "projectfileselectionswidget.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace QmlPreview { + +class ProjectFileItem : public Utils::TreeItem +{ +public: + ProjectFileItem() = default; + ProjectFileItem(const Utils::FilePath &f, bool d) + : filePath(f) + , disabled(d) + {} + + Qt::ItemFlags flags(int) const override + { + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; + } + + QVariant data(int , int role) const override + { + if (role == Qt::DisplayRole) + return filePath.toUserOutput(); + if (role == Qt::CheckStateRole) { + if (disabled) + return Qt::Unchecked; + else + return Qt::Checked; + } + return QVariant(); + } + + bool setData(int , const QVariant &data, int role) override + { + if (role != Qt::CheckStateRole) + return false; + disabled = (data == Qt::Unchecked); + return true; + } + + Utils::FilePath filePath; + bool disabled = false; +}; + + +ProjectFileSelectionsWidget::ProjectFileSelectionsWidget(const QString &projectSettingsKey, ProjectExplorer::FileType fileType, QWidget *parent) + : QWidget(parent) + , m_projectSettingsKey(projectSettingsKey) + , m_fileType(fileType) +{ + auto model = new Utils::TreeModel(this); + model->setHeader({tr("Files to test:")}); + auto updateCheckedFiles = [this, model] () { + m_checkedFiles.clear(); + QStringList uncheckedFiles; + model->forAllItems([&, this](ProjectFileItem *item) { + if (item->disabled) + uncheckedFiles.append(item->filePath.toString()); + else + m_checkedFiles.append(item->filePath); + }); + if (auto project = ProjectExplorer::SessionManager::startupProject()) + project->setNamedSettings(m_projectSettingsKey, uncheckedFiles); + emit selectionChanged(m_checkedFiles); + }; + + connect(model, &QAbstractItemModel::dataChanged, updateCheckedFiles); + + auto view = new QTreeView(this); + view->setMinimumSize(QSize(100, 100)); + view->setTextElideMode(Qt::ElideMiddle); + view->setWordWrap(false); + view->setUniformRowHeights(true); + view->setModel(model); + + const auto viewLayout = new QHBoxLayout; + viewLayout->addWidget(view); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(viewLayout); + + auto initModel = [this, model, updateCheckedFiles] (ProjectExplorer::Project *project) { + auto refreshModel = [this, model, updateCheckedFiles] () { + model->clear(); + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + const auto settingsDisabledFiles = project->namedSettings(m_projectSettingsKey).toStringList(); + + if (auto rootProjectNode = project->rootProjectNode()) { + rootProjectNode->forEachNode([this, settingsDisabledFiles, model](ProjectExplorer::FileNode *fileNode) { + if (fileNode->fileType() == m_fileType) { + bool isDisabled = settingsDisabledFiles.contains(fileNode->filePath().toString()); + model->rootItem()->appendChild(new ProjectFileItem(fileNode->filePath(), isDisabled)); + } + }); + } + updateCheckedFiles(); + } + }; + // deploymentDataChanged is only triggered if the active project changed, so it is not a + // problem that maybe many different targets are connected to refreshModel + this->connect(project->activeTarget(), &ProjectExplorer::Target::deploymentDataChanged, + model, refreshModel, Qt::UniqueConnection); + refreshModel(); + }; + + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + initModel(project); + } + + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::startupProjectChanged, + initModel); +} + +Utils::FilePaths ProjectFileSelectionsWidget::checkedFiles() +{ + return m_checkedFiles; +} + +} // QmlPreview diff --git a/src/plugins/qmlpreview/projectfileselectionswidget.h b/src/plugins/qmlpreview/projectfileselectionswidget.h new file mode 100644 index 00000000000..4bacb41d49b --- /dev/null +++ b/src/plugins/qmlpreview/projectfileselectionswidget.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once +#include +#include + +#include + +namespace QmlPreview { + +class ProjectFileSelectionsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ProjectFileSelectionsWidget(const QString &projectSettingsKey, ProjectExplorer::FileType fileType, QWidget *parent = nullptr); + Utils::FilePaths checkedFiles(); +signals: + void selectionChanged(const Utils::FilePaths &selectedFiles); +private: + const QString m_projectSettingsKey; + ProjectExplorer::FileType m_fileType; + + Utils::FilePaths m_checkedFiles; +}; + +} // QmlPreview diff --git a/src/plugins/qmlpreview/qmldebugtranslationclient.cpp b/src/plugins/qmlpreview/qmldebugtranslationclient.cpp index 47474546ee1..4d11f900bce 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationclient.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationclient.cpp @@ -57,6 +57,14 @@ void QmlDebugTranslationClient::changeElidedTextWarningString(const QString &war sendMessage(packet.data()); } +void QmlDebugTranslationClient::changeElideWarning(bool elideWarning) +{ + if (elideWarning) + enableElidedTextWarning(); + else + disableElidedTextWarning(); +} + void QmlDebugTranslationClient::setDebugTranslationServiceLogFile(const QString &logFilePath) { QmlDebug::QPacket packet(dataStreamVersion()); diff --git a/src/plugins/qmlpreview/qmldebugtranslationclient.h b/src/plugins/qmlpreview/qmldebugtranslationclient.h index c27726a7eb2..2b69e99b0a6 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationclient.h +++ b/src/plugins/qmlpreview/qmldebugtranslationclient.h @@ -50,6 +50,7 @@ public: void changeLanguage(const QUrl &url, const QString &locale); void changeWarningColor(const QColor &warningColor); void changeElidedTextWarningString(const QString &warningString); //is QByteArray better here? + void changeElideWarning(bool elideWarning); void setDebugTranslationServiceLogFile(const QString &logFilePath); void enableElidedTextWarning(); void disableElidedTextWarning(); diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp new file mode 100644 index 00000000000..efe6605de70 --- /dev/null +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -0,0 +1,454 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qmldebugtranslationwidget.h" +#include "qmlpreviewruncontrol.h" +#include "qmlpreviewplugin.h" +#include "projectfileselectionswidget.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +QObject *getPreviewPlugin() +{ + auto pluginIt = std::find_if(ExtensionSystem::PluginManager::plugins().begin(), + ExtensionSystem::PluginManager::plugins().end(), + [](const ExtensionSystem::PluginSpec *p) { + return p->name() == "QmlPreview"; + }); + + if (pluginIt != ExtensionSystem::PluginManager::plugins().constEnd()) + return (*pluginIt)->plugin(); + + return nullptr; +} + +} + +namespace QmlPreview { + +QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent) + : QWidget(parent) +{ + auto mainLayout = new QVBoxLayout(this); + + auto buttonGroup = new QButtonGroup(this); + // it gets the text from updateCurrentEditor method + m_singleFileButton = new QRadioButton(); + m_singleFileButton->setChecked(true); + buttonGroup->addButton(m_singleFileButton); + + const QString projectSettingsKey = "QmlPreview.DisabledDebugTranslationFiles"; + const ProjectExplorer::FileType filterFileType = ProjectExplorer::FileType::QML; + auto checkableProjectFileView = new ProjectFileSelectionsWidget(projectSettingsKey, filterFileType); + checkableProjectFileView->setVisible(false); + connect(checkableProjectFileView, &ProjectFileSelectionsWidget::selectionChanged, this, &QmlDebugTranslationWidget::setFiles); + m_multipleFileButton = new QRadioButton(tr("multiple files")); + // TODO: fix multiple files issues, because it have some issues disable it for now + m_multipleFileButton->setDisabled(true); + buttonGroup->addButton(m_multipleFileButton); + connect(m_multipleFileButton, &QAbstractButton::toggled, [checkableProjectFileView, this](bool checked) { + checkableProjectFileView->setVisible(checked); + setFiles(checkableProjectFileView->checkedFiles()); + }); + + mainLayout->addWidget(m_singleFileButton); + mainLayout->addWidget(m_multipleFileButton); + mainLayout->addWidget(checkableProjectFileView); + + // language checkboxes are add in updateAvailableTranslations method + m_selectLanguageLayout = new QHBoxLayout; + mainLayout->addLayout(m_selectLanguageLayout); + + auto elideWarningCheckBox = new QCheckBox(tr("Enable elide warning")); + layout()->addWidget(elideWarningCheckBox); + connect(elideWarningCheckBox, &QCheckBox::stateChanged, [this] (int state) { + m_elideWarning = (state == Qt::Checked); + }); + + auto controlLayout = new QHBoxLayout; + mainLayout->addLayout(controlLayout); + + auto showLogButton = new QToolButton; + showLogButton->setText(tr("Show log")); + showLogButton->setCheckable(true); + controlLayout->addWidget(showLogButton); + + // TODO: do we still need this buttons? +// auto pauseButton = new QToolButton; +// pauseButton->setText(tr("Pause")); +// pauseButton->setCheckable(true); +// controlLayout->addWidget(pauseButton); + +// auto onTheFlyButton = new QToolButton; +// onTheFlyButton->setText(tr("On the fly")); +// controlLayout->addWidget(onTheFlyButton); + + m_runTestButton = new QPushButton(); + m_runTestButton->setCheckable(true); + m_runTestButton->setText(runButtonText()); + connect(m_runTestButton, &QPushButton::toggled, [this](bool checked) { + m_runTestButton->setText(runButtonText(checked)); + }); + + connect(m_runTestButton, &QPushButton::clicked, [this](bool checked) { + if (checked) + runTest(); + else { + if (m_currentRunControl) + m_currentRunControl->initiateStop(); + // TODO: what happens if we already have a preview running? +// QmlPreviewPlugin::stopAllRunControls(); +// qWarning() << "not implemented"; // TODO: stop still running tests + } + }); + controlLayout->addWidget(m_runTestButton); + + m_runOutputWindow = new Core::OutputWindow(Core::Context("QmlPreview.DebugTranslation"), + "QmlPreview/OutputWindow/Zoom"); + + m_runOutputWindow->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_runOutputWindow->setReadOnly(true); + m_runOutputWindow->setVisible(false); + mainLayout->addWidget(m_runOutputWindow); + + QSpacerItem *endSpacerItem = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding); + mainLayout->addItem(endSpacerItem); + + connect(showLogButton, &QToolButton::toggled, m_runOutputWindow, [this, mainLayout, endSpacerItem](bool checked) { + m_runOutputWindow->setVisible(checked); + if (m_runOutputWindow->isVisible()) + mainLayout->takeAt(mainLayout->count() - 1); + else + mainLayout->addItem(endSpacerItem); + }); + + auto loadLogButton = new QToolButton; + loadLogButton->setText(tr("Load")); + controlLayout->addWidget(loadLogButton); + connect(loadLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::loadLogFile); + + auto saveLogButton = new QToolButton; + saveLogButton->setText(tr("Save")); + controlLayout->addWidget(saveLogButton); + connect(saveLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::saveLogToFile); + + auto clearButton = new QToolButton; + clearButton->setText(tr("Clear")); + controlLayout->addWidget(clearButton); + connect(clearButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::clear); + + Core::EditorManager *editorManager = Core::EditorManager::instance(); + connect(editorManager, &Core::EditorManager::currentEditorChanged, this, &QmlDebugTranslationWidget::updateCurrentEditor); + updateCurrentEditor(Core::EditorManager::currentEditor()); + + connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, + this, &QmlDebugTranslationWidget::updateCurrentTranslations); + + updateStartupProjectTranslations(); + + ProjectExplorer::TaskHub::addCategory("QmlPreview.Translation", tr("Translation issues")); +} + +QmlDebugTranslationWidget::~QmlDebugTranslationWidget() +{ + +} + +void QmlDebugTranslationWidget::updateCurrentEditor(const Core::IEditor *editor) +{ + if (editor && editor->document()) + m_currentFilePath = editor->document()->filePath(); + else + m_currentFilePath.clear(); + m_singleFileButton->setText(singleFileButtonText(m_currentFilePath.toString())); + +} + +void QmlDebugTranslationWidget::updateStartupProjectTranslations() +{ + updateCurrentTranslations(ProjectExplorer::SessionManager::startupProject()); +} + +void QmlDebugTranslationWidget::updateCurrentTranslations(ProjectExplorer::Project *project) +{ + for (int i = m_selectLanguageLayout->count()-1; i >= 0; --i) { + auto layoutItem = m_selectLanguageLayout->takeAt(i); + delete layoutItem->widget(); + delete layoutItem; + } + if (!project) + return; + + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(project)) { + connect(multiLanguageAspect, &QmlProjectManager::QmlMultiLanguageAspect::changed, + this, &QmlDebugTranslationWidget::updateStartupProjectTranslations, + Qt::UniqueConnection); + if (multiLanguageAspect->value()) { + m_selectLanguageLayout->addWidget(new QLabel( + tr("Current language is \'%1\' can be changed in the 'Translation' tab.") + .arg(multiLanguageAspect->currentLocale()))); + m_testLanguages.clear(); + } else { + m_selectLanguageLayout->addWidget(new QLabel(tr("Select which language should be tested:"))); + QString errorMessage; + for (auto language : project->availableQmlPreviewTranslations(&errorMessage)) { + auto languageCheckBox = new QCheckBox(language); + m_selectLanguageLayout->addWidget(languageCheckBox); + connect(languageCheckBox, &QCheckBox::stateChanged, [this, language] (int state) { + if (state == Qt::Checked) + m_testLanguages.append(language); + else + m_testLanguages.removeAll(language); + }); + languageCheckBox->setChecked(true); + } + m_selectLanguageLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); + } + } +} + +void QmlDebugTranslationWidget::setFiles(const Utils::FilePaths &filePathes) +{ + m_selectedFilePaths = filePathes; +} + +void QmlDebugTranslationWidget::runTest() +{ + m_runOutputWindow->grayOutOldContent(); + + auto runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); + QTC_ASSERT(runControl, qWarning("Can not create a QmlPreviewRunner"); return;); + auto previewPlugin = qobject_cast(getPreviewPlugin()); + + connect(runControl, &ProjectExplorer::RunControl::started, [this, runControl, previewPlugin]() { + //Q_ASSERT(m_currentRunControl == nullptr); //TODO: who deletes the runcontrol + m_currentRunControl = runControl; + m_runOutputWindow->setFormatter(runControl->outputFormatter()); + int timerCounter = 1; + auto testLanguages = [this, previewPlugin](int timerCounter, const QString &previewedFile = QString()) { + qDebug() << "testLanguages" << previewedFile; + for (auto language : m_testLanguages) { + QTimer::singleShot(timerCounter * 1000, previewPlugin, [this, previewPlugin, language, previewedFile]() { + if (m_currentRunControl && m_currentRunControl->isRunning()) { + if (!previewedFile.isEmpty()) + previewPlugin->setPreviewedFile(previewedFile); + previewPlugin->setLocale(language); + } + }); + } + }; + if (m_multipleFileButton->isChecked()) { + for (auto filePath : m_selectedFilePaths) { + testLanguages(timerCounter++, filePath.toString()); + } + } else { + testLanguages(timerCounter); + } + + }); + connect(runControl, &ProjectExplorer::RunControl::stopped, [this]() { + m_runTestButton->setChecked(false); + //delete m_currentRunControl; // who deletes the runcontrol? + m_currentRunControl = nullptr; + if (auto previewPlugin = qobject_cast(getPreviewPlugin())) + previewPlugin->setLocale(m_lastUsedLanguageBeforeTest); + }); + + connect(runControl, &ProjectExplorer::RunControl::appendMessage, + this, &QmlDebugTranslationWidget::appendMessage); + + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + if (auto target = project->activeTarget()) { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(target)) + m_lastUsedLanguageBeforeTest = multiLanguageAspect->currentLocale(); + if (auto runConfiguration = target->activeRunConfiguration()) { + runControl->setRunConfiguration(runConfiguration); + if (runControl->createMainWorker()) { + previewPlugin->setLocale(QString()); + runControl->initiateStart(); + } + } + } + } +} + +void QmlDebugTranslationWidget::clear() +{ + m_runOutputWindow->clear(); + ProjectExplorer::TaskHub::clearTasks("QmlPreview.Translation"); +} + +QString QmlDebugTranslationWidget::currentDir() const +{ + return m_lastDir.isEmpty() ? + ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; +} + +void QmlDebugTranslationWidget::setCurrentDir(const QString &path) +{ + m_lastDir = path; + const QString currentDir = m_lastDir.isEmpty() ? + ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; +} + +void QmlDebugTranslationWidget::loadLogFile() +{ + const auto fileName = QFileDialog::getOpenFileName(this, QStringLiteral("Open File"), currentDir()); + if (!fileName.isEmpty()) { + setCurrentDir(QFileInfo(fileName).absolutePath()); + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + clear(); + while (!f.atEnd()) + appendMessage(QString::fromUtf8(f.readLine()), Utils::DebugFormat); + } else { + // TODO: maybe add this message to log and tasks + qWarning() << "Failed to open" << fileName << ":" << f.errorString(); + } + } +} + +void QmlDebugTranslationWidget::saveLogToFile() +{ + const QString fileName = QFileDialog::getSaveFileName( + this, tr("Choose file to save logged issues."), currentDir()); + if (!fileName.isEmpty()) { + setCurrentDir(QFileInfo(fileName).absolutePath()); + QFile f(fileName); + if (f.open(QFile::WriteOnly | QFile::Text)) + f.write(m_runOutputWindow->toPlainText().toUtf8()); + } +} + +void QmlDebugTranslationWidget::appendMessage(const QString &message, Utils::OutputFormat format) +{ + const auto newLine = QRegularExpression("[\r\n]"); + const auto messages = message.split(newLine, QString::SkipEmptyParts); + + if (messages.count() > 1) { + for (auto m : messages) + appendMessage(m + "\n", format); + return; + } + const QString serviceSeperator = ": QQmlDebugTranslationService: "; + if (!message.contains(serviceSeperator) || message.contains("DebugTranslation service - language changed")) + return; + QString locationString = message; + locationString = locationString.split(serviceSeperator).first(); + static const QRegularExpression qmlLineColumnLink("^(" QT_QML_URL_REGEXP ")" // url + ":(\\d+)" // line + ":(\\d+)$"); // column + const QRegularExpressionMatch qmlLineColumnMatch = qmlLineColumnLink.match(locationString); + + auto fileLine = -1; + QUrl fileUrl; + if (qmlLineColumnMatch.hasMatch()) { + fileUrl = QUrl(qmlLineColumnMatch.captured(1)); + fileLine = qmlLineColumnMatch.captured(2).toInt(); + } + + if (!m_runOutputWindow->formatter()) { + auto defaultFormatter = new Utils::OutputFormatter(); + defaultFormatter->setParent(this); + m_runOutputWindow->setFormatter(defaultFormatter); + } + m_runOutputWindow->appendMessage(message, format); + + + auto type = ProjectExplorer::Task::TaskType::Warning; + auto description = message.split(serviceSeperator).at(1); + auto filePath = Utils::FilePath::fromString(fileUrl.toLocalFile()); + auto category = "QmlPreview.Translation"; + auto icon = Utils::Icons::WARNING.icon(); + + ProjectExplorer::TaskHub::addTask(ProjectExplorer::Task(type, + description, + filePath, + fileLine, + category, + icon, + ProjectExplorer::Task::NoOptions)); +} + +QString QmlDebugTranslationWidget::singleFileButtonText(const QString &filePath) +{ + auto buttonText = tr("current file: %1"); + if (filePath.isEmpty()) + return buttonText.arg(tr("empty")); + return buttonText.arg(filePath); +} + +QString QmlDebugTranslationWidget::runButtonText(bool isRunning) +{ + if (isRunning) { + return tr("Stop"); + } + return tr("Run language tests"); +} + +} // namespace QmlPreview diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.h b/src/plugins/qmlpreview/qmldebugtranslationwidget.h new file mode 100644 index 00000000000..ef9deb14e91 --- /dev/null +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "qmlpreview_global.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QRadioButton; +class QPushButton; +class QHBoxLayout; +QT_END_NAMESPACE + +namespace Core { +class IEditor; +class OutputWindow; +} +namespace ProjectExplorer { +class Project; +class RunControl; +} + +namespace QmlPreview { + +class QMLPREVIEW_EXPORT QmlDebugTranslationWidget : public QWidget +{ + Q_OBJECT +public: + explicit QmlDebugTranslationWidget(QWidget *parent = nullptr); + ~QmlDebugTranslationWidget() override; + + void setCurrentFile(const Utils::FilePath &filepath); + void setFiles(const Utils::FilePaths &filePathes); + void updateStartupProjectTranslations(); +private: + void updateCurrentEditor(const Core::IEditor *editor); + void updateCurrentTranslations(ProjectExplorer::Project *project); + void runTest(); + void appendMessage(const QString &message, Utils::OutputFormat format); + void clear(); + void loadLogFile(); + void saveLogToFile(); + QString currentDir() const; + void setCurrentDir(const QString &path); + + QString singleFileButtonText(const QString &filePath); + QString runButtonText(bool isRunning = false); + + QStringList m_testLanguages; + QString m_lastUsedLanguageBeforeTest; + bool m_elideWarning = false; + + Core::OutputWindow *m_runOutputWindow = nullptr; + + QRadioButton *m_singleFileButton = nullptr; + QRadioButton *m_multipleFileButton = nullptr; + QPushButton *m_runTestButton = nullptr; + + Utils::FilePath m_currentFilePath; + Utils::FilePaths m_selectedFilePaths; + ProjectExplorer::RunControl *m_currentRunControl = nullptr; + + QString m_lastDir; + + QHBoxLayout *m_selectLanguageLayout; +}; + +} // namespace QmlPreview diff --git a/src/plugins/qmlpreview/qmlpreview.pro b/src/plugins/qmlpreview/qmlpreview.pro index cfc571521f5..ea53ac2f4ea 100644 --- a/src/plugins/qmlpreview/qmlpreview.pro +++ b/src/plugins/qmlpreview/qmlpreview.pro @@ -10,19 +10,23 @@ include(tests/tests.pri) HEADERS += \ qmlpreview_global.h \ qmldebugtranslationclient.h \ + qmldebugtranslationwidget.h \ qmlpreviewclient.h \ qmlpreviewplugin.h \ qmlpreviewruncontrol.h \ qmlpreviewconnectionmanager.h \ - qmlpreviewfileontargetfinder.h + qmlpreviewfileontargetfinder.h \ + projectfileselectionswidget.h SOURCES += \ qmlpreviewplugin.cpp \ qmldebugtranslationclient.cpp \ + qmldebugtranslationwidget.cpp \ qmlpreviewclient.cpp \ qmlpreviewruncontrol.cpp \ qmlpreviewconnectionmanager.cpp \ - qmlpreviewfileontargetfinder.cpp + qmlpreviewfileontargetfinder.cpp \ + projectfileselectionswidget.cpp OTHER_FILES += \ QmlPreview.json.in diff --git a/src/plugins/qmlpreview/qmlpreview.qbs b/src/plugins/qmlpreview/qmlpreview.qbs index 08e64725789..67734879620 100644 --- a/src/plugins/qmlpreview/qmlpreview.qbs +++ b/src/plugins/qmlpreview/qmlpreview.qbs @@ -26,6 +26,8 @@ QtcPlugin { "qmlpreviewclient.h", "qmldebugtranslationclient.cpp", "qmldebugtranslationclient.h", + "qmldebugtranslationwidget.cpp", + "qmldebugtranslationwidget.h", "qmlpreviewconnectionmanager.cpp", "qmlpreviewconnectionmanager.h", "qmlpreviewfileontargetfinder.cpp", @@ -35,6 +37,8 @@ QtcPlugin { "qmlpreviewplugin.h", "qmlpreviewruncontrol.cpp", "qmlpreviewruncontrol.h", + "projectfileselectionswidget.cpp", + "projectfileselectionswidget.h" ] } diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp index 7d16f1a5420..7da4bdea13f 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp @@ -116,7 +116,7 @@ QUrl QmlPreviewConnectionManager::findValidI18nDirectoryAsUrl(const QString &loc void QmlPreviewConnectionManager::createDebugTranslationClient() { m_qmlDebugTranslationClient = new QmlDebugTranslationClient(connection()); - QObject::connect(this, &QmlPreviewConnectionManager::language, + connect(this, &QmlPreviewConnectionManager::language, m_qmlDebugTranslationClient.data(), [this](const QString &locale) { if (m_lastLoadedUrl.isEmpty()) { @@ -128,7 +128,10 @@ void QmlPreviewConnectionManager::createDebugTranslationClient() m_qmlDebugTranslationClient->changeLanguage(findValidI18nDirectoryAsUrl(locale), locale); } }); - QObject::connect(m_qmlDebugTranslationClient.data(), &QmlDebugTranslationClient::debugServiceUnavailable, + connect(this, &QmlPreviewConnectionManager::changeElideWarning, + m_qmlDebugTranslationClient, &QmlDebugTranslationClient::changeElideWarning); + + connect(m_qmlDebugTranslationClient.data(), &QmlDebugTranslationClient::debugServiceUnavailable, this, []() { QMessageBox::warning(Core::ICore::dialogParent(), "Error connect to QML DebugTranslation service", "QML DebugTranslation feature is not available for this version of Qt."); @@ -139,8 +142,7 @@ void QmlPreviewConnectionManager::createPreviewClient() { m_qmlPreviewClient = new QmlPreviewClient(connection()); - QObject::connect( - this, &QmlPreviewConnectionManager::loadFile, m_qmlPreviewClient.data(), + connect(this, &QmlPreviewConnectionManager::loadFile, m_qmlPreviewClient.data(), [this](const QString &filename, const QString &changedFile, const QByteArray &contents) { if (!m_fileClassifier(changedFile)) { @@ -163,13 +165,13 @@ void QmlPreviewConnectionManager::createPreviewClient() } }); - QObject::connect(this, &QmlPreviewConnectionManager::rerun, + connect(this, &QmlPreviewConnectionManager::rerun, m_qmlPreviewClient.data(), &QmlPreviewClient::rerun); - QObject::connect(this, &QmlPreviewConnectionManager::zoom, + connect(this, &QmlPreviewConnectionManager::zoom, m_qmlPreviewClient.data(), &QmlPreviewClient::zoom); - QObject::connect(this, &QmlPreviewConnectionManager::language, + connect(this, &QmlPreviewConnectionManager::language, m_qmlPreviewClient.data(), [this](const QString &locale) { if (m_lastLoadedUrl.isEmpty()) { @@ -182,7 +184,7 @@ void QmlPreviewConnectionManager::createPreviewClient() } }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::pathRequested, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::pathRequested, this, [this](const QString &path) { const bool found = m_projectFileFinder.findFileOrDirectory( path, [&](const QString &filename, int confidence) { @@ -212,13 +214,13 @@ void QmlPreviewConnectionManager::createPreviewClient() m_qmlPreviewClient->announceError(path); }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::errorReported, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::errorReported, this, [](const QString &error) { Core::MessageManager::write("Error loading QML Live Preview:"); Core::MessageManager::write(error); }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::fpsReported, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::fpsReported, this, [this](const QmlPreviewClient::FpsInfo &frames) { if (m_fpsHandler) { quint16 stats[] = { @@ -229,13 +231,13 @@ void QmlPreviewConnectionManager::createPreviewClient() } }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::debugServiceUnavailable, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::debugServiceUnavailable, this, []() { QMessageBox::warning(Core::ICore::dialogParent(), "Error loading QML Live Preview", "QML Live Preview is not available for this version of Qt."); }, Qt::QueuedConnection); // Queue it, so that it interfere with the connection timer - QObject::connect(&m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, + connect(&m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, m_qmlPreviewClient.data(), [this](const QString &changedFile) { if (!m_fileLoader || !m_lastLoadedUrl.isValid()) return; diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h index 788df212b55..7693eda6950 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h @@ -57,6 +57,7 @@ signals: void loadFile(const QString &filename, const QString &changedFile, const QByteArray &contents); void zoom(float zoomFactor); void language(const QString &locale); + void changeElideWarning(bool elideWarning); void rerun(); void restart(); diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index 72927f8cdf3..ffae9a1815a 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -26,11 +26,14 @@ #include "qmlpreviewplugin.h" #include "qmlpreviewruncontrol.h" +#include "qmldebugtranslationwidget.h" + #ifdef WITH_TESTS #include "tests/qmlpreviewclient_test.h" #include "tests/qmlpreviewplugin_test.h" #endif +#include #include #include #include @@ -54,6 +57,11 @@ #include #include + +#include +#include +#include + #include using namespace ProjectExplorer; @@ -143,6 +151,8 @@ public: float m_zoomFactor = -1.0; QmlPreview::QmlPreviewFpsHandler m_fpsHandler = nullptr; QString m_locale; + bool elideWarning = false; + QPointer m_qmlDebugTranslationWidget; RunWorkerFactory localRunWorkerFactory{ RunWorkerFactory::make(), @@ -165,6 +175,8 @@ public: runner, &QmlPreviewRunner::zoom); connect(q, &QmlPreviewPlugin::localeChanged, runner, &QmlPreviewRunner::language); + connect(q, &QmlPreviewPlugin::elideWarningChanged, + runner, &QmlPreviewRunner::changeElideWarning); connect(runner, &RunWorker::started, this, [this, runControl] { addPreview(runControl); @@ -199,10 +211,54 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE); }); - menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Internal"), - Constants::G_BUILD_RUN); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.RunPreview"), + Constants::G_BUILD_RUN); + + action = new QAction(QmlPreviewPlugin::tr("Test translations"), this); + action->setToolTip(QLatin1String("Runs the preview with all available translations and collects all issues.")); + action->setEnabled(SessionManager::startupProject() != nullptr); + connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action, + &QAction::setEnabled); + connect(action, &QAction::triggered, this, [this]() { + if (SessionManager::startupProject()) { + // Deletion for this widget is taken care of in aboutToShutdown() and registerWindow() + m_qmlDebugTranslationWidget = new QmlDebugTranslationWidget(); + Core::ICore::registerWindow(m_qmlDebugTranslationWidget, Core::Context("Core.DebugTranslation")); + m_qmlDebugTranslationWidget->show(); + } + }); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.TestTranslations"), + Constants::G_BUILD_RUN); + auto updateTestTranslationAction = [action]() { + bool showTestTranslationAction = false; + bool enableTestTranslationAction = false; + QtSupport::BaseQtVersion *activeQt{}; + if (auto project = SessionManager::startupProject()) { + if (auto target = project->activeTarget()) { + if (auto activeKit = target->kit()) + activeQt = QtSupport::QtKitAspect::qtVersion(activeKit); + } + } + for (auto qtVersion : QtSupport::QtVersionManager::versions()) { + if (qtVersion->features().contains("QtStudio")) { + showTestTranslationAction = true; + if (qtVersion == activeQt) + enableTestTranslationAction = true; + } + } + action->setVisible(showTestTranslationAction); + action->setEnabled(enableTestTranslationAction); + }; + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::startupProjectChanged, + updateTestTranslationAction); + + connect(QtSupport::QtVersionManager::instance(), + &QtSupport::QtVersionManager::qtVersionsChanged, + updateTestTranslationAction); - Core::Context projectTreeContext(Constants::C_PROJECT_TREE); menu = Core::ActionManager::actionContainer(Constants::M_FILECONTEXT); action = new QAction(QmlPreviewPlugin::tr("Preview File"), this); action->setEnabled(false); @@ -211,9 +267,9 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) action->setEnabled(!previews.isEmpty()); }); connect(action, &QAction::triggered, this, &QmlPreviewPluginPrivate::previewCurrentFile); - menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Preview", - projectTreeContext), - Constants::G_FILE_OTHER); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.PreviewFile", Core::Context(Constants::C_PROJECT_TREE)), + Constants::G_FILE_OTHER); action->setVisible(false); connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged, action, [action]() { const Node *node = ProjectTree::currentNode(); @@ -251,6 +307,7 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlPreviewPlugin::aboutToShutdown() { d->m_parseThread.quit(); d->m_parseThread.wait(); + delete d->m_qmlDebugTranslationWidget; return SynchronousShutdown; } @@ -346,6 +403,16 @@ void QmlPreviewPlugin::setLocale(const QString &locale) emit localeChanged(d->m_locale); } +bool QmlPreviewPlugin::elideWarning() const +{ + return d->elideWarning; +} + +void QmlPreviewPlugin::changeElideWarning(bool elideWarning) +{ + d->elideWarning = elideWarning; +} + void QmlPreviewPlugin::setFileLoader(QmlPreviewFileLoader fileLoader) { if (d->m_fileLoader == fileLoader) diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.h b/src/plugins/qmlpreview/qmlpreviewplugin.h index df0a59f2b80..13146105ac9 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.h +++ b/src/plugins/qmlpreview/qmlpreviewplugin.h @@ -59,6 +59,7 @@ class QmlPreviewPlugin : public ExtensionSystem::IPlugin WRITE setFpsHandler NOTIFY fpsHandlerChanged) Q_PROPERTY(float zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged) + Q_PROPERTY(bool elideWarning READ elideWarning WRITE changeElideWarning NOTIFY elideWarningChanged) public: ~QmlPreviewPlugin() override; @@ -86,6 +87,9 @@ public: QString locale() const; void setLocale(const QString &locale); + bool elideWarning() const; + void changeElideWarning(bool elideWarning); + signals: void checkDocument(const QString &name, const QByteArray &contents, QmlJS::Dialect::Enum dialect); @@ -100,6 +104,7 @@ signals: void zoomFactorChanged(float zoomFactor); void localeChanged(const QString &locale); + void elideWarningChanged(bool elideWarning); private: class QmlPreviewPluginPrivate *d = nullptr; diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index 2966cf6e2c8..518e33794fb 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -66,6 +66,9 @@ QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl, &m_connectionManager, &Internal::QmlPreviewConnectionManager::zoom); connect(this, &QmlPreviewRunner::language, &m_connectionManager, &Internal::QmlPreviewConnectionManager::language); + connect(this, &QmlPreviewRunner::changeElideWarning, + &m_connectionManager, &Internal::QmlPreviewConnectionManager::changeElideWarning); + connect(&m_connectionManager, &Internal::QmlPreviewConnectionManager::connectionOpened, this, [this, initialZoom]() { if (initialZoom > 0) diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.h b/src/plugins/qmlpreview/qmlpreviewruncontrol.h index 7108b8f8f2c..38740b31a39 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.h +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.h @@ -51,7 +51,7 @@ signals: void zoom(float zoomFactor); void rerun(); void ready(); - + void changeElideWarning(bool elideWarning); private: void start() override; void stop() override; From 955f124ffd3864a4403d721344e49609c49fdc12 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 23 Jul 2020 13:42:49 +0200 Subject: [PATCH 32/42] fix build on older compilers Change-Id: Id5ccad524c7609eb789d7fa805ace0719a807e65 Reviewed-by: Tim Jenssen --- src/plugins/qmlpreview/qmldebugtranslationwidget.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp index efe6605de70..071faf07058 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -290,11 +290,13 @@ void QmlDebugTranslationWidget::runTest() m_currentRunControl = runControl; m_runOutputWindow->setFormatter(runControl->outputFormatter()); int timerCounter = 1; - auto testLanguages = [this, previewPlugin](int timerCounter, const QString &previewedFile = QString()) { + const auto testLanguageList = m_testLanguages; + + auto testLanguages = [previewPlugin, runControl, testLanguageList](int timerCounter, const QString &previewedFile = QString()) { qDebug() << "testLanguages" << previewedFile; - for (auto language : m_testLanguages) { - QTimer::singleShot(timerCounter * 1000, previewPlugin, [this, previewPlugin, language, previewedFile]() { - if (m_currentRunControl && m_currentRunControl->isRunning()) { + for (auto language : testLanguageList) { + QTimer::singleShot(timerCounter * 1000, previewPlugin, [previewPlugin, runControl, language, previewedFile]() { + if (runControl && runControl->isRunning()) { if (!previewedFile.isEmpty()) previewPlugin->setPreviewedFile(previewedFile); previewPlugin->setLocale(language); From 49bce1d9e6b7cf2938570cdfa82223dc808e2ba8 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 23 Jul 2020 19:03:40 +0200 Subject: [PATCH 33/42] qmlpreview: fix compile Change-Id: I7c9b82637217e45c6d239413df07cc9d62440603 Reviewed-by: Tim Jenssen --- src/plugins/qmlpreview/qmldebugtranslationwidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp index 071faf07058..2590e96363e 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -292,7 +292,7 @@ void QmlDebugTranslationWidget::runTest() int timerCounter = 1; const auto testLanguageList = m_testLanguages; - auto testLanguages = [previewPlugin, runControl, testLanguageList](int timerCounter, const QString &previewedFile = QString()) { + auto testLanguages = [previewPlugin, runControl, testLanguageList](int timerCounter, const QString &previewedFile) { qDebug() << "testLanguages" << previewedFile; for (auto language : testLanguageList) { QTimer::singleShot(timerCounter * 1000, previewPlugin, [previewPlugin, runControl, language, previewedFile]() { @@ -309,7 +309,7 @@ void QmlDebugTranslationWidget::runTest() testLanguages(timerCounter++, filePath.toString()); } } else { - testLanguages(timerCounter); + testLanguages(timerCounter, QString()); } }); From 50791a07a646ac5cb54c4409c871f7d9a61cd7f4 Mon Sep 17 00:00:00 2001 From: Kwanghyo Park Date: Tue, 4 Aug 2020 17:02:25 +0900 Subject: [PATCH 34/42] Change encoding to Unicode (UTF-8) There is an error while build on the machine which needs Unicode encoding for compile. Most of the files are fine but not the fixed file. So changed the files encoding to Unicode. Change-Id: I8abb2471c26231c8d659dcacd99e71f8d255415f Reviewed-by: Kwanghyo Park Reviewed-by: Thomas Hartmann --- src/libs/advanceddockingsystem/elidinglabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/advanceddockingsystem/elidinglabel.cpp b/src/libs/advanceddockingsystem/elidinglabel.cpp index dfd812bae2a..8cff2357d87 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.cpp +++ b/src/libs/advanceddockingsystem/elidinglabel.cpp @@ -1,4 +1,4 @@ -/**************************************************************************** +/**************************************************************************** ** ** Copyright (C) 2020 Uwe Kindler ** Contact: https://www.qt.io/licensing/ From 97cc02b78105f1f6f478764ee0c6a56cc806fb07 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 6 Aug 2020 09:59:29 +0200 Subject: [PATCH 35/42] QmlDesigner: Fix typo bug Change-Id: I074beda7dc5fa6dc2775e764983bf72e17ea90cf Reviewed-by: Thomas Hartmann Reviewed-by: Michael Winkelmann --- .../qmldesigner/designercore/instances/nodeinstanceview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 063a2d1e7d0..ce6412962dc 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -355,8 +355,8 @@ void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) QList bindingList; QList valueList; - if (modelNode.hasBindingProperty("yx")) - bindingList.append(modelNode.bindingProperty("yx")); + if (modelNode.hasBindingProperty("x")) + bindingList.append(modelNode.bindingProperty("x")); else if (modelNode.hasVariantProperty("y")) valueList.append(modelNode.variantProperty("y")); From b4027b7943e52374141ec6b66e9ec0ceadfe81e9 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 6 Aug 2020 10:45:02 +0200 Subject: [PATCH 36/42] QmlDesigner: Fix crash for list view model There was a type, so it crashed. The code is now under tests so we cannot break it anymore. Task-number: QDS-2563 Change-Id: I81426a9f8a568b217b7bf9c8c261b24be14ff61a Reviewed-by: Thomas Hartmann --- .../componentcore/designeractionmanager.cpp | 34 ++++++++-------- .../listmodeleditor/listmodeleditormodel.cpp | 31 +++++++++++++- .../listmodeleditor/listmodeleditormodel.h | 16 +++++--- tests/unit/unittest/listmodeleditor-test.cpp | 40 ++++++++++++++++++- 4 files changed, 96 insertions(+), 25 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index fb7d4ce2029..e0978df21c2 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -363,32 +363,30 @@ public: bool isEnabled(const SelectionContext &) const override { return true; } - static ModelNode listModelNode(const ModelNode &listViewNode) - { - if (listViewNode.hasProperty("model")) { - if (listViewNode.hasBindingProperty("model")) - return listViewNode.bindingProperty("model").resolveToModelNode(); - else if (listViewNode.hasNodeProperty("model")) - return listViewNode.nodeProperty("model").modelNode(); - } - - ModelNode newModel = listViewNode.view()->createModelNode("QtQml.Models.ListModel", 2, 15); - listViewNode.nodeProperty("mode").reparentHere(newModel); - - return newModel; - } - static void openDialog(const SelectionContext &selectionState) { - ListModelEditorModel model; - ModelNode targetNode = selectionState.targetNode(); if (!targetNode.isValid()) targetNode = selectionState.currentSingleSelectedNode(); if (!targetNode.isValid()) return; - model.setListModel(listModelNode(targetNode)); + AbstractView *view = targetNode.view(); + NodeMetaInfo modelMetaInfo = view->model()->metaInfo("ListModel"); + NodeMetaInfo elementMetaInfo = view->model()->metaInfo("ListElement"); + + ListModelEditorModel model{[&] { + return view->createModelNode(modelMetaInfo.typeName(), + modelMetaInfo.majorVersion(), + modelMetaInfo.minorVersion()); + }, + [&] { + return view->createModelNode(elementMetaInfo.typeName(), + elementMetaInfo.majorVersion(), + elementMetaInfo.minorVersion()); + }}; + + model.setListView(targetNode); ListModelEditorDialog dialog{Core::ICore::mainWindow()}; dialog.setModel(&model); diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 0aeabb8b895..48b1ad6fc97 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -26,7 +26,9 @@ #include "listmodeleditormodel.h" #include +#include #include +#include #include #include @@ -185,6 +187,22 @@ void renameProperties(const QStandardItemModel *model, static_cast(model->item(rowIndex, columnIndex))->renameProperty(newPropertyName); } +ModelNode listModelNode(const ModelNode &listViewNode, + const std::function &createModelCallback) +{ + if (listViewNode.hasProperty("model")) { + if (listViewNode.hasBindingProperty("model")) + return listViewNode.bindingProperty("model").resolveToModelNode(); + else if (listViewNode.hasNodeProperty("model")) + return listViewNode.nodeProperty("model").modelNode(); + } + + ModelNode newModel = createModelCallback(); + listViewNode.nodeProperty("model").reparentHere(newModel); + + return newModel; +} + } // namespace void ListModelEditorModel::populateModel() @@ -214,9 +232,20 @@ void ListModelEditorModel::appendItems(const ModelNode &listElementNode) appendRow(row); } +void ListModelEditorModel::setListModel(ModelNode node) +{ + m_listModelNode = node; + populateModel(); +} + +void ListModelEditorModel::setListView(ModelNode listView) +{ + setListModel(listModelNode(listView, m_createModelCallback)); +} + void ListModelEditorModel::addRow() { - auto newElement = m_listModelNode.view()->createModelNode("QtQml.Models.ListElement", 2, 15); + auto newElement = m_createElementCallback(); m_listModelNode.defaultNodeListProperty().reparentHere(newElement); appendItems(newElement); diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h index 3056d32dbb0..97bd9c18d8b 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h @@ -38,11 +38,15 @@ class ListModelEditorModel : public QStandardItemModel using QStandardItemModel::removeRows; public: - void setListModel(ModelNode node) - { - m_listModelNode = node; - populateModel(); - } + ListModelEditorModel(std::function createModelCallback, + std::function createElementCallback) + : m_createModelCallback(std::move(createModelCallback)) + , m_createElementCallback(std::move(createElementCallback)) + {} + + void setListModel(ModelNode node); + + void setListView(ModelNode listView); void addRow(); void addColumn(const QString &columnName); @@ -70,6 +74,8 @@ private: private: ModelNode m_listModelNode; QList m_propertyNames; + std::function m_createModelCallback; + std::function m_createElementCallback; }; } // namespace QmlDesigner diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index af4009ae9b0..0a5a91327aa 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -31,8 +31,10 @@ #include #include +#include #include #include +#include #include namespace { @@ -94,6 +96,7 @@ public: emptyListModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); + listViewNode = mockView.createModelNode("QtQuick.ListView", 2, 15); listModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); mockView.rootModelNode().defaultNodeListProperty().reparentHere(listModelNode); element1 = createElement({{"name", "foo"}, {"value", 1}, {"value2", 42}}); @@ -183,7 +186,10 @@ public: protected: std::unique_ptr designerModel{QmlDesigner::Model::create("QtQuick.Item", 1, 1)}; NiceMock mockView; - QmlDesigner::ListModelEditorModel model; + QmlDesigner::ListModelEditorModel model{ + [&] { return mockView.createModelNode("QtQml.Models.ListModel", 2, 15); }, + [&] { return mockView.createModelNode("QtQml.Models.ListElement", 2, 15); }}; + ModelNode listViewNode; ModelNode listModelNode; ModelNode emptyListModelNode; ModelNode element1; @@ -1272,4 +1278,36 @@ TEST_F(ListModelEditor, SelectionAfterMoveRowsUp) index(2, 3))); } +TEST_F(ListModelEditor, ListViewHasNoModel) +{ + model.setListView(listViewNode); + + ASSERT_THAT(listViewNode.nodeProperty("model").modelNode().type(), Eq("QtQml.Models.ListModel")); +} + +TEST_F(ListModelEditor, ListViewHasModelInside) +{ + listViewNode.nodeProperty("model").reparentHere(listModelNode); + + model.setListView(listViewNode); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, ListViewHasModelBinding) +{ + listModelNode.setIdWithoutRefactoring("listModel"); + listViewNode.bindingProperty("model").setExpression("listModel"); + + model.setListView(listViewNode); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + } // namespace From b5d59c75a7dee830483631cd9045d5338fc25c2d Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 6 Aug 2020 11:27:27 +0200 Subject: [PATCH 37/42] QmlDesigner: Fix bool handling in the list model editor Task-number: QDS-2581 Change-Id: I47a9ed4ca55532bb7199a6c5dd4894b7adb7d05d Reviewed-by: Thomas Hartmann --- .../listmodeleditor/listmodeleditormodel.cpp | 11 ++++ tests/unit/unittest/listmodeleditor-test.cpp | 66 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 48b1ad6fc97..b73a53f76c8 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -51,6 +51,17 @@ public: QVariant maybeConvertToNumber(const QVariant &value) { + if (value.type() == QVariant::Bool) + return value; + + if (value.type() == QVariant::String) { + const QString text = value.toString(); + if (text == "true") + return QVariant(true); + if (text == "false") + return QVariant(false); + } + bool canConvert = false; double convertedValue = value.toDouble(&canConvert); if (canConvert) { diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index 0a5a91327aa..bdd5e9c0700 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -1310,4 +1310,70 @@ TEST_F(ListModelEditor, ListViewHasModelBinding) ElementsAre("pic.png", "poo", 111, IsInvalid()))); } +TEST_F(ListModelEditor, AddBooleanDisplayValues) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, true); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), true, 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, AddBooleanProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, true); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"), + IsVariantProperty("value", true), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, AddTrueAsStringProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, "true"); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", true), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, AddFalseAsStringProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, "false"); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", false), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + } // namespace From e43c7fdb1de786a2d5b90d68830c5308319a7dcf Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 27 Jul 2020 18:14:33 +0200 Subject: [PATCH 38/42] QmlDesigner: Split messaging and process for puppets This will make it easier to implement custom puppets. The new connection manager will restucture the code and it add a mechanism to capture data too. Task-number: QDS-2529 Change-Id: I5d15c3303ef1c9a3e25ba197d350e0d561ce813a Reviewed-by: Thomas Hartmann --- .../qmlpuppet/commands/captureddatacommand.h | 107 ++++ .../qml/qmlpuppet/commands/commands.pri | 135 ++--- .../instances/nodeinstanceclientproxy.cpp | 27 +- .../instances/nodeinstanceclientproxy.h | 7 +- .../interfaces/nodeinstanceclientinterface.h | 2 + .../nodeinstanceserverinterface.cpp | 6 +- .../interfaces/nodeinstanceserverinterface.h | 5 - .../qml2puppet/instances/instances.pri | 98 ++-- .../instances/nodeinstanceserver.cpp | 21 +- .../qml2puppet/instances/nodeinstanceserver.h | 4 +- .../qt5capturenodeinstanceserver.cpp | 105 ++++ .../instances/qt5capturenodeinstanceserver.h | 45 ++ .../instances/qt5nodeinstanceclientproxy.cpp | 14 +- .../qt5previewnodeinstanceserver.cpp | 3 +- .../qml2puppet/instances/servernodeinstance.h | 8 +- src/plugins/qmldesigner/CMakeLists.txt | 6 + .../itemlibrary/itemlibraryassetimporter.cpp | 28 +- .../itemlibrary/itemlibraryassetimporter.h | 12 +- .../designercore/include/nodeinstanceview.h | 18 +- .../designercore/include/viewmanager.h | 2 +- .../instances/baseconnectionmanager.cpp | 126 +++++ .../instances/baseconnectionmanager.h | 76 +++ .../instances/capturingconnectionmanager.cpp | 61 +++ .../instances/capturingconnectionmanager.h | 46 ++ .../instances/connectionmanager.cpp | 177 ++++++ .../instances/connectionmanager.h | 77 +++ .../instances/connectionmanagerinterface.cpp | 54 ++ .../instances/connectionmanagerinterface.h | 80 +++ .../designercore/instances/instances.pri | 29 +- .../interactiveconnectionmanager.cpp | 109 ++++ .../instances/interactiveconnectionmanager.h | 51 ++ .../instances/nodeinstanceserverproxy.cpp | 504 +----------------- .../instances/nodeinstanceserverproxy.h | 56 +- .../instances/nodeinstanceview.cpp | 130 ++--- .../designercore/instances/puppetcreator.cpp | 49 +- .../designercore/instances/puppetcreator.h | 31 +- .../instances/qprocessuniqueptr.h | 53 ++ .../designercore/model/viewmanager.cpp | 35 +- src/plugins/qmldesigner/qmldesignerplugin.qbs | 11 + src/tools/qml2puppet/CMakeLists.txt | 2 + src/tools/qml2puppet/qml2puppet.qbs | 4 + .../qml/qmldesigner/coretests/CMakeLists.txt | 1 + .../qml/qmldesigner/coretests/coretests.pro | 6 +- .../qmldesigner/coretests/tst_testcore.cpp | 24 +- .../qml/qmldesigner/testconnectionmanager.cpp | 72 +++ .../qml/qmldesigner/testconnectionmanager.h | 56 ++ 46 files changed, 1738 insertions(+), 835 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h create mode 100644 src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp create mode 100644 src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h create mode 100644 src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp create mode 100644 src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h create mode 100644 src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp create mode 100644 src/plugins/qmldesigner/designercore/instances/connectionmanager.h create mode 100644 src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp create mode 100644 src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h create mode 100644 src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp create mode 100644 src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h create mode 100644 src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h create mode 100644 tests/auto/qml/qmldesigner/testconnectionmanager.cpp create mode 100644 tests/auto/qml/qmldesigner/testconnectionmanager.h diff --git a/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h new file mode 100644 index 00000000000..c7950e278bc --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include "imagecontainer.h" + +namespace QmlDesigner { + +class CapturedDataCommand +{ +public: + struct NodeData + { + friend QDataStream &operator<<(QDataStream &out, const NodeData &data) + { + out << data.nodeId; + out << data.contentRect; + out << data.sceneTransform; + out << data.text; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, NodeData &data) + { + in >> data.nodeId; + in >> data.contentRect; + in >> data.sceneTransform; + in >> data.text; + + return in; + } + + qint32 nodeId = -1; + QRectF contentRect; + QTransform sceneTransform; + QString text; + }; + + struct StateData + { + friend QDataStream &operator<<(QDataStream &out, const StateData &data) + { + out << data.image; + out << data.nodeData; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, StateData &data) + { + in >> data.image; + in >> data.nodeData; + + return in; + } + + ImageContainer image; + QVector nodeData; + }; + + friend QDataStream &operator<<(QDataStream &out, const CapturedDataCommand &command) + { + out << command.stateData; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, CapturedDataCommand &command) + { + in >> command.stateData; + + return in; + } + +public: + QVector stateData; +}; + +} // namespace QmlDesigner + +Q_DECLARE_METATYPE(QmlDesigner::CapturedDataCommand) diff --git a/share/qtcreator/qml/qmlpuppet/commands/commands.pri b/share/qtcreator/qml/qmlpuppet/commands/commands.pri index 03a44ae7509..f343cbbd47a 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/commands.pri +++ b/share/qtcreator/qml/qmlpuppet/commands/commands.pri @@ -1,69 +1,70 @@ -INCLUDEPATH += $$PWD/ +INCLUDEPATH += $$PWD -HEADERS += $$PWD/synchronizecommand.h -HEADERS += $$PWD/changepreviewimagesizecommand.h -HEADERS += $$PWD/changelanguagecommand.h -HEADERS += $$PWD//debugoutputcommand.h -HEADERS += $$PWD/endpuppetcommand.h -HEADERS += $$PWD/tokencommand.h -HEADERS += $$PWD/componentcompletedcommand.h -HEADERS += $$PWD/completecomponentcommand.h -HEADERS += $$PWD/statepreviewimagechangedcommand.h -HEADERS += $$PWD/childrenchangedcommand.h -HEADERS += $$PWD/changebindingscommand.h -HEADERS += $$PWD/changefileurlcommand.h -HEADERS += $$PWD/changeidscommand.h -HEADERS += $$PWD/changenodesourcecommand.h -HEADERS += $$PWD/changestatecommand.h -HEADERS += $$PWD/changevaluescommand.h -HEADERS += $$PWD/createscenecommand.h -HEADERS += $$PWD/clearscenecommand.h -HEADERS += $$PWD/createinstancescommand.h -HEADERS += $$PWD/informationchangedcommand.h -HEADERS += $$PWD/pixmapchangedcommand.h -HEADERS += $$PWD/removeinstancescommand.h -HEADERS += $$PWD/removepropertiescommand.h -HEADERS += $$PWD/reparentinstancescommand.h -HEADERS += $$PWD/valueschangedcommand.h -HEADERS += $$PWD/changeauxiliarycommand.h -HEADERS += $$PWD/removesharedmemorycommand.h -HEADERS += $$PWD/puppetalivecommand.h -HEADERS += $$PWD/changeselectioncommand.h -HEADERS += $$PWD/update3dviewstatecommand.h -HEADERS += $$PWD/puppettocreatorcommand.h -HEADERS += $$PWD/inputeventcommand.h -HEADERS += $$PWD/view3dactioncommand.h +HEADERS += $$PWD/synchronizecommand.h \ \ + $$PWD/captureddatacommand.h \ + $$PWD/changepreviewimagesizecommand.h \ + $$PWD/changelanguagecommand.h \ + $$PWD//debugoutputcommand.h \ + $$PWD/endpuppetcommand.h \ + $$PWD/tokencommand.h \ + $$PWD/componentcompletedcommand.h \ + $$PWD/completecomponentcommand.h \ + $$PWD/statepreviewimagechangedcommand.h \ + $$PWD/childrenchangedcommand.h \ + $$PWD/changebindingscommand.h \ + $$PWD/changefileurlcommand.h \ + $$PWD/changeidscommand.h \ + $$PWD/changenodesourcecommand.h \ + $$PWD/changestatecommand.h \ + $$PWD/changevaluescommand.h \ + $$PWD/createscenecommand.h \ + $$PWD/clearscenecommand.h \ + $$PWD/createinstancescommand.h \ + $$PWD/informationchangedcommand.h \ + $$PWD/pixmapchangedcommand.h \ + $$PWD/removeinstancescommand.h \ + $$PWD/removepropertiescommand.h \ + $$PWD/reparentinstancescommand.h \ + $$PWD/valueschangedcommand.h \ + $$PWD/changeauxiliarycommand.h \ + $$PWD/removesharedmemorycommand.h \ + $$PWD/puppetalivecommand.h \ + $$PWD/changeselectioncommand.h \ + $$PWD/update3dviewstatecommand.h \ + $$PWD/puppettocreatorcommand.h \ + $$PWD/inputeventcommand.h \ + $$PWD/view3dactioncommand.h -SOURCES += $$PWD/synchronizecommand.cpp -SOURCES += $$PWD/changepreviewimagesizecommand.cpp -SOURCES += $$PWD/changelanguagecommand.cpp -SOURCES += $$PWD/debugoutputcommand.cpp -SOURCES += $$PWD/endpuppetcommand.cpp -SOURCES += $$PWD/tokencommand.cpp -SOURCES += $$PWD/componentcompletedcommand.cpp -SOURCES += $$PWD/completecomponentcommand.cpp -SOURCES += $$PWD/statepreviewimagechangedcommand.cpp -SOURCES += $$PWD/childrenchangedcommand.cpp -SOURCES += $$PWD/changebindingscommand.cpp -SOURCES += $$PWD/changefileurlcommand.cpp -SOURCES += $$PWD/changeidscommand.cpp -SOURCES += $$PWD/changenodesourcecommand.cpp -SOURCES += $$PWD/changestatecommand.cpp -SOURCES += $$PWD/changevaluescommand.cpp -SOURCES += $$PWD/informationchangedcommand.cpp -SOURCES += $$PWD/removeinstancescommand.cpp -SOURCES += $$PWD/removepropertiescommand.cpp -SOURCES += $$PWD/reparentinstancescommand.cpp -SOURCES += $$PWD/valueschangedcommand.cpp -SOURCES += $$PWD/clearscenecommand.cpp -SOURCES += $$PWD/createinstancescommand.cpp -SOURCES += $$PWD/createscenecommand.cpp -SOURCES += $$PWD/pixmapchangedcommand.cpp -SOURCES += $$PWD/changeauxiliarycommand.cpp -SOURCES += $$PWD/removesharedmemorycommand.cpp -SOURCES += $$PWD/puppetalivecommand.cpp -SOURCES += $$PWD/changeselectioncommand.cpp -SOURCES += $$PWD/update3dviewstatecommand.cpp -SOURCES += $$PWD/puppettocreatorcommand.cpp -SOURCES += $$PWD/inputeventcommand.cpp -SOURCES += $$PWD/view3dactioncommand.cpp +SOURCES += $$PWD/synchronizecommand.cpp \ + $$PWD/changepreviewimagesizecommand.cpp \ + $$PWD/changelanguagecommand.cpp \ + $$PWD/debugoutputcommand.cpp \ + $$PWD/endpuppetcommand.cpp \ + $$PWD/tokencommand.cpp \ + $$PWD/componentcompletedcommand.cpp \ + $$PWD/completecomponentcommand.cpp \ + $$PWD/statepreviewimagechangedcommand.cpp \ + $$PWD/childrenchangedcommand.cpp \ + $$PWD/changebindingscommand.cpp \ + $$PWD/changefileurlcommand.cpp \ + $$PWD/changeidscommand.cpp \ + $$PWD/changenodesourcecommand.cpp \ + $$PWD/changestatecommand.cpp \ + $$PWD/changevaluescommand.cpp \ + $$PWD/informationchangedcommand.cpp \ + $$PWD/removeinstancescommand.cpp \ + $$PWD/removepropertiescommand.cpp \ + $$PWD/reparentinstancescommand.cpp \ + $$PWD/valueschangedcommand.cpp \ + $$PWD/clearscenecommand.cpp \ + $$PWD/createinstancescommand.cpp \ + $$PWD/createscenecommand.cpp \ + $$PWD/pixmapchangedcommand.cpp \ + $$PWD/changeauxiliarycommand.cpp \ + $$PWD/removesharedmemorycommand.cpp \ + $$PWD/puppetalivecommand.cpp \ + $$PWD/changeselectioncommand.cpp \ + $$PWD/update3dviewstatecommand.cpp \ + $$PWD/puppettocreatorcommand.cpp \ + $$PWD/inputeventcommand.cpp \ + $$PWD/view3dactioncommand.cpp diff --git a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp index 8a338df93d9..24dad650903 100644 --- a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp @@ -35,6 +35,7 @@ #include "nodeinstanceserverinterface.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" @@ -84,12 +85,11 @@ constexpr void (QLocalSocket::*LocalSocketErrorFunction)(QLocalSocket::LocalSock #endif NodeInstanceClientProxy::NodeInstanceClientProxy(QObject *parent) - : QObject(parent), - m_inputIoDevice(nullptr), - m_outputIoDevice(nullptr), - m_nodeInstanceServer(nullptr), - m_writeCommandCounter(0), - m_synchronizeId(-1) + : QObject(parent) + , m_inputIoDevice(nullptr) + , m_outputIoDevice(nullptr) + , m_writeCommandCounter(0) + , m_synchronizeId(-1) { connect(&m_puppetAliveTimer, &QTimer::timeout, this, &NodeInstanceClientProxy::sendPuppetAliveCommand); m_puppetAliveTimer.setInterval(2000); @@ -174,7 +174,8 @@ bool compareCommands(const QVariant &command, const QVariant &controlCommand) else if (command.userType() == debugOutputCommandType) return command.value() == controlCommand.value(); else if (command.userType() == changeSelectionCommandType) - return command.value() == controlCommand.value(); + return command.value() + == controlCommand.value(); } return false; @@ -267,6 +268,11 @@ void NodeInstanceClientProxy::handlePuppetToCreatorCommand(const PuppetToCreator writeCommand(QVariant::fromValue(command)); } +void NodeInstanceClientProxy::capturedData(const CapturedDataCommand &command) +{ + writeCommand(QVariant::fromValue(command)); +} + void NodeInstanceClientProxy::flush() { } @@ -365,12 +371,13 @@ void NodeInstanceClientProxy::sendPuppetAliveCommand() NodeInstanceServerInterface *NodeInstanceClientProxy::nodeInstanceServer() const { - return m_nodeInstanceServer; + return m_nodeInstanceServer.get(); } -void NodeInstanceClientProxy::setNodeInstanceServer(NodeInstanceServerInterface *nodeInstanceServer) +void NodeInstanceClientProxy::setNodeInstanceServer( + std::unique_ptr nodeInstanceServer) { - m_nodeInstanceServer = nodeInstanceServer; + m_nodeInstanceServer = std::move(nodeInstanceServer); } void NodeInstanceClientProxy::createInstances(const CreateInstancesCommand &command) diff --git a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h index e6f4b58df49..fd681b69902 100644 --- a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h +++ b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h @@ -69,7 +69,7 @@ class NodeInstanceClientProxy : public QObject, public NodeInstanceClientInterfa Q_OBJECT public: - NodeInstanceClientProxy(QObject *parent = nullptr); + NodeInstanceClientProxy(QObject *parent); void informationChanged(const InformationChangedCommand &command) override; void valuesChanged(const ValuesChangedCommand &command) override; @@ -83,6 +83,7 @@ public: void puppetAlive(const PuppetAliveCommand &command); void selectionChanged(const ChangeSelectionCommand &command) override; void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override; + void capturedData(const CapturedDataCommand &capturedData) override; void flush() override; void synchronizeWithClientProcess() override; @@ -94,7 +95,7 @@ protected: void writeCommand(const QVariant &command); void dispatchCommand(const QVariant &command); NodeInstanceServerInterface *nodeInstanceServer() const; - void setNodeInstanceServer(NodeInstanceServerInterface *nodeInstanceServer); + void setNodeInstanceServer(std::unique_ptr nodeInstanceServer); void createInstances(const CreateInstancesCommand &command); void changeFileUrl(const ChangeFileUrlCommand &command); @@ -130,7 +131,7 @@ private: QTimer m_puppetAliveTimer; QIODevice *m_inputIoDevice; QIODevice *m_outputIoDevice; - NodeInstanceServerInterface *m_nodeInstanceServer; + std::unique_ptr m_nodeInstanceServer; quint32 m_writeCommandCounter; int m_synchronizeId; }; diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h index 9498cfb1f76..d60e0d7ff0c 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h @@ -42,6 +42,7 @@ class DebugOutputCommand; class PuppetAliveCommand; class ChangeSelectionCommand; class PuppetToCreatorCommand; +class CapturedDataCommand; class NodeInstanceClientInterface { @@ -57,6 +58,7 @@ public: virtual void debugOutput(const DebugOutputCommand &command) = 0; virtual void selectionChanged(const ChangeSelectionCommand &command) = 0; virtual void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) = 0; + virtual void capturedData(const CapturedDataCommand &command) = 0; virtual void flush() {} virtual void synchronizeWithClientProcess() {} diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp index 62669d31588..e3a05376e27 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp @@ -27,16 +27,17 @@ #include #include "addimportcontainer.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" #include "changeidscommand.h" #include "changelanguagecommand.h" #include "changenodesourcecommand.h" +#include "changepreviewimagesizecommand.h" #include "changeselectioncommand.h" #include "changestatecommand.h" #include "changevaluescommand.h" -#include "changepreviewimagesizecommand.h" #include "childrenchangedcommand.h" #include "clearscenecommand.h" #include "completecomponentcommand.h" @@ -219,6 +220,9 @@ void NodeInstanceServerInterface::registerCommands() qRegisterMetaType("ChangePreviewImageSizeCommand"); qRegisterMetaTypeStreamOperators("ChangePreviewImageSizeCommand"); + + qRegisterMetaType("CapturedDataCommand"); + qRegisterMetaTypeStreamOperators("CapturedDataCommand"); } } diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h index 10c2d1fdbb5..39eb2618d67 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h @@ -60,11 +60,6 @@ class NodeInstanceServerInterface : public QObject { Q_OBJECT public: - enum RunModus { - NormalModus, - TestModus // No preview images and synchronized - }; - explicit NodeInstanceServerInterface(QObject *parent = nullptr); virtual void createInstances(const CreateInstancesCommand &command) = 0; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri index 4fe46e29d83..ce9bb2376e7 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri @@ -5,52 +5,54 @@ versionAtLeast(QT_VERSION, 5.15.0):qtHaveModule(quick3d) { DEFINES *= QUICK3D_MODULE } -HEADERS += $$PWD/qt5nodeinstanceserver.h -HEADERS += $$PWD/qt5testnodeinstanceserver.h -HEADERS += $$PWD/qt5informationnodeinstanceserver.h -HEADERS += $$PWD/qt5rendernodeinstanceserver.h -HEADERS += $$PWD/qt5previewnodeinstanceserver.h -HEADERS += $$PWD/qt5nodeinstanceclientproxy.h -HEADERS += $$PWD/quickitemnodeinstance.h -HEADERS += $$PWD/behaviornodeinstance.h -HEADERS += $$PWD/dummycontextobject.h -HEADERS += $$PWD/childrenchangeeventfilter.h -HEADERS += $$PWD/componentnodeinstance.h -HEADERS += $$PWD/dummynodeinstance.h -HEADERS += $$PWD/nodeinstanceserver.h -HEADERS += $$PWD/nodeinstancesignalspy.h -HEADERS += $$PWD/objectnodeinstance.h -HEADERS += $$PWD/qmlpropertychangesnodeinstance.h -HEADERS += $$PWD/qmlstatenodeinstance.h -HEADERS += $$PWD/qmltransitionnodeinstance.h -HEADERS += $$PWD/servernodeinstance.h -HEADERS += $$PWD/anchorchangesnodeinstance.h -HEADERS += $$PWD/positionernodeinstance.h -HEADERS += $$PWD/layoutnodeinstance.h -HEADERS += $$PWD/qt3dpresentationnodeinstance.h -HEADERS += $$PWD/quick3dnodeinstance.h +HEADERS += $$PWD/qt5nodeinstanceserver.h \ + $$PWD/qt5capturenodeinstanceserver.h \ + $$PWD/qt5testnodeinstanceserver.h \ + $$PWD/qt5informationnodeinstanceserver.h \ + $$PWD/qt5rendernodeinstanceserver.h \ + $$PWD/qt5previewnodeinstanceserver.h \ + $$PWD/qt5nodeinstanceclientproxy.h \ + $$PWD/quickitemnodeinstance.h \ + $$PWD/behaviornodeinstance.h \ + $$PWD/dummycontextobject.h \ + $$PWD/childrenchangeeventfilter.h \ + $$PWD/componentnodeinstance.h \ + $$PWD/dummynodeinstance.h \ + $$PWD/nodeinstanceserver.h \ + $$PWD/nodeinstancesignalspy.h \ + $$PWD/objectnodeinstance.h \ + $$PWD/qmlpropertychangesnodeinstance.h \ + $$PWD/qmlstatenodeinstance.h \ + $$PWD/qmltransitionnodeinstance.h \ + $$PWD/servernodeinstance.h \ + $$PWD/anchorchangesnodeinstance.h \ + $$PWD/positionernodeinstance.h \ + $$PWD/layoutnodeinstance.h \ + $$PWD/qt3dpresentationnodeinstance.h \ + $$PWD/quick3dnodeinstance.h -SOURCES += $$PWD/qt5nodeinstanceserver.cpp -SOURCES += $$PWD/qt5testnodeinstanceserver.cpp -SOURCES += $$PWD/qt5informationnodeinstanceserver.cpp -SOURCES += $$PWD/qt5rendernodeinstanceserver.cpp -SOURCES += $$PWD/qt5previewnodeinstanceserver.cpp -SOURCES += $$PWD/qt5nodeinstanceclientproxy.cpp -SOURCES += $$PWD/quickitemnodeinstance.cpp -SOURCES += $$PWD/behaviornodeinstance.cpp -SOURCES += $$PWD/dummycontextobject.cpp -SOURCES += $$PWD/childrenchangeeventfilter.cpp -SOURCES += $$PWD/componentnodeinstance.cpp -SOURCES += $$PWD/dummynodeinstance.cpp -SOURCES += $$PWD/nodeinstanceserver.cpp -SOURCES += $$PWD/nodeinstancesignalspy.cpp -SOURCES += $$PWD/objectnodeinstance.cpp -SOURCES += $$PWD/qmlpropertychangesnodeinstance.cpp -SOURCES += $$PWD/qmlstatenodeinstance.cpp -SOURCES += $$PWD/qmltransitionnodeinstance.cpp -SOURCES += $$PWD/servernodeinstance.cpp -SOURCES += $$PWD/anchorchangesnodeinstance.cpp -SOURCES += $$PWD/positionernodeinstance.cpp -SOURCES += $$PWD/layoutnodeinstance.cpp -SOURCES += $$PWD/qt3dpresentationnodeinstance.cpp -SOURCES += $$PWD/quick3dnodeinstance.cpp +SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ + $$PWD/qt5capturenodeinstanceserver.cpp \ + $$PWD/qt5testnodeinstanceserver.cpp \ + $$PWD/qt5informationnodeinstanceserver.cpp \ + $$PWD/qt5rendernodeinstanceserver.cpp \ + $$PWD/qt5previewnodeinstanceserver.cpp \ + $$PWD/qt5nodeinstanceclientproxy.cpp \ + $$PWD/quickitemnodeinstance.cpp \ + $$PWD/behaviornodeinstance.cpp \ + $$PWD/dummycontextobject.cpp \ + $$PWD/childrenchangeeventfilter.cpp \ + $$PWD/componentnodeinstance.cpp \ + $$PWD/dummynodeinstance.cpp \ + $$PWD/nodeinstanceserver.cpp \ + $$PWD/nodeinstancesignalspy.cpp \ + $$PWD/objectnodeinstance.cpp \ + $$PWD/qmlpropertychangesnodeinstance.cpp \ + $$PWD/qmlstatenodeinstance.cpp \ + $$PWD/qmltransitionnodeinstance.cpp \ + $$PWD/servernodeinstance.cpp \ + $$PWD/anchorchangesnodeinstance.cpp \ + $$PWD/positionernodeinstance.cpp \ + $$PWD/layoutnodeinstance.cpp \ + $$PWD/qt3dpresentationnodeinstance.cpp \ + $$PWD/quick3dnodeinstance.cpp diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index 9b0b59d58bb..1abfc634bd2 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -179,6 +179,8 @@ NodeInstanceServer::NodeInstanceServer(NodeInstanceClientInterface *nodeInstance m_childrenChangeEventFilter(new Internal::ChildrenChangeEventFilter(this)), m_nodeInstanceClient(nodeInstanceClient) { + m_idInstances.reserve(1000); + qmlRegisterType("QmlDesigner", 1, 0, "DummyContextObject"); connect(m_childrenChangeEventFilter.data(), &Internal::ChildrenChangeEventFilter::childrenChanged, this, &NodeInstanceServer::emitParentChanged); @@ -226,8 +228,8 @@ ServerNodeInstance NodeInstanceServer::instanceForId(qint32 id) const if (id < 0) return ServerNodeInstance(); - Q_ASSERT(m_idInstanceHash.contains(id)); - return m_idInstanceHash.value(id); + Q_ASSERT(m_idInstances.size() > id); + return m_idInstances[id]; } bool NodeInstanceServer::hasInstanceForId(qint32 id) const @@ -235,7 +237,7 @@ bool NodeInstanceServer::hasInstanceForId(qint32 id) const if (id < 0) return false; - return m_idInstanceHash.contains(id) && m_idInstanceHash.value(id).isValid(); + return m_idInstances.size() > id && m_idInstances[id].isValid(); } ServerNodeInstance NodeInstanceServer::instanceForObject(QObject *object) const @@ -790,7 +792,7 @@ void NodeInstanceServer::removeAllInstanceRelationships() instance.makeInvalid(); } - m_idInstanceHash.clear(); + m_idInstances.clear(); m_objectInstanceHash.clear(); } @@ -1243,10 +1245,11 @@ void NodeInstanceServer::notifyPropertyChange(qint32 instanceid, const PropertyN void NodeInstanceServer::insertInstanceRelationship(const ServerNodeInstance &instance) { Q_ASSERT(instance.isValid()); - Q_ASSERT(!m_idInstanceHash.contains(instance.instanceId())); Q_ASSERT(!m_objectInstanceHash.contains(instance.internalObject())); m_objectInstanceHash.insert(instance.internalObject(), instance); - m_idInstanceHash.insert(instance.instanceId(), instance); + if (instance.instanceId() >= m_idInstances.size()) + m_idInstances.resize(instance.instanceId() + 1); + m_idInstances[instance.instanceId()] = instance; } void NodeInstanceServer::removeInstanceRelationsip(qint32 instanceId) @@ -1255,7 +1258,7 @@ void NodeInstanceServer::removeInstanceRelationsip(qint32 instanceId) ServerNodeInstance instance = instanceForId(instanceId); if (instance.isValid()) instance.setId(QString()); - m_idInstanceHash.remove(instanceId); + m_idInstances[instanceId] = ServerNodeInstance{}; m_objectInstanceHash.remove(instance.internalObject()); instance.makeInvalid(); } @@ -1383,8 +1386,8 @@ void NodeInstanceServer::removeInstanceRelationsipForDeletedObject(QObject *obje ServerNodeInstance instance = instanceForObject(object); m_objectInstanceHash.remove(object); - if (m_idInstanceHash.contains(instance.instanceId())) - m_idInstanceHash.remove(instance.instanceId()); + if (instance.instanceId() >= 0 && m_idInstances.size() > instance.instanceId()) + m_idInstances[instance.instanceId()] = ServerNodeInstance{}; } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h index 87eb5a1b7e9..2c628f1411b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h @@ -160,6 +160,8 @@ public: ServerNodeInstance instanceForObject(QObject *object) const; bool hasInstanceForObject(QObject *object) const; + const QVector &nodeInstances() const { return m_idInstances; } + virtual QQmlEngine *engine() const = 0; QQmlContext *context() const; @@ -272,7 +274,7 @@ private: void setupOnlyWorkingImports(const QStringList &workingImportStatementList); ServerNodeInstance m_rootNodeInstance; ServerNodeInstance m_activeStateInstance; - QHash m_idInstanceHash; + QVector m_idInstances; QHash m_objectInstanceHash; QMultiHash m_fileSystemWatcherHash; QList > > m_dummyObjectList; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp new file mode 100644 index 00000000000..aea75a76c7f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qt5capturenodeinstanceserver.h" +#include "servernodeinstance.h" + +#include +#include +#include + +#include +#include + +namespace QmlDesigner { + +namespace { + +QImage renderPreviewImage(ServerNodeInstance rootNodeInstance) +{ + rootNodeInstance.updateDirtyNodeRecursive(); + + QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize(); + + QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize); + + return previewImage; +} + +CapturedDataCommand::StateData collectStateData(ServerNodeInstance rootNodeInstance, + const QVector &nodeInstances, + qint32 stateInstanceId) +{ + CapturedDataCommand::StateData stateData; + stateData.image = ImageContainer(stateInstanceId, + QmlDesigner::renderPreviewImage(rootNodeInstance), + stateInstanceId); + + for (const ServerNodeInstance &instance : nodeInstances) { + auto textProperty = instance.property("text"); + if (!textProperty.isNull() && instance.holdsGraphical()) { + CapturedDataCommand::NodeData nodeData; + nodeData.nodeId = instance.instanceId(); + nodeData.contentRect = instance.contentItemBoundingRect(); + nodeData.sceneTransform = instance.sceneTransform(); + nodeData.text = textProperty.toString(); + stateData.nodeData.push_back(std::move(nodeData)); + } + } + + return stateData; +} +} // namespace + +void Qt5CaptureNodeInstanceServer::collectItemChangesAndSendChangeCommands() +{ + static bool inFunction = false; + + if (!rootNodeInstance().holdsGraphical()) + return; + + if (!inFunction) { + inFunction = true; + + DesignerSupport::polishItems(quickView()); + + QVector stateDatas; + stateDatas.push_back(collectStateData(rootNodeInstance(), nodeInstances(), 0)); + + for (ServerNodeInstance stateInstance : rootNodeInstance().stateInstances()) { + stateInstance.activateState(); + stateDatas.push_back( + collectStateData(rootNodeInstance(), nodeInstances(), stateInstance.instanceId())); + stateInstance.deactivateState(); + } + + nodeInstanceClient()->capturedData(CapturedDataCommand{stateDatas}); + + slowDownRenderTimer(); + inFunction = false; + } +} + +} // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h new file mode 100644 index 00000000000..cd0208e5633 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class Qt5CaptureNodeInstanceServer : public Qt5PreviewNodeInstanceServer +{ +public: + explicit Qt5CaptureNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) + : Qt5PreviewNodeInstanceServer(nodeInstanceClient) + {} + +protected: + void collectItemChangesAndSendChangeCommands() override; + +private: +}; + +} // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index 449e4ff188f..1cdfc910742 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -27,6 +27,7 @@ #include +#include "qt5capturenodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #include "qt5previewnodeinstanceserver.h" #include "qt5rendernodeinstanceserver.h" @@ -37,7 +38,7 @@ #if defined(Q_OS_UNIX) #include #elif defined(Q_OS_WIN) -#include +#include #endif namespace QmlDesigner { @@ -57,18 +58,21 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : DesignerSupport::activateDesignerWindowManager(); if (QCoreApplication::arguments().at(1) == QLatin1String("--readcapturedstream")) { qputenv("DESIGNER_DONT_USE_SHARED_MEMORY", "1"); - setNodeInstanceServer(new Qt5TestNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique(this)); initializeCapturedStream(QCoreApplication::arguments().at(2)); readDataStream(); QCoreApplication::exit(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("previewmode")) { - setNodeInstanceServer(new Qt5PreviewNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique(this)); initializeSocket(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("editormode")) { - setNodeInstanceServer(new Qt5InformationNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique(this)); initializeSocket(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("rendermode")) { - setNodeInstanceServer(new Qt5RenderNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique(this)); + initializeSocket(); + } else if (QCoreApplication::arguments().at(2) == QLatin1String("capturemode")) { + setNodeInstanceServer(std::make_unique(this)); initializeSocket(); } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp index 50db9b99719..452f2ddd2fc 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp @@ -84,7 +84,8 @@ void Qt5PreviewNodeInstanceServer::collectItemChangesAndSendChangeCommands() instance.deactivateState(); } - nodeInstanceClient()->statePreviewImagesChanged(StatePreviewImageChangedCommand(imageContainerVector)); + nodeInstanceClient()->statePreviewImagesChanged( + StatePreviewImageChangedCommand(imageContainerVector)); slowDownRenderTimer(); inFunction = false; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h index ceb3b1e5b14..0881b738d94 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h @@ -71,6 +71,7 @@ class ServerNodeInstance friend class Qt5InformationNodeInstanceServer; friend class Qt5NodeInstanceServer; friend class Qt5PreviewNodeInstanceServer; + friend class Qt5CaptureNodeInstanceServer; friend class Qt5TestNodeInstanceServer; friend class QHash; friend uint qHash(const ServerNodeInstance &instance); @@ -169,6 +170,8 @@ public: static bool isSubclassOf(QObject *object, const QByteArray &superTypeName); void setModifiedFlag(bool b); + void updateDirtyNodeRecursive(); + bool holdsGraphical() const; private: // functions ServerNodeInstance(const QSharedPointer &abstractInstance); @@ -195,7 +198,6 @@ private: // functions void setDeleteHeldInstance(bool deleteInstance); void reparent(const ServerNodeInstance &oldParentInstance, const PropertyName &oldParentProperty, const ServerNodeInstance &newParentInstance, const PropertyName &newParentProperty); - void setId(const QString &id); static QSharedPointer createInstance(QObject *objectToBeWrapped); @@ -204,10 +206,6 @@ private: // functions void setNodeSource(const QString &source); - bool holdsGraphical() const; - - void updateDirtyNodeRecursive(); - QObject *internalObject() const; // should be not used outside of the nodeinstances!!!! private: // variables diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index f5714970161..ecec481f90d 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -505,6 +505,12 @@ extend_qtc_plugin(QmlDesigner puppetbuildprogressdialog.cpp puppetbuildprogressdialog.h puppetbuildprogressdialog.ui puppetcreator.cpp puppetcreator.h puppetdialog.cpp puppetdialog.h puppetdialog.ui + connectionmanagerinterface.cpp connectionmanagerinterface.h + baseconnectionmanager.cpp baseconnectionmanager.h + connectionmanager.cpp connectionmanager.h + capturingconnectionmanager.cpp capturingconnectionmanager.h + interactiveconnectionmanager.cpp interactiveconnectionmanager.h + qprocessuniqueptr.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 6a5edb18a39..1fc817cd01c 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -86,7 +86,7 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, if (!isCancelled()) { // Wait for icon generation processes to finish - if (m_qmlPuppetProcesses.isEmpty()) { + if (m_qmlPuppetProcesses.empty()) { finalizeQuick3DImport(); } else { m_qmlPuppetCount = m_qmlPuppetProcesses.size(); @@ -186,10 +186,12 @@ void ItemLibraryAssetImporter::processFinished(int exitCode, QProcess::ExitStatu auto process = qobject_cast(sender()); if (process) { - m_qmlPuppetProcesses.remove(process); - process->deleteLater(); + m_qmlPuppetProcesses.erase( + std::remove_if(m_qmlPuppetProcesses.begin(), + m_qmlPuppetProcesses.end(), + [&](const auto &entry) { return entry.get() == process; })); const QString progressTitle = tr("Generating icons."); - if (m_qmlPuppetProcesses.isEmpty()) { + if (m_qmlPuppetProcesses.empty()) { notifyProgress(100, progressTitle); finalizeQuick3DImport(); } else { @@ -215,7 +217,6 @@ void ItemLibraryAssetImporter::reset() m_tempDir = new QTemporaryDir; m_importFiles.clear(); m_overwrittenImports.clear(); - qDeleteAll(m_qmlPuppetProcesses); m_qmlPuppetProcesses.clear(); m_qmlPuppetCount = 0; #endif @@ -498,16 +499,21 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic puppetCreator.createQml2PuppetExecutableIfMissing(); QStringList puppetArgs; puppetArgs << "--rendericon" << QString::number(size) << iconFile << iconSource; - QProcess *process = puppetCreator.createPuppetProcess( - "custom", {}, this, "", SLOT(processFinished(int, QProcess::ExitStatus)), puppetArgs); + QProcessUniquePointer process = puppetCreator.createPuppetProcess( + "custom", + {}, + this, + std::function(), + [&](int exitCode, QProcess::ExitStatus exitStatus) { + processFinished(exitCode, exitStatus); + }, + puppetArgs); if (process->waitForStarted(5000)) { - connect(process, QOverload::of(&QProcess::finished), - process, &QProcess::deleteLater); - m_qmlPuppetProcesses << process; + m_qmlPuppetProcesses.push_back(std::move(process)); return true; } else { - delete process; + process.reset(); } } return false; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 4bdccad6af9..59211441519 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -24,14 +24,16 @@ ****************************************************************************/ #pragma once +#include "import.h" + +#include + #include -#include -#include #include #include +#include #include - -#include "import.h" +#include QT_BEGIN_NAMESPACE class QSSGAssetImportManager; @@ -99,7 +101,7 @@ private: bool m_cancelled = false; QString m_importPath; QTemporaryDir *m_tempDir = nullptr; - QSet m_qmlPuppetProcesses; + std::vector m_qmlPuppetProcesses; int m_qmlPuppetCount = 0; }; } // QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index 63b73cbdff9..78e56d46cdd 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -62,6 +62,7 @@ class RemovePropertiesCommand; class CompleteComponentCommand; class InformationContainer; class TokenCommand; +class ConnectionManagerInterface; class QMLDESIGNERCORE_EXPORT NodeInstanceView : public AbstractView, public NodeInstanceClientInterface { @@ -72,7 +73,7 @@ class QMLDESIGNERCORE_EXPORT NodeInstanceView : public AbstractView, public Node public: using Pointer = QWeakPointer; - explicit NodeInstanceView(QObject *parent = nullptr, NodeInstanceServerInterface::RunModus runModus = NodeInstanceServerInterface::NormalModus); + explicit NodeInstanceView(ConnectionManagerInterface &connectionManager); ~NodeInstanceView() override; void modelAttached(Model *model) override; @@ -94,7 +95,7 @@ public: void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; void nodeSourceChanged(const ModelNode &modelNode, const QString &newNodeSource) override; - + void capturedData(const CapturedDataCommand &capturedData) override; void currentStateChanged(const ModelNode &node) override; QList instances() const; @@ -142,6 +143,7 @@ protected: void timerEvent(QTimerEvent *event) override; private: // functions + std::unique_ptr createNodeInstanceServerProxy(); void activateState(const NodeInstance &instance); void activateBaseState(); @@ -161,9 +163,8 @@ private: // functions void setStateInstance(const NodeInstance &stateInstance); void clearStateInstance(); - NodeInstanceServerInterface *nodeInstanceServer() const; - QMultiHash informationChanged(const QVector &containerVector); - + QMultiHash informationChanged( + const QVector &containerVector); CreateSceneCommand createCreateSceneCommand(); ClearSceneCommand createClearSceneCommand() const; @@ -196,16 +197,15 @@ private: // functions // puppet to creator command handlers void handlePuppetKeyPress(int key, Qt::KeyboardModifiers modifiers); +private: NodeInstance m_rootNodeInstance; NodeInstance m_activeStateInstance; - QHash m_nodeInstanceHash; QHash m_statePreviewImage; - - QPointer m_nodeInstanceServer; + ConnectionManagerInterface &m_connectionManager; + std::unique_ptr m_nodeInstanceServer; QImage m_baseStatePreviewImage; QElapsedTimer m_lastCrashTime; - NodeInstanceServerInterface::RunModus m_runModus; ProjectExplorer::Target *m_currentTarget = nullptr; int m_restartProcessTimerId; RewriterTransaction m_puppetTransaction; diff --git a/src/plugins/qmldesigner/designercore/include/viewmanager.h b/src/plugins/qmldesigner/designercore/include/viewmanager.h index 3862d541871..cc6f5d15d04 100644 --- a/src/plugins/qmldesigner/designercore/include/viewmanager.h +++ b/src/plugins/qmldesigner/designercore/include/viewmanager.h @@ -122,7 +122,7 @@ private: // functions QList> standardViews() const; private: // variables - ViewManagerData *d; + std::unique_ptr d; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp new file mode 100644 index 00000000000..0d4540b37ff --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "baseconnectionmanager.h" +#include "endpuppetcommand.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" + +#include + +namespace QmlDesigner { + +void BaseConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &, + ProjectExplorer::Target *) +{ + m_nodeInstanceServerProxy = nodeInstanceServerProxy; + m_isActive = true; +} + +void BaseConnectionManager::shutDown() +{ + m_isActive = false; + + writeCommand(QVariant::fromValue(EndPuppetCommand())); + + m_nodeInstanceServerProxy = nullptr; +} + +bool BaseConnectionManager::isActive() const +{ + return m_isActive; +} + +void BaseConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() {} + +void BaseConnectionManager::processFinished() +{ + processFinished(-1, QProcess::CrashExit); +} + +void BaseConnectionManager::writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter) +{ + if (ioDevice) { + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_4_8); + out << quint32(0); + out << quint32(commandCounter); + out << command; + out.device()->seek(0); + out << quint32(static_cast(block.size()) - sizeof(quint32)); + + ioDevice->write(block); + } +} + +void BaseConnectionManager::dispatchCommand(const QVariant &command, Connection &) +{ + if (!isActive()) + return; + + m_nodeInstanceServerProxy->dispatchCommand(command); +} + +void BaseConnectionManager::readDataStream(Connection &connection) +{ + QList commandList; + + while (!connection.socket->atEnd()) { + if (connection.socket->bytesAvailable() < int(sizeof(quint32))) + break; + + QDataStream in(connection.socket.get()); + in.setVersion(QDataStream::Qt_4_8); + + if (connection.blockSize == 0) + in >> connection.blockSize; + + if (connection.socket->bytesAvailable() < connection.blockSize) + break; + + quint32 commandCounter = 0; + in >> commandCounter; + bool commandLost = !((connection.lastReadCommandCounter == 0 && commandCounter == 0) + || (connection.lastReadCommandCounter + 1 == commandCounter)); + if (commandLost) + qDebug() << "server command lost: " << connection.lastReadCommandCounter << commandCounter; + connection.lastReadCommandCounter = commandCounter; + + QVariant command; + in >> command; + connection.blockSize = 0; + + commandList.append(command); + } + + for (const QVariant &command : commandList) + dispatchCommand(command, connection); +} +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h new file mode 100644 index 00000000000..83a41a2bd89 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "connectionmanagerinterface.h" + +#include + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class AbstractView; +class NodeInstanceServerProxy; + +class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public ConnectionManagerInterface +{ + Q_OBJECT + +public: + BaseConnectionManager() = default; + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + void shutDown() override; + + bool isActive() const; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + virtual void showCannotConnectToPuppetWarningAndSwitchToEditMode(); + using ConnectionManagerInterface::processFinished; + void processFinished(); + void writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter); + void readDataStream(Connection &connection); + + NodeInstanceServerProxy *nodeInstanceServerProxy() const { return m_nodeInstanceServerProxy; } + +private: + NodeInstanceServerProxy *m_nodeInstanceServerProxy{}; + bool m_isActive = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp new file mode 100644 index 00000000000..b6e81f1ffeb --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "capturingconnectionmanager.h" + +#include + +namespace QmlDesigner { + +void CapturingConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + InteractiveConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf( + "-capture-puppet-stream"); + if (indexOfCapturePuppetStream > 0) { + m_captureFileForTest.setFileName( + QCoreApplication::arguments().at(indexOfCapturePuppetStream + 1)); + bool isOpen = m_captureFileForTest.open(QIODevice::WriteOnly); + qDebug() << "file is open: " << isOpen; + } +} + +void CapturingConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (m_captureFileForTest.isOpen()) { + m_captureFileForTest.close(); + Core::AsynchronousMessageBox::warning( + tr("QML Emulation Layer (QML Puppet) Crashed"), + tr("You are recording a puppet stream and the emulations layer crashed. " + "It is recommended to reopen the Qt Quick Designer and start again.")); + } + + InteractiveConnectionManager::processFinished(exitCode, exitStatus); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h new file mode 100644 index 00000000000..de63da87fcf --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class CapturingConnectionManager : public InteractiveConnectionManager +{ +public: + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + + void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + +private: + QFile m_captureFileForTest; +}; + +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp new file mode 100644 index 00000000000..edf7f20b9c1 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "connectionmanager.h" +#include "endpuppetcommand.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" +#include "puppetcreator.h" + +#ifndef QMLDESIGNER_TEST +#include +#endif + +#include + +#include +#include +#include + +namespace QmlDesigner { + +ConnectionManager::ConnectionManager() = default; + +ConnectionManager::~ConnectionManager() = default; + +void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + m_localServer = std::make_unique(); + QString socketToken(QUuid::createUuid().toString()); + m_localServer->listen(socketToken); + m_localServer->setMaxPendingConnections(3); + + NodeInstanceView *nodeInstanceView = nodeInstanceServerProxy->nodeInstanceView(); + PuppetCreator puppetCreator(target, nodeInstanceView->model()); + puppetCreator.setQrcMappingString(qrcMappingString); + + puppetCreator.createQml2PuppetExecutableIfMissing(); + + for (Connection &connection : m_connections) { + connection.qmlPuppetProcess = puppetCreator.createPuppetProcess( + connection.mode, + socketToken, + nodeInstanceView, + [&] { printProcessOutput(connection.qmlPuppetProcess.get(), connection.name); }, + [&](int exitCode, QProcess::ExitStatus exitStatus) { + processFinished(exitCode, exitStatus); + }); + + const int second = 1000; + int waitConstant = 8 * second; + + if (!connection.qmlPuppetProcess->waitForStarted(waitConstant)) { + closeSocketsAndKillProcesses(); + showCannotConnectToPuppetWarningAndSwitchToEditMode(); + return; + } + + waitConstant /= 2; + + bool connectedToPuppet = true; + if (!m_localServer->hasPendingConnections()) + connectedToPuppet = m_localServer->waitForNewConnection(waitConstant); + + if (connectedToPuppet) { + connection.socket.reset(m_localServer->nextPendingConnection()); + QObject::connect(connection.socket.get(), &QIODevice::readyRead, [&] { + readDataStream(connection); + }); + } else { + closeSocketsAndKillProcesses(); + showCannotConnectToPuppetWarningAndSwitchToEditMode(); + return; + } + } + + m_localServer->close(); + + connect(this, + &ConnectionManager::processCrashed, + nodeInstanceServerProxy, + &NodeInstanceServerProxy::processCrashed); +} + +void ConnectionManager::shutDown() +{ + BaseConnectionManager::shutDown(); + + closeSocketsAndKillProcesses(); + + m_localServer.reset(); + + for (Connection &connection : m_connections) + connection.clear(); +} + +void ConnectionManager::writeCommand(const QVariant &command) +{ + for (Connection &connection : m_connections) + writeCommandToIODevice(command, connection.socket.get(), m_writeCommandCounter); + + m_writeCommandCounter++; +} + +void ConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + auto finishedProcess = qobject_cast(sender()); + if (finishedProcess) + qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") + << finishedProcess->arguments() << "exitCode:" << exitCode; + else + qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") + << sender() << "exitCode:" << exitCode; + + writeCommand(QVariant::fromValue(EndPuppetCommand())); + + closeSocketsAndKillProcesses(); + + if (exitStatus == QProcess::CrashExit) + emit processCrashed(); +} + +void ConnectionManager::closeSocketsAndKillProcesses() +{ + for (Connection &connection : m_connections) { + if (connection.socket) { + disconnect(connection.socket.get()); + disconnect(connection.qmlPuppetProcess.get()); + connection.socket->waitForBytesWritten(1000); + connection.socket->abort(); + } + + if (connection.qmlPuppetProcess) { + QTimer::singleShot(3000, connection.qmlPuppetProcess.get(), &QProcess::terminate); + QTimer::singleShot(6000, connection.qmlPuppetProcess.get(), &QProcess::kill); + } + + connection.clear(); + } +} + +void ConnectionManager::printProcessOutput(QProcess *process, const QString &connectionName) +{ + while (process && process->canReadLine()) { + QByteArray line = process->readLine(); + line.chop(1); + qDebug().nospace() << connectionName << " Puppet: " << line; + } + qDebug() << "\n"; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h new file mode 100644 index 00000000000..4bb85b5ff25 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "baseconnectionmanager.h" +#include "nodeinstanceserverinterface.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QLocalServer; +class QLocalSocket; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class QMLDESIGNERCORE_EXPORT ConnectionManager : public BaseConnectionManager +{ + Q_OBJECT + +public: + ConnectionManager(); + ~ConnectionManager() override; + enum PuppetStreamType { FirstPuppetStream, SecondPuppetStream, ThirdPuppetStream }; + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + void shutDown() override; + + void writeCommand(const QVariant &command) override; + +signals: + void processCrashed(); + +protected: + using BaseConnectionManager::processFinished; + void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + +private: + void printProcessOutput(QProcess *process, const QString &connectionName); + void closeSocketsAndKillProcesses(); + +protected: + std::vector m_connections; + quint32 m_writeCommandCounter = 0; + +private: + std::unique_ptr m_localServer; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp new file mode 100644 index 00000000000..2aa7df6be62 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "connectionmanagerinterface.h" + +#include +#include + +namespace QmlDesigner { + +ConnectionManagerInterface::~ConnectionManagerInterface() = default; + +ConnectionManagerInterface::Connection::~Connection() = default; + +ConnectionManagerInterface::Connection::Connection(const QString &name, const QString &mode) + : name{name} + , mode{mode} + , timer{std::make_unique()} +{} + +ConnectionManagerInterface::Connection::Connection(Connection &&connection) = default; + +void ConnectionManagerInterface::Connection::clear() +{ + qmlPuppetProcess.reset(); + socket.reset(); + blockSize = 0; + lastReadCommandCounter = 0; + timer->stop(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h new file mode 100644 index 00000000000..f3d8e6293e4 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qprocessuniqueptr.h" +#include + + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class NodeInstanceServerProxy; + +class QMLDESIGNERCORE_EXPORT ConnectionManagerInterface +{ +public: + class QMLDESIGNERCORE_EXPORT Connection final + { + public: + Connection(const QString &name, const QString &mode); + Connection(Connection &&connection); + + ~Connection(); + + void clear(); + + public: + QString name; + QString mode; + QProcessUniquePointer qmlPuppetProcess; + std::unique_ptr socket; + quint32 blockSize = 0; + quint32 lastReadCommandCounter = 0; + std::unique_ptr timer; + }; + + virtual ~ConnectionManagerInterface(); + + virtual void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) + = 0; + virtual void shutDown() = 0; + + virtual void writeCommand(const QVariant &command) = 0; + +protected: + virtual void dispatchCommand(const QVariant &command, Connection &connection) = 0; + virtual void processFinished(int exitCode, QProcess::ExitStatus exitStatus) = 0; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/instances.pri b/src/plugins/qmldesigner/designercore/instances/instances.pri index 9856e1243f3..7d6b71a080f 100644 --- a/src/plugins/qmldesigner/designercore/instances/instances.pri +++ b/src/plugins/qmldesigner/designercore/instances/instances.pri @@ -1,14 +1,25 @@ INCLUDEPATH += $$PWD/ -HEADERS += $$PWD/../include/nodeinstance.h -HEADERS += $$PWD/nodeinstanceserverproxy.h -HEADERS += $$PWD/puppetcreator.h -HEADERS += $$PWD/puppetbuildprogressdialog.h +HEADERS += $$PWD/../include/nodeinstance.h \ + $$PWD/baseconnectionmanager.h \ + $$PWD/capturingconnectionmanager.h \ + $$PWD/connectionmanager.h \ + $$PWD/connectionmanagerinterface.h \ + $$PWD/interactiveconnectionmanager.h \ + $$PWD/nodeinstanceserverproxy.h \ + $$PWD/puppetcreator.h \ + $$PWD/puppetbuildprogressdialog.h \ + $$PWD/qprocessuniqueptr.h -SOURCES += $$PWD/nodeinstanceserverproxy.cpp -SOURCES += $$PWD/nodeinstance.cpp -SOURCES += $$PWD/nodeinstanceview.cpp -SOURCES += $$PWD/puppetcreator.cpp -SOURCES += $$PWD/puppetbuildprogressdialog.cpp +SOURCES += $$PWD/nodeinstanceserverproxy.cpp \ + $$PWD/baseconnectionmanager.cpp \ + $$PWD/capturingconnectionmanager.cpp \ + $$PWD/connectionmanager.cpp \ + $$PWD/connectionmanagerinterface.cpp \ + $$PWD/interactiveconnectionmanager.cpp \ + $$PWD/nodeinstance.cpp \ + $$PWD/nodeinstanceview.cpp \ + $$PWD/puppetcreator.cpp \ + $$PWD/puppetbuildprogressdialog.cpp FORMS += $$PWD/puppetbuildprogressdialog.ui diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp new file mode 100644 index 00000000000..4b6c73f19e4 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "interactiveconnectionmanager.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" + +#include + +#include + +#include + +namespace QmlDesigner { + +InteractiveConnectionManager::InteractiveConnectionManager() +{ + m_connections.emplace_back("Editor", "editormode"); + m_connections.emplace_back("Render", "rendermode"); + m_connections.emplace_back("Preview", "previewmode"); +} + +void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + ConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); + int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); + for (Connection &connection : m_connections) + connection.timer->setInterval(timeOutTime); + + if (QmlDesignerPlugin::instance() + ->settings() + .value(DesignerSettingsKey::DEBUG_PUPPET) + .toString() + .isEmpty()) { + for (Connection &connection : m_connections) { + QObject::connect(connection.timer.get(), &QTimer::timeout, [&]() { + puppetTimeout(connection); + }); + } + } +} + +void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() +{ + Core::AsynchronousMessageBox::warning( + tr("Cannot Connect to QML Emulation Layer (QML Puppet)"), + tr("The executable of the QML emulation layer (QML Puppet) may not be responding. " + "Switching to another kit might help.")); + + QmlDesignerPlugin::instance()->switchToTextModeDeferred(); + nodeInstanceServerProxy()->nodeInstanceView()->emitDocumentMessage( + tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); +} + +void InteractiveConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) +{ + static const int puppetAliveCommandType = QMetaType::type("PuppetAliveCommand"); + + if (command.userType() == puppetAliveCommandType) { + puppetAlive(connection); + } else { + BaseConnectionManager::dispatchCommand(command, connection); + } +} + +void InteractiveConnectionManager::puppetTimeout(Connection &connection) +{ + if (connection.socket && connection.socket->waitForReadyRead(10)) { + connection.timer->stop(); + connection.timer->start(); + return; + } + + processFinished(); +} + +void InteractiveConnectionManager::puppetAlive(Connection &connection) +{ + connection.timer->stop(); + connection.timer->start(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h new file mode 100644 index 00000000000..1946620a432 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "connectionmanager.h" + +namespace QmlDesigner { + +class InteractiveConnectionManager : public ConnectionManager +{ +public: + InteractiveConnectionManager(); + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + + void showCannotConnectToPuppetWarningAndSwitchToEditMode() override; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + void puppetTimeout(Connection &connection); + void puppetAlive(Connection &connection); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp index 8db9aa3a98e..026d5872339 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp @@ -25,6 +25,7 @@ #include "nodeinstanceserverproxy.h" +#include "connectionmanagerinterface.h" #include "puppetcreator.h" #include @@ -60,218 +61,55 @@ #include #include -#include #include +#include #include -#ifndef QMLDESIGNER_TEST -#include -#endif - -#include -#include -#include #include +#include #include -#include +#include #include +#include #include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include namespace QmlDesigner { -static Q_LOGGING_CATEGORY(instanceViewBenchmark, "qtc.nodeinstances.init", QtWarningMsg) - -void NodeInstanceServerProxy::showCannotConnectToPuppetWarningAndSwitchToEditMode() -{ -#ifndef QMLDESIGNER_TEST - Core::AsynchronousMessageBox::warning(tr("Cannot Connect to QML Emulation Layer (QML Puppet)"), - tr("The executable of the QML emulation layer (QML Puppet) may not be responding. " - "Switching to another kit might help.")); - - QmlDesignerPlugin::instance()->switchToTextModeDeferred(); - m_nodeInstanceView->emitDocumentMessage(tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); -#endif - -} +static Q_LOGGING_CATEGORY(instanceViewBenchmark, "qtc.nodeinstances.init", QtWarningMsg); NodeInstanceServerProxy::NodeInstanceServerProxy(NodeInstanceView *nodeInstanceView, - RunModus runModus, - ProjectExplorer::Target *target) - : NodeInstanceServerInterface(nodeInstanceView), - m_localServer(new QLocalServer(this)), - m_nodeInstanceView(nodeInstanceView), - m_runModus(runModus) + ProjectExplorer::Target *target, + ConnectionManagerInterface &connectionManager) + : m_nodeInstanceView(nodeInstanceView) + , m_connectionManager{connectionManager} + { if (instanceViewBenchmark().isInfoEnabled()) m_benchmarkTimer.start(); - QString socketToken(QUuid::createUuid().toString()); - m_localServer->listen(socketToken); - m_localServer->setMaxPendingConnections(3); + m_connectionManager.setUp(this, qrcMappingString(), target); - PuppetCreator puppetCreator(target, nodeInstanceView->model()); - puppetCreator.setQrcMappingString(qrcMappingString()); - - puppetCreator.createQml2PuppetExecutableIfMissing(); - - m_qmlPuppetEditorProcess = puppetCreator.createPuppetProcess("editormode", - socketToken, - this, - SLOT(printEditorProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - - if (runModus == NormalModus) { - m_qmlPuppetRenderProcess = puppetCreator.createPuppetProcess("rendermode", - socketToken, - this, - SLOT(printRenderProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - m_qmlPuppetPreviewProcess = puppetCreator.createPuppetProcess("previewmode", - socketToken, - this, - SLOT(printPreviewProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - } - - const int second = 1000; - const int waitConstant = 8 * second; - if (m_qmlPuppetEditorProcess->waitForStarted(waitConstant)) { - connect(m_qmlPuppetEditorProcess.data(), QOverload::of(&QProcess::finished), - m_qmlPuppetEditorProcess.data(), &QProcess::deleteLater); - qCInfo(instanceViewBenchmark) << "puppets started:" << m_benchmarkTimer.elapsed(); - - if (runModus == NormalModus) { - m_qmlPuppetPreviewProcess->waitForStarted(waitConstant / 2); - connect(m_qmlPuppetPreviewProcess.data(), QOverload::of(&QProcess::finished), - m_qmlPuppetPreviewProcess.data(), &QProcess::deleteLater); - - m_qmlPuppetRenderProcess->waitForStarted(waitConstant / 2); - connect(m_qmlPuppetRenderProcess.data(), QOverload::of(&QProcess::finished), - m_qmlPuppetRenderProcess.data(), &QProcess::deleteLater); - } - - bool connectedToPuppet = true; - - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - if (connectedToPuppet) { - m_firstSocket = m_localServer->nextPendingConnection(); - connect(m_firstSocket.data(), &QIODevice::readyRead, this, - &NodeInstanceServerProxy::readFirstDataStream); - - if (runModus == NormalModus) { - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - if (connectedToPuppet) { - m_secondSocket = m_localServer->nextPendingConnection(); - connect(m_secondSocket.data(), &QIODevice::readyRead, this, &NodeInstanceServerProxy::readSecondDataStream); - - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - qCInfo(instanceViewBenchmark) << "puppets connected:" << m_benchmarkTimer.elapsed(); - if (connectedToPuppet) { - m_thirdSocket = m_localServer->nextPendingConnection(); - connect(m_thirdSocket.data(), &QIODevice::readyRead, this, &NodeInstanceServerProxy::readThirdDataStream); - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - } - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - - m_localServer->close(); - - - int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf("-capture-puppet-stream"); - if (indexOfCapturePuppetStream > 0) { - m_captureFileForTest.setFileName(QCoreApplication::arguments().at(indexOfCapturePuppetStream + 1)); - bool isOpen = m_captureFileForTest.open(QIODevice::WriteOnly); - qDebug() << "file is open: " << isOpen; - } - -#ifndef QMLDESIGNER_TEST - DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); - int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); - m_firstTimer.setInterval(timeOutTime); - m_secondTimer.setInterval(timeOutTime); - m_thirdTimer.setInterval(timeOutTime); - - if (QmlDesignerPlugin::instance()->settings().value(DesignerSettingsKey:: - DEBUG_PUPPET).toString().isEmpty()) { - - connect(&m_firstTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(FirstPuppetStream); }); - connect(&m_secondTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(SecondPuppetStream); }); - connect(&m_thirdTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(ThirdPuppetStream); }); - } -#endif + qCInfo(instanceViewBenchmark) << "puppets setup:" << m_benchmarkTimer.elapsed(); } NodeInstanceServerProxy::~NodeInstanceServerProxy() { - m_destructing = true; - - disconnect(this, SLOT(processFinished(int,QProcess::ExitStatus))); - - writeCommand(QVariant::fromValue(EndPuppetCommand())); - - if (m_firstSocket) { - m_firstSocket->waitForBytesWritten(1000); - m_firstSocket->abort(); - } - - if (m_secondSocket) { - m_secondSocket->waitForBytesWritten(1000); - m_secondSocket->abort(); - } - - if (m_thirdSocket) { - m_thirdSocket->waitForBytesWritten(1000); - m_thirdSocket->abort(); - } - - if (m_qmlPuppetEditorProcess) { - QTimer::singleShot(3000, m_qmlPuppetEditorProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetEditorProcess.data(), &QProcess::kill); - } - - if (m_qmlPuppetPreviewProcess) { - QTimer::singleShot(3000, m_qmlPuppetPreviewProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetPreviewProcess.data(), &QProcess::kill); - } - - if (m_qmlPuppetRenderProcess) { - QTimer::singleShot(3000, m_qmlPuppetRenderProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetRenderProcess.data(), &QProcess::kill); - } + m_connectionManager.shutDown(); } -void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStreamType puppetStreamType) +void NodeInstanceServerProxy::dispatchCommand(const QVariant &command) { static const int informationChangedCommandType = QMetaType::type("InformationChangedCommand"); static const int valuesChangedCommandType = QMetaType::type("ValuesChangedCommand"); @@ -280,15 +118,10 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr static const int childrenChangedCommandType = QMetaType::type("ChildrenChangedCommand"); static const int statePreviewImageChangedCommandType = QMetaType::type("StatePreviewImageChangedCommand"); static const int componentCompletedCommandType = QMetaType::type("ComponentCompletedCommand"); - static const int synchronizeCommandType = QMetaType::type("SynchronizeCommand"); static const int tokenCommandType = QMetaType::type("TokenCommand"); static const int debugOutputCommandType = QMetaType::type("DebugOutputCommand"); - static const int puppetAliveCommandType = QMetaType::type("PuppetAliveCommand"); static const int changeSelectionCommandType = QMetaType::type("ChangeSelectionCommand"); - static const int puppetToCreatorCommand = QMetaType::type("PuppetToCreatorCommand"); - - if (m_destructing) - return; + static const int puppetToCreatorCommandType = QMetaType::type("PuppetToCreatorCommand"); qCInfo(instanceViewBenchmark) << "dispatching command" << command.userType() << command.typeName(); if (command.userType() == informationChangedCommandType) { @@ -311,13 +144,8 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr nodeInstanceClient()->debugOutput(command.value()); } else if (command.userType() == changeSelectionCommandType) { nodeInstanceClient()->selectionChanged(command.value()); - } else if (command.userType() == puppetToCreatorCommand) { + } else if (command.userType() == puppetToCreatorCommandType) { nodeInstanceClient()->handlePuppetToCreatorCommand(command.value()); - } else if (command.userType() == puppetAliveCommandType) { - puppetAlive(puppetStreamType); - } else if (command.userType() == synchronizeCommandType) { - SynchronizeCommand synchronizeCommand = command.value(); - m_synchronizeId = synchronizeCommand.synchronizeId(); } else { Q_ASSERT(false); } @@ -327,33 +155,13 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr NodeInstanceClientInterface *NodeInstanceServerProxy::nodeInstanceClient() const { - return m_nodeInstanceView.data(); -} - -void NodeInstanceServerProxy::puppetAlive(NodeInstanceServerProxy::PuppetStreamType puppetStreamType) -{ - switch (puppetStreamType) { - case FirstPuppetStream: - m_firstTimer.stop(); - m_firstTimer.start(); - break; - case SecondPuppetStream: - m_secondTimer.stop(); - m_secondTimer.start(); - break; - case ThirdPuppetStream: - m_thirdTimer.stop(); - m_thirdTimer.start(); - break; - default: - break; - } + return m_nodeInstanceView; } QString NodeInstanceServerProxy::qrcMappingString() const { - if (m_nodeInstanceView && m_nodeInstanceView.data()->model()) { - RewriterView *rewriterView = m_nodeInstanceView.data()->model()->rewriterView(); + if (m_nodeInstanceView && m_nodeInstanceView->model()) { + RewriterView *rewriterView = m_nodeInstanceView->model()->rewriterView(); if (rewriterView) { QString mappingString; @@ -374,265 +182,9 @@ QString NodeInstanceServerProxy::qrcMappingString() const return QString(); } -void NodeInstanceServerProxy::processFinished() -{ - processFinished(-1, QProcess::CrashExit); -} - -void NodeInstanceServerProxy::puppetTimeout(PuppetStreamType puppetStreamType) -{ - switch (puppetStreamType) { - case FirstPuppetStream: - if (m_firstSocket->waitForReadyRead(10)) { - m_firstTimer.stop(); - m_firstTimer.start(); - return; - } - break; - case SecondPuppetStream: - if (m_secondSocket->waitForReadyRead(10)) { - m_secondTimer.stop(); - m_secondTimer.start(); - return; - } - break; - case ThirdPuppetStream: - if (m_thirdSocket->waitForReadyRead(10)) { - m_thirdTimer.stop(); - m_thirdTimer.start(); - return; - } - break; - default: - break; - } - - processFinished(); -} - -static void writeCommandToIODecive(const QVariant &command, QIODevice *ioDevice, unsigned int commandCounter) -{ - if (ioDevice) { - QByteArray block; - QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_4_8); - out << quint32(0); - out << quint32(commandCounter); - out << command; - out.device()->seek(0); - out << quint32(block.size() - sizeof(quint32)); - - ioDevice->write(block); - } -} - void NodeInstanceServerProxy::writeCommand(const QVariant &command) { - writeCommandToIODecive(command, m_firstSocket.data(), m_writeCommandCounter); - writeCommandToIODecive(command, m_secondSocket.data(), m_writeCommandCounter); - writeCommandToIODecive(command, m_thirdSocket.data(), m_writeCommandCounter); - - if (m_captureFileForTest.isWritable()) { - qDebug() << "Write stream to file: " << m_captureFileForTest.fileName(); - writeCommandToIODecive(command, &m_captureFileForTest, m_writeCommandCounter); - qDebug() << "\twrite file: " << m_captureFileForTest.pos(); - } - - m_writeCommandCounter++; - if (m_runModus == TestModus) { - static int synchronizeId = 0; - synchronizeId++; - SynchronizeCommand synchronizeCommand(synchronizeId); - - writeCommandToIODecive(QVariant::fromValue(synchronizeCommand), m_firstSocket.data(), m_writeCommandCounter); - m_writeCommandCounter++; - - while (m_firstSocket->waitForReadyRead(100)) { - readFirstDataStream(); - if (m_synchronizeId == synchronizeId) - return; - } - } -} - -void NodeInstanceServerProxy::processFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - auto finishedProcess = qobject_cast(sender()); - if (finishedProcess) - qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") << finishedProcess->arguments() << "exitCode:" << exitCode; - else - qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") << sender() << "exitCode:" << exitCode; - - if (m_captureFileForTest.isOpen()) { - m_captureFileForTest.close(); - Core::AsynchronousMessageBox::warning(tr("QML Emulation Layer (QML Puppet) Crashed"), - tr("You are recording a puppet stream and the emulations layer crashed. " - "It is recommended to reopen the Qt Quick Designer and start again.")); - } - - - writeCommand(QVariant::fromValue(EndPuppetCommand())); - - if (m_firstSocket) { - m_firstSocket->waitForBytesWritten(1000); - m_firstSocket->abort(); - } - - if (m_secondSocket) { - m_secondSocket->waitForBytesWritten(1000); - m_secondSocket->abort(); - } - - if (m_thirdSocket) { - m_thirdSocket->waitForBytesWritten(1000); - m_thirdSocket->abort(); - } - - if (exitStatus == QProcess::CrashExit) - emit processCrashed(); -} - - -void NodeInstanceServerProxy::readFirstDataStream() -{ - QList commandList; - - while (!m_firstSocket->atEnd()) { - if (m_firstSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_firstSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_firstBlockSize == 0) - in >> m_firstBlockSize; - - if (m_firstSocket->bytesAvailable() < m_firstBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_firstLastReadCommandCounter == 0 && commandCounter == 0) || (m_firstLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_firstLastReadCommandCounter << commandCounter; - m_firstLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_firstBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, FirstPuppetStream); - } -} - -void NodeInstanceServerProxy::readSecondDataStream() -{ - QList commandList; - - while (!m_secondSocket->atEnd()) { - if (m_secondSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_secondSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_secondBlockSize == 0) - in >> m_secondBlockSize; - - if (m_secondSocket->bytesAvailable() < m_secondBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_secondLastReadCommandCounter == 0 && commandCounter == 0) || (m_secondLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_secondLastReadCommandCounter << commandCounter; - m_secondLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_secondBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, SecondPuppetStream); - } -} - -void NodeInstanceServerProxy::readThirdDataStream() -{ - QList commandList; - - while (!m_thirdSocket->atEnd()) { - if (m_thirdSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_thirdSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_thirdBlockSize == 0) - in >> m_thirdBlockSize; - - if (m_thirdSocket->bytesAvailable() < m_thirdBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_thirdLastReadCommandCounter == 0 && commandCounter == 0) || (m_thirdLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_thirdLastReadCommandCounter << commandCounter; - m_thirdLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_thirdBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, ThirdPuppetStream); - } -} - -void NodeInstanceServerProxy::printEditorProcessOutput() -{ - while (m_qmlPuppetEditorProcess && m_qmlPuppetEditorProcess->canReadLine()) { - QByteArray line = m_qmlPuppetEditorProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Editor Puppet: " << line; - } - qDebug() << "\n"; -} - -void NodeInstanceServerProxy::printPreviewProcessOutput() -{ - while (m_qmlPuppetPreviewProcess && m_qmlPuppetPreviewProcess->canReadLine()) { - QByteArray line = m_qmlPuppetPreviewProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Preview Puppet: " << line; - } - qDebug() << "\n"; -} - -void NodeInstanceServerProxy::printRenderProcessOutput() -{ - while (m_qmlPuppetRenderProcess && m_qmlPuppetRenderProcess->canReadLine()) { - QByteArray line = m_qmlPuppetRenderProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Render Puppet: " << line; - } - - qDebug() << "\n"; + m_connectionManager.writeCommand(command); } void NodeInstanceServerProxy::createInstances(const CreateInstancesCommand &command) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h index 4b0df0fd9aa..2efc7ea8a46 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h @@ -48,21 +48,17 @@ namespace QmlDesigner { class NodeInstanceClientInterface; class NodeInstanceView; class NodeInstanceClientProxy; +class ConnectionManagerInterface; class NodeInstanceServerProxy : public NodeInstanceServerInterface { + friend class BaseConnectionManager; Q_OBJECT public: - enum PuppetStreamType { - FirstPuppetStream, - SecondPuppetStream, - ThirdPuppetStream, - }; - explicit NodeInstanceServerProxy(NodeInstanceView *nodeInstanceView, - RunModus runModus, - ProjectExplorer::Target *target); + ProjectExplorer::Target *target, + ConnectionManagerInterface &connectionManager); ~NodeInstanceServerProxy() override; void createInstances(const CreateInstancesCommand &command) override; void changeFileUrl(const ChangeFileUrlCommand &command) override; @@ -88,52 +84,22 @@ public: void changeLanguage(const ChangeLanguageCommand &command) override; void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) override; + NodeInstanceView *nodeInstanceView() const { return m_nodeInstanceView; } + + QString qrcMappingString() const; + protected: void writeCommand(const QVariant &command); - void dispatchCommand(const QVariant &command, PuppetStreamType puppetStreamType); + void dispatchCommand(const QVariant &command); NodeInstanceClientInterface *nodeInstanceClient() const; - void puppetAlive(PuppetStreamType puppetStreamType); - QString qrcMappingString() const; signals: void processCrashed(); -private slots: - void processFinished(); - void puppetTimeout(PuppetStreamType puppetStreamType); - void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - void readFirstDataStream(); - void readSecondDataStream(); - void readThirdDataStream(); - - void printEditorProcessOutput(); - void printPreviewProcessOutput(); - void printRenderProcessOutput(); - void showCannotConnectToPuppetWarningAndSwitchToEditMode(); private: - QFile m_captureFileForTest; - QTimer m_firstTimer; - QTimer m_secondTimer; - QTimer m_thirdTimer; - QPointer m_localServer; - QPointer m_firstSocket; - QPointer m_secondSocket; - QPointer m_thirdSocket; - QPointer m_nodeInstanceView; - QPointer m_qmlPuppetEditorProcess; - QPointer m_qmlPuppetPreviewProcess; - QPointer m_qmlPuppetRenderProcess; - quint32 m_firstBlockSize = 0; - quint32 m_secondBlockSize = 0; - quint32 m_thirdBlockSize = 0; - quint32 m_writeCommandCounter = 0; - quint32 m_firstLastReadCommandCounter = 0; - quint32 m_secondLastReadCommandCounter = 0; - quint32 m_thirdLastReadCommandCounter = 0; - RunModus m_runModus; - int m_synchronizeId = -1; + NodeInstanceView *m_nodeInstanceView{}; QElapsedTimer m_benchmarkTimer; - bool m_destructing = false; + ConnectionManagerInterface &m_connectionManager; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index ce6412962dc..8c078e29aa2 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -27,6 +27,7 @@ #include "abstractproperty.h" #include "bindingproperty.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" @@ -41,6 +42,7 @@ #include "clearscenecommand.h" #include "completecomponentcommand.h" #include "componentcompletedcommand.h" +#include "connectionmanagerinterface.h" #include "createinstancescommand.h" #include "createscenecommand.h" #include "debugoutputcommand.h" @@ -124,11 +126,10 @@ namespace QmlDesigner { \sa ~NodeInstanceView, setRenderOffScreen() */ -NodeInstanceView::NodeInstanceView(QObject *parent, NodeInstanceServerInterface::RunModus runModus) - : AbstractView(parent), - m_baseStatePreviewImage(QSize(100, 100), QImage::Format_ARGB32), - m_runModus(runModus), - m_restartProcessTimerId(0) +NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager) + : m_connectionManager(connectionManager) + , m_baseStatePreviewImage(QSize(100, 100), QImage::Format_ARGB32) + , m_restartProcessTimerId(0) { m_baseStatePreviewImage.fill(0xFFFFFF); } @@ -140,7 +141,6 @@ NodeInstanceView::NodeInstanceView(QObject *parent, NodeInstanceServerInterface: NodeInstanceView::~NodeInstanceView() { removeAllInstanceNodeRelationships(); - delete nodeInstanceServer(); m_currentTarget = nullptr; } @@ -191,14 +191,16 @@ bool static parentTakesOverRendering(const ModelNode &modelNode) void NodeInstanceView::modelAttached(Model *model) { AbstractView::modelAttached(model); - auto server = new NodeInstanceServerProxy(this, m_runModus, m_currentTarget); - m_nodeInstanceServer = server; + m_nodeInstanceServer = createNodeInstanceServerProxy(); m_lastCrashTime.start(); - connect(server, &NodeInstanceServerProxy::processCrashed, this, &NodeInstanceView::handleCrash); + connect(m_nodeInstanceServer.get(), + &NodeInstanceServerProxy::processCrashed, + this, + &NodeInstanceView::handleCrash); if (!isSkippedRootNode(rootModelNode())) { - nodeInstanceServer()->createScene(createCreateSceneCommand()); - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(model->selectedNodes(this))); + m_nodeInstanceServer->createScene(createCreateSceneCommand()); + m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(model->selectedNodes(this))); } ModelNode stateNode = currentStateNode(); @@ -206,15 +208,14 @@ void NodeInstanceView::modelAttached(Model *model) NodeInstance newStateInstance = instanceForModelNode(stateNode); activateState(newStateInstance); } - } void NodeInstanceView::modelAboutToBeDetached(Model * model) { removeAllInstanceNodeRelationships(); - if (nodeInstanceServer()) { - nodeInstanceServer()->clearScene(createClearSceneCommand()); - delete nodeInstanceServer(); + if (m_nodeInstanceServer) { + m_nodeInstanceServer->clearScene(createClearSceneCommand()); + m_nodeInstanceServer.reset(); } m_statePreviewImage.clear(); m_baseStatePreviewImage = QImage(); @@ -274,15 +275,18 @@ void NodeInstanceView::restartProcess() killTimer(m_restartProcessTimerId); if (model()) { - delete nodeInstanceServer(); + m_nodeInstanceServer.reset(); + m_nodeInstanceServer = createNodeInstanceServerProxy(); - auto server = new NodeInstanceServerProxy(this, m_runModus, m_currentTarget); - m_nodeInstanceServer = server; - connect(server, &NodeInstanceServerProxy::processCrashed, this, &NodeInstanceView::handleCrash); + connect(m_nodeInstanceServer.get(), + &NodeInstanceServerProxy::processCrashed, + this, + &NodeInstanceView::handleCrash); if (!isSkippedRootNode(rootModelNode())) { - nodeInstanceServer()->createScene(createCreateSceneCommand()); - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(model()->selectedNodes(this))); + m_nodeInstanceServer->createScene(createCreateSceneCommand()); + m_nodeInstanceServer->changeSelection( + createChangeSelectionCommand(model()->selectedNodes(this))); } ModelNode stateNode = currentStateNode(); @@ -313,17 +317,19 @@ void NodeInstanceView::nodeCreated(const ModelNode &createdNode) propertyList.append(createdNode.variantProperty("y")); updatePosition(propertyList); - nodeInstanceServer()->createInstances(createCreateInstancesCommand({instance})); - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(createdNode.variantProperties())); - nodeInstanceServer()->completeComponent(createComponentCompleteCommand({instance})); + m_nodeInstanceServer->createInstances(createCreateInstancesCommand({instance})); + m_nodeInstanceServer->changePropertyValues( + createChangeValueCommand(createdNode.variantProperties())); + m_nodeInstanceServer->completeComponent(createComponentCompleteCommand({instance})); } /*! Notifies the view that \a removedNode will be removed. */ void NodeInstanceView::nodeAboutToBeRemoved(const ModelNode &removedNode) { - nodeInstanceServer()->removeInstances(createRemoveInstancesCommand(removedNode)); - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand("Image", removedNode.internalId())); + m_nodeInstanceServer->removeInstances(createRemoveInstancesCommand(removedNode)); + m_nodeInstanceServer->removeSharedMemory( + createRemoveSharedMemoryCommand("Image", removedNode.internalId())); removeInstanceAndSubInstances(removedNode); } @@ -343,11 +349,10 @@ void NodeInstanceView::resetHorizontalAnchors(const ModelNode &modelNode) valueList.append(modelNode.variantProperty("width")); if (!valueList.isEmpty()) - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(valueList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(valueList)); if (!bindingList.isEmpty()) - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(bindingList)); - + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(bindingList)); } void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) @@ -366,10 +371,10 @@ void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) valueList.append(modelNode.variantProperty("height")); if (!valueList.isEmpty()) - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(valueList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(valueList)); if (!bindingList.isEmpty()) - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(bindingList)); + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(bindingList)); } void NodeInstanceView::propertiesAboutToBeRemoved(const QList& propertyList) @@ -388,10 +393,10 @@ void NodeInstanceView::propertiesAboutToBeRemoved(const QList& RemoveInstancesCommand removeInstancesCommand = createRemoveInstancesCommand(nodeList); if (!removeInstancesCommand.instanceIds().isEmpty()) - nodeInstanceServer()->removeInstances(removeInstancesCommand); + m_nodeInstanceServer->removeInstances(removeInstancesCommand); - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand("Image", nodeList)); - nodeInstanceServer()->removeProperties(createRemovePropertiesCommand(nonNodePropertyList)); + m_nodeInstanceServer->removeSharedMemory(createRemoveSharedMemoryCommand("Image", nodeList)); + m_nodeInstanceServer->removeProperties(createRemovePropertiesCommand(nonNodePropertyList)); foreach (const AbstractProperty &property, propertyList) { const PropertyName &name = property.name(); @@ -445,7 +450,7 @@ void NodeInstanceView::nodeTypeChanged(const ModelNode &, const TypeName &, int, void NodeInstanceView::bindingPropertiesChanged(const QList& propertyList, PropertyChangeFlags /*propertyChange*/) { - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(propertyList)); + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(propertyList)); } /*! @@ -460,7 +465,7 @@ void NodeInstanceView::bindingPropertiesChanged(const QList& pr void NodeInstanceView::variantPropertiesChanged(const QList& propertyList, PropertyChangeFlags /*propertyChange*/) { updatePosition(propertyList); - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(propertyList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(propertyList)); } /*! Notifies the view that the property parent of the model node \a node has @@ -476,20 +481,21 @@ void NodeInstanceView::nodeReparented(const ModelNode &node, const NodeAbstractP { if (!isSkippedNode(node)) { updateChildren(newPropertyParent); - nodeInstanceServer()->reparentInstances(createReparentInstancesCommand(node, newPropertyParent, oldPropertyParent)); + m_nodeInstanceServer->reparentInstances( + createReparentInstancesCommand(node, newPropertyParent, oldPropertyParent)); } } void NodeInstanceView::fileUrlChanged(const QUrl &/*oldUrl*/, const QUrl &newUrl) { - nodeInstanceServer()->changeFileUrl(createChangeFileUrlCommand(newUrl)); + m_nodeInstanceServer->changeFileUrl(createChangeFileUrlCommand(newUrl)); } void NodeInstanceView::nodeIdChanged(const ModelNode& node, const QString& /*newId*/, const QString& /*oldId*/) { if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); - nodeInstanceServer()->changeIds(createChangeIdsCommand({instance})); + m_nodeInstanceServer->changeIds(createChangeIdsCommand({instance})); } } @@ -512,7 +518,7 @@ void NodeInstanceView::nodeOrderChanged(const NodeListProperty & listProperty, } } - nodeInstanceServer()->reparentInstances(ReparentInstancesCommand(containerList)); + m_nodeInstanceServer->reparentInstances(ReparentInstancesCommand(containerList)); } void NodeInstanceView::importsChanged(const QList &/*addedImports*/, const QList &/*removedImports*/) @@ -531,16 +537,16 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, if (value.isValid() || name == "invisible") { PropertyValueContainer container(instance.instanceId(), name, value, TypeName()); ChangeAuxiliaryCommand changeAuxiliaryCommand({container}); - nodeInstanceServer()->changeAuxiliaryValues(changeAuxiliaryCommand); + m_nodeInstanceServer->changeAuxiliaryValues(changeAuxiliaryCommand); } else { if (node.hasVariantProperty(name)) { PropertyValueContainer container(instance.instanceId(), name, node.variantProperty(name).value(), TypeName()); ChangeValuesCommand changeValueCommand({container}); - nodeInstanceServer()->changePropertyValues(changeValueCommand); + m_nodeInstanceServer->changePropertyValues(changeValueCommand); } else if (node.hasBindingProperty(name)) { PropertyBindingContainer container(instance.instanceId(), name, node.bindingProperty(name).expression(), TypeName()); ChangeBindingsCommand changeValueCommand({container}); - nodeInstanceServer()->changePropertyBindings(changeValueCommand); + m_nodeInstanceServer->changePropertyBindings(changeValueCommand); } } } @@ -548,9 +554,9 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, const QString languageAsString = value.toString(); if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) multiLanguageAspect->setCurrentLocale(languageAsString); - nodeInstanceServer()->changeLanguage({languageAsString}); + m_nodeInstanceServer->changeLanguage({languageAsString}); } else if (node.isRootNode() && name == "previewSize@Internal") { - nodeInstanceServer()->changePreviewImageSize(value.toSize()); + m_nodeInstanceServer->changePreviewImageSize(value.toSize()); } } @@ -565,10 +571,12 @@ void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); ChangeNodeSourceCommand changeNodeSourceCommand(instance.instanceId(), newNodeSource); - nodeInstanceServer()->changeNodeSource(changeNodeSourceCommand); + m_nodeInstanceServer->changeNodeSource(changeNodeSourceCommand); } } +void NodeInstanceView::capturedData(const CapturedDataCommand &) {} + void NodeInstanceView::currentStateChanged(const ModelNode &node) { NodeInstance newStateInstance = instanceForModelNode(node); @@ -790,12 +798,6 @@ void NodeInstanceView::updatePosition(const QList &propertyList emitInstanceInformationsChange(informationChangeHash); } -NodeInstanceServerInterface *NodeInstanceView::nodeInstanceServer() const -{ - return m_nodeInstanceServer.data(); -} - - NodeInstance NodeInstanceView::loadNode(const ModelNode &node) { NodeInstance instance(NodeInstance::create(node)); @@ -810,12 +812,12 @@ NodeInstance NodeInstanceView::loadNode(const ModelNode &node) void NodeInstanceView::activateState(const NodeInstance &instance) { - nodeInstanceServer()->changeState(ChangeStateCommand(instance.instanceId())); + m_nodeInstanceServer->changeState(ChangeStateCommand(instance.instanceId())); } void NodeInstanceView::activateBaseState() { - nodeInstanceServer()->changeState(ChangeStateCommand(-1)); + m_nodeInstanceServer->changeState(ChangeStateCommand(-1)); } void NodeInstanceView::removeRecursiveChildRelationship(const ModelNode &removedNode) @@ -1248,7 +1250,8 @@ void NodeInstanceView::valuesChanged(const ValuesChangedCommand &command) } } - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand(QStringLiteral("Values"), command.keyNumber())); + m_nodeInstanceServer->removeSharedMemory( + createRemoveSharedMemoryCommand(QStringLiteral("Values"), command.keyNumber())); if (!valuePropertyChangeList.isEmpty()) emitInstancePropertyChange(valuePropertyChangeList); @@ -1456,7 +1459,7 @@ void NodeInstanceView::sendToken(const QString &token, int number, const QVector foreach (const ModelNode &node, nodeVector) instanceIdVector.append(node.internalId()); - nodeInstanceServer()->token(TokenCommand(token, number, instanceIdVector)); + m_nodeInstanceServer->token(TokenCommand(token, number, instanceIdVector)); } void NodeInstanceView::selectionChanged(const ChangeSelectionCommand &command) @@ -1471,7 +1474,7 @@ void NodeInstanceView::selectionChanged(const ChangeSelectionCommand &command) void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) { if (command.type() == PuppetToCreatorCommand::Edit3DToolState) { - if (!m_nodeInstanceServer.isNull()) { + if (m_nodeInstanceServer) { auto data = qvariant_cast(command.data()); if (data.size() == 3) { QString qmlId = data[0].toString(); @@ -1488,25 +1491,30 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand } } +std::unique_ptr NodeInstanceView::createNodeInstanceServerProxy() +{ + return std::make_unique(this, m_currentTarget, m_connectionManager); +} + void NodeInstanceView::selectedNodesChanged(const QList &selectedNodeList, const QList & /*lastSelectedNodeList*/) { - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(selectedNodeList)); + m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList)); } void NodeInstanceView::sendInputEvent(QInputEvent *e) const { - nodeInstanceServer()->inputEvent(InputEventCommand(e)); + m_nodeInstanceServer->inputEvent(InputEventCommand(e)); } void NodeInstanceView::view3DAction(const View3DActionCommand &command) { - nodeInstanceServer()->view3DAction(command); + m_nodeInstanceServer->view3DAction(command); } void NodeInstanceView::edit3DViewResized(const QSize &size) const { - nodeInstanceServer()->update3DViewState(Update3dViewStateCommand(size)); + m_nodeInstanceServer->update3DViewState(Update3dViewStateCommand(size)); } void NodeInstanceView::timerEvent(QTimerEvent *event) diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 7eefa7f1bca..a26c59d7929 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -176,39 +176,46 @@ PuppetCreator::PuppetCreator(ProjectExplorer::Target *target, const Model *model { } -QProcess *PuppetCreator::createPuppetProcess(const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const +QProcessUniquePointer PuppetCreator::createPuppetProcess( + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const { return puppetProcess(qml2PuppetPath(m_availablePuppetType), qmlPuppetDirectory(m_availablePuppetType), puppetMode, socketToken, handlerObject, - outputSlot, - finishSlot, + processOutputCallback, + processFinishCallback, customOptions); } - -QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, - const QString &workingDirectory, - const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const +QProcessUniquePointer PuppetCreator::puppetProcess( + const QString &puppetPath, + const QString &workingDirectory, + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const { - auto puppetProcess = new QProcess; + QProcessUniquePointer puppetProcess{new QProcess}; puppetProcess->setObjectName(puppetMode); puppetProcess->setProcessEnvironment(processEnvironment()); - QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, puppetProcess, &QProcess::kill); - QObject::connect(puppetProcess, SIGNAL(finished(int,QProcess::ExitStatus)), handlerObject, finishSlot); + QObject::connect(QCoreApplication::instance(), + &QCoreApplication::aboutToQuit, + puppetProcess.get(), + &QProcess::kill); + QObject::connect(puppetProcess.get(), + static_cast(&QProcess::finished), + handlerObject, + processFinishCallback); #ifndef QMLDESIGNER_TEST QString forwardOutput = m_designerSettings.value(DesignerSettingsKey:: @@ -218,7 +225,7 @@ QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, #endif if (forwardOutput == puppetMode || forwardOutput == "all") { puppetProcess->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(puppetProcess, SIGNAL(readyRead()), handlerObject, outputSlot); + QObject::connect(puppetProcess.get(), &QProcess::readyRead, handlerObject, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h index bafea8fa3e6..e6083d70147 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h @@ -25,6 +25,8 @@ #pragma once +#include "qprocessuniqueptr.h" + #include #include @@ -53,12 +55,13 @@ public: void createQml2PuppetExecutableIfMissing(); - QProcess *createPuppetProcess(const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions = {}) const; + QProcessUniquePointer createPuppetProcess( + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions = {}) const; void setQrcMappingString(const QString qrcMapping); @@ -82,14 +85,14 @@ protected: bool checkPuppetIsReady(const QString &puppetPath) const; bool qtIsSupported() const; - QProcess *puppetProcess(const QString &puppetPath, - const QString &workingDirectory, - const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const; + QProcessUniquePointer puppetProcess(const QString &puppetPath, + const QString &workingDirectory, + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function processOutputCallback, + std::function processFinishCallback, + const QStringList &customOptions) const; QProcessEnvironment processEnvironment() const; diff --git a/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h b/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h new file mode 100644 index 00000000000..b03c86f772b --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +namespace QmlDesigner { + +class QProcessUniquePointerDeleter +{ +public: + void operator()(QProcess *process) + { + process->disconnect(); + QObject::connect(process, + QOverload::of(&QProcess::finished), + process, + &QProcess::deleteLater); + + process->terminate(); + + process->deleteLater(); + } +}; + +using QProcessUniquePointer = std::unique_ptr; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp index 3173a68abd7..f5dc99bf356 100644 --- a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp +++ b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp @@ -27,23 +27,24 @@ #ifndef QMLDESIGNER_TEST -#include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -59,10 +60,11 @@ static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg) class ViewManagerData { public: + InteractiveConnectionManager connectionManager; QmlModelState savedState; Internal::DebugView debugView; DesignerActionManagerView designerActionManagerView; - NodeInstanceView nodeInstanceView; + NodeInstanceView nodeInstanceView{connectionManager}; ComponentView componentView; Edit3DView edit3DView; FormEditorView formEditorView; @@ -81,7 +83,7 @@ static CrumbleBar *crumbleBar() { } ViewManager::ViewManager() - : d(new ViewManagerData) + : d(std::make_unique()) { d->formEditorView.setGotoErrorCallback([this](int line, int column) { d->textEditorView.gotoCursorPosition(line, column); @@ -92,10 +94,9 @@ ViewManager::ViewManager() ViewManager::~ViewManager() { - foreach (const QPointer &view, d->additionalViews) + for (const QPointer &view : d->additionalViews) delete view.data(); - delete d; } DesignDocument *ViewManager::currentDesignDocument() const diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index dceaf8656df..971bcffb471 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -321,6 +321,17 @@ Project { "instances/puppetdialog.cpp", "instances/puppetdialog.h", "instances/puppetdialog.ui", + "instances/connectionmanagerinterface.cpp", + "instances/connectionmanagerinterface.h", + "instances/baseconnectionmanager.cpp ", + "instances/baseconnectionmanager.h", + "instances/connectionmanager.cpp", + "instances/connectionmanager.h", + "instances/capturingconnectionmanager.cpp", + "instances/capturingconnectionmanager.h", + "instances/interactiveconnectionmanager.cpp", + "instances/interactiveconnectionmanager.h", + "instances/qprocessuniqueptr.h", "metainfo/itemlibraryinfo.cpp", "metainfo/metainfo.cpp", "metainfo/metainforeader.cpp", diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 9d90fd16c9a..d5a6b39e067 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -52,6 +52,7 @@ extend_qtc_executable(qml2puppet inputeventcommand.cpp inputeventcommand.h view3dactioncommand.cpp view3dactioncommand.h valueschangedcommand.cpp + captureddatacommand.cpp captureddatacommand.h ) extend_qtc_executable(qml2puppet @@ -156,6 +157,7 @@ extend_qtc_executable(qml2puppet quick3dnodeinstance.cpp quick3dnodeinstance.h quickitemnodeinstance.cpp quickitemnodeinstance.h servernodeinstance.cpp servernodeinstance.h + qt5capturenodeinstanceserver.cpp qt5capturenodeinstanceserver.h ) extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 485534008cc..b30ce11dc8a 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -107,6 +107,8 @@ QtcTool { "commands/inputeventcommand.h", "commands/view3dactioncommand.cpp", "commands/view3dactioncommand.h", + "commands/captureddatacommand.cpp", + "commands/captureddatacommand.h", "container/addimportcontainer.cpp", "container/addimportcontainer.h", "container/idcontainer.cpp", @@ -207,6 +209,8 @@ QtcTool { "instances/qt5testnodeinstanceserver.h", "instances/servernodeinstance.cpp", "instances/servernodeinstance.h", + "instances/qt5capturenodeinstanceserver.cpp", + "instances/qt5capturenodeinstanceserver.h", "editor3d/generalhelper.cpp", "editor3d/generalhelper.h", "editor3d/mousearea3d.cpp", diff --git a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt index 83f9da98a0a..fb22fbf06b4 100644 --- a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt @@ -17,4 +17,5 @@ add_qtc_test(tst_qml_testcore ../testview.cpp ../testview.h testrewriterview.cpp testrewriterview.h tst_testcore.cpp tst_testcore.h + ../testconnectionmanager.cpp ../testconnectionmanager.h ) diff --git a/tests/auto/qml/qmldesigner/coretests/coretests.pro b/tests/auto/qml/qmldesigner/coretests/coretests.pro index dd0f071b0e9..fb0437b69f0 100644 --- a/tests/auto/qml/qmldesigner/coretests/coretests.pro +++ b/tests/auto/qml/qmldesigner/coretests/coretests.pro @@ -60,11 +60,13 @@ TEMPLATE = app SOURCES += \ ../testview.cpp \ testrewriterview.cpp \ - tst_testcore.cpp + tst_testcore.cpp \ + ../testconnectionmanager.cpp HEADERS += \ ../testview.h \ testrewriterview.h \ - tst_testcore.h + tst_testcore.h \ + ../testconnectionmanager.h RESOURCES += ../data/testfiles.qrc diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 52fa6f519f8..e843ad7a3d0 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -46,11 +46,12 @@ #include #include +#include "../testconnectionmanager.h" #include "../testview.h" -#include #include #include #include +#include #include #include @@ -1999,8 +2000,9 @@ void tst_TestCore::testModelRemoveNode() QVERIFY(view.data()); model->attachView(view.data()); - NodeInstanceView *nodeInstanceView = new NodeInstanceView(model.data(), NodeInstanceServerInterface::TestModus); - model->attachView(nodeInstanceView); + TestConnectionManager connectionManager; + NodeInstanceView nodeInstanceView{connectionManager}; + model->attachView(&nodeInstanceView); QCOMPARE(view->rootModelNode().directSubModelNodes().count(), 0); @@ -2051,7 +2053,7 @@ void tst_TestCore::testModelRemoveNode() childNode = view->createModelNode("QtQuick.Item", 1, 1); childNode.destroy(); - model->detachView(nodeInstanceView); + model->detachView(&nodeInstanceView); } void tst_TestCore::reparentingNode() @@ -6140,17 +6142,21 @@ void tst_TestCore::testInstancesAttachToExistingModel() // Attach NodeInstanceView - QScopedPointer instanceView(new NodeInstanceView(0, NodeInstanceServerInterface::TestModus)); - QVERIFY(instanceView.data()); - model->attachView(instanceView.data()); + TestConnectionManager connectionManager; - NodeInstance rootInstance = instanceView->instanceForModelNode(rootNode); - NodeInstance rectangleInstance = instanceView->instanceForModelNode(rectangleNode); + NodeInstanceView instanceView{connectionManager}; + + model->attachView(&instanceView); + + NodeInstance rootInstance = instanceView.instanceForModelNode(rootNode); + NodeInstance rectangleInstance = instanceView.instanceForModelNode(rectangleNode); QVERIFY(rootInstance.isValid()); QVERIFY(rectangleInstance.isValid()); QCOMPARE(QVariant(100), rectangleInstance.property("width")); QVERIFY(rootInstance.instanceId() >= 0); QVERIFY(rectangleInstance.instanceId() >= 0); + + model->detachView(&instanceView); } void tst_TestCore::testQmlModelAddMultipleStates() diff --git a/tests/auto/qml/qmldesigner/testconnectionmanager.cpp b/tests/auto/qml/qmldesigner/testconnectionmanager.cpp new file mode 100644 index 00000000000..f637fb1f1ab --- /dev/null +++ b/tests/auto/qml/qmldesigner/testconnectionmanager.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "testconnectionmanager.h" +#include "synchronizecommand.h" + +#include + +namespace QmlDesigner { + +TestConnectionManager::TestConnectionManager() +{ + m_connections.emplace_back("Editor", "editormode"); +} + +void TestConnectionManager::writeCommand(const QVariant &command) +{ + TestConnectionManager::writeCommand(command); + + m_writeCommandCounter++; + + static int synchronizeId = 0; + synchronizeId++; + SynchronizeCommand synchronizeCommand(synchronizeId); + + QLocalSocket *socket = m_connections.front().socket.get(); + + writeCommandToIODevice(QVariant::fromValue(synchronizeCommand), socket, m_writeCommandCounter); + m_writeCommandCounter++; + + while (socket->waitForReadyRead(100)) { + readDataStream(m_connections.front()); + if (m_synchronizeId == synchronizeId) + return; + } +} + +void TestConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) +{ + static const int synchronizeCommandType = QMetaType::type("SynchronizeCommand"); + + if (command.userType() == synchronizeCommandType) { + SynchronizeCommand synchronizeCommand = command.value(); + m_synchronizeId = synchronizeCommand.synchronizeId(); + } else { + ConnectionManager::dispatchCommand(command, connection); + } +} + +} // namespace QmlDesigner diff --git a/tests/auto/qml/qmldesigner/testconnectionmanager.h b/tests/auto/qml/qmldesigner/testconnectionmanager.h new file mode 100644 index 00000000000..6ea44c1c1df --- /dev/null +++ b/tests/auto/qml/qmldesigner/testconnectionmanager.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QLocalServer; +class QLocalSocket; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class TestConnectionManager final : public ConnectionManager +{ +public: + TestConnectionManager(); + + void writeCommand(const QVariant &command) override; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + int m_synchronizeId = -1; +}; + +} // namespace QmlDesigner From 208ebd60437666515a4d64b7792a156952ebbc55 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 10 Aug 2020 13:49:58 +0200 Subject: [PATCH 39/42] QmlPreview: Fix crash at shutdown Seem like it get a nullptr project at shutdown. Change-Id: I76d9f4b2b90b0641d645cb413f9ebfe678bb1c9f Reviewed-by: Thomas Hartmann --- src/plugins/qmlpreview/projectfileselectionswidget.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmlpreview/projectfileselectionswidget.cpp b/src/plugins/qmlpreview/projectfileselectionswidget.cpp index ac098886196..f6f20fc2d4c 100644 --- a/src/plugins/qmlpreview/projectfileselectionswidget.cpp +++ b/src/plugins/qmlpreview/projectfileselectionswidget.cpp @@ -115,7 +115,10 @@ ProjectFileSelectionsWidget::ProjectFileSelectionsWidget(const QString &projectS layout->setContentsMargins(0, 0, 0, 0); layout->addLayout(viewLayout); - auto initModel = [this, model, updateCheckedFiles] (ProjectExplorer::Project *project) { + auto initModel = [this, model, updateCheckedFiles](ProjectExplorer::Project *project) { + if (!project) + return; + auto refreshModel = [this, model, updateCheckedFiles] () { model->clear(); if (auto project = ProjectExplorer::SessionManager::startupProject()) { From dc870f538dd3e11464a3ece179edf024161e14ba Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Wed, 12 Aug 2020 13:18:24 +0200 Subject: [PATCH 40/42] Prepare merge of qds-1.59 to 4.13 It is not compiling but we are closing this branch anyway. Change-Id: If96318175d92ae492871d668d5c937c473fd05d5 Reviewed-by: Tim Jenssen --- .../qml/qmlpuppet/qml2puppet/instances/instances.pri | 3 ++- .../designercore/instances/baseconnectionmanager.cpp | 1 + .../designercore/instances/capturingconnectionmanager.cpp | 4 ++++ .../designercore/instances/connectionmanager.cpp | 1 + .../designercore/instances/connectionmanagerinterface.cpp | 1 + .../designercore/instances/connectionmanagerinterface.h | 1 + .../instances/interactiveconnectionmanager.cpp | 1 + src/plugins/qmldesigner/qmldesignerplugin.qbs | 1 + src/plugins/qmlpreview/CMakeLists.txt | 1 + src/plugins/qmlpreview/qmldebugtranslationwidget.cpp | 8 ++------ src/tools/qml2puppet/CMakeLists.txt | 2 +- src/tools/qml2puppet/qml2puppet.qbs | 1 - 12 files changed, 16 insertions(+), 9 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri index ce9bb2376e7..fdc862991e3 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri @@ -55,4 +55,5 @@ SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ $$PWD/positionernodeinstance.cpp \ $$PWD/layoutnodeinstance.cpp \ $$PWD/qt3dpresentationnodeinstance.cpp \ - $$PWD/quick3dnodeinstance.cpp + $$PWD/quick3dnodeinstance.cpp \ + $$PWD/quick3dtexturenodeinstance.cpp diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp index 0d4540b37ff..b7a2cc282e5 100644 --- a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp @@ -29,6 +29,7 @@ #include "nodeinstanceview.h" #include +#include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp index b6e81f1ffeb..4be52a327c7 100644 --- a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp @@ -27,6 +27,10 @@ #include +#include +#include +#include + namespace QmlDesigner { void CapturingConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp index edf7f20b9c1..fa8528579d0 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp @@ -37,6 +37,7 @@ #include #include +#include #include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp index 2aa7df6be62..b4fdb707944 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp @@ -27,6 +27,7 @@ #include #include +#include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h index f3d8e6293e4..92d7449bc0b 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h @@ -26,6 +26,7 @@ #include "qprocessuniqueptr.h" #include +#include QT_BEGIN_NAMESPACE class QLocalSocket; diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp index 4b6c73f19e4..6da44603df8 100644 --- a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp @@ -32,6 +32,7 @@ #include #include +#include namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 971bcffb471..4c0b1d223dd 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -134,6 +134,7 @@ Project { "commands/changestatecommand.h", "commands/changevaluescommand.cpp", "commands/changevaluescommand.h", + "commands/captureddatacommand.h", "commands/childrenchangedcommand.cpp", "commands/childrenchangedcommand.h", "commands/clearscenecommand.cpp", diff --git a/src/plugins/qmlpreview/CMakeLists.txt b/src/plugins/qmlpreview/CMakeLists.txt index ab5024660cf..00bda33ab0b 100644 --- a/src/plugins/qmlpreview/CMakeLists.txt +++ b/src/plugins/qmlpreview/CMakeLists.txt @@ -10,6 +10,7 @@ add_qtc_plugin(QmlPreview qmldebugtranslationclient.cpp qmldebugtranslationclient.h qmlpreview_global.h projectfileselectionswidget.cpp projectfileselectionswidget.h + qmldebugtranslationwidget.cpp qmldebugtranslationwidget.h ) extend_qtc_plugin(QmlPreview diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp index 2590e96363e..3de3eff2c21 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -288,7 +288,8 @@ void QmlDebugTranslationWidget::runTest() connect(runControl, &ProjectExplorer::RunControl::started, [this, runControl, previewPlugin]() { //Q_ASSERT(m_currentRunControl == nullptr); //TODO: who deletes the runcontrol m_currentRunControl = runControl; - m_runOutputWindow->setFormatter(runControl->outputFormatter()); + m_runOutputWindow->setLineParsers( + ProjectExplorer::OutputFormatterFactory::createFormatters(runControl->target())); int timerCounter = 1; const auto testLanguageList = m_testLanguages; @@ -414,11 +415,6 @@ void QmlDebugTranslationWidget::appendMessage(const QString &message, Utils::Out fileLine = qmlLineColumnMatch.captured(2).toInt(); } - if (!m_runOutputWindow->formatter()) { - auto defaultFormatter = new Utils::OutputFormatter(); - defaultFormatter->setParent(this); - m_runOutputWindow->setFormatter(defaultFormatter); - } m_runOutputWindow->appendMessage(message, format); diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index d5a6b39e067..34c50da05d2 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -52,7 +52,7 @@ extend_qtc_executable(qml2puppet inputeventcommand.cpp inputeventcommand.h view3dactioncommand.cpp view3dactioncommand.h valueschangedcommand.cpp - captureddatacommand.cpp captureddatacommand.h + captureddatacommand.h ) extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index b30ce11dc8a..61c59625422 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -107,7 +107,6 @@ QtcTool { "commands/inputeventcommand.h", "commands/view3dactioncommand.cpp", "commands/view3dactioncommand.h", - "commands/captureddatacommand.cpp", "commands/captureddatacommand.h", "container/addimportcontainer.cpp", "container/addimportcontainer.h", From 66122492a6358b07b041096b24e87241a9ae9696 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Wed, 15 Jul 2020 15:47:13 +0200 Subject: [PATCH 41/42] QmlDesigner: Extend Connections view support - Connection view support to components - Connection view support to singletons - Minor reparenting improvements Task: QDS-2411 Change-Id: I337535012dbb3d3a1722d75d89156463eabb8a4c Reviewed-by: Vikas Pachdha --- .../connectioneditor/connectionmodel.cpp | 112 +++++++++++++----- .../components/connectioneditor/delegates.cpp | 48 ++++++-- .../designercore/include/nodemetainfo.h | 2 + .../designercore/metainfo/nodemetainfo.cpp | 13 +- 4 files changed, 135 insertions(+), 40 deletions(-) diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 4bafa046474..04c6c343dc5 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -221,6 +221,13 @@ void ConnectionModel::updateTargetNode(int rowNumber) isSingleton = true; break; } + } else if (isAlias) { + if (data.typeName == newTarget.split(".").constFirst()) { + if (connectionView()->model()->metaInfo(data.typeName.toUtf8()).isValid()) { + isSingleton = true; + break; + } + } } } } @@ -229,7 +236,7 @@ void ConnectionModel::updateTargetNode(int rowNumber) if (!newTarget.isEmpty()) { //if it's a singleton, then let's reparent connections to rootNode, //if it's an alias, then reparent to alias property owner: - const ModelNode parent = connectionView()->modelNodeForId(isSingleton + const ModelNode parent = connectionView()->modelNodeForId((isSingleton || (isSingleton && isAlias)) ? connectionView()->rootModelNode().id() : isAlias ? newTarget.split(".").constFirst() @@ -255,35 +262,36 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const { - BindingProperty bindingProperty = connection.bindingProperty("target"); + ModelNode result; + + const BindingProperty bindingProperty = connection.bindingProperty("target"); + const QString bindExpression = bindingProperty.expression(); if (bindingProperty.isValid()) { - bool isAlias = bindingProperty.expression().contains("."); - - if (bindingProperty.expression() == QLatin1String("parent")) - return connection.parentProperty().parentModelNode(); - else if (isAlias) { - QStringList substr = bindingProperty.expression().split("."); + if (bindExpression == QLatin1String("parent")) { + result = connection.parentProperty().parentModelNode(); + } else if (bindExpression.contains(".")) { + QStringList substr = bindExpression.split("."); + const QString itemId = substr.constFirst(); if (substr.size() > 1) { - ModelNode aliasParent = connectionView()->modelNodeForId(substr.constFirst()); - QString aliasBody = substr.at(1); - if (aliasParent.hasProperty(aliasBody.toUtf8())) { - AbstractProperty abstractProp = aliasParent.property(aliasBody.toUtf8()); - if (abstractProp.isBindingProperty()) { - BindingProperty binding = abstractProp.toBindingProperty(); - if (connectionView()->hasId(binding.expression())) { - ModelNode resolve = connectionView()->modelNodeForId(binding.expression()); - if (resolve.isValid()) - return resolve; - } + const ModelNode aliasParent = (itemId == QLatin1String("parent") + ? connection.parentProperty().parentModelNode() + : connectionView()->modelNodeForId(itemId)); + substr.removeFirst(); //remove id, only alias pieces left + const QString aliasBody = substr.join("."); + if (aliasParent.isValid() && aliasParent.hasBindingProperty(aliasBody.toUtf8())) { + const BindingProperty binding = aliasParent.bindingProperty(aliasBody.toUtf8()); + if (binding.isValid() && connectionView()->hasId(binding.expression())) { + result = connectionView()->modelNodeForId(binding.expression()); } } } + } else { + result = connectionView()->modelNodeForId(bindExpression); } - return connectionView()->modelNodeForId(bindingProperty.expression()); } - return ModelNode(); + return result; } void ConnectionModel::addConnection() @@ -355,8 +363,7 @@ void ConnectionModel::deleteConnectionByRow(int currentRow) if (allSignals.size() > 1) { if (allSignals.contains(targetSignal)) node.removeProperty(targetSignal.name()); - } - else { + } else { node.destroy(); } } @@ -448,21 +455,44 @@ QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &co { QStringList stringList; - if (connection.isValid()) { + auto getAliasMetaSignals = [&](QString aliasPart, NodeMetaInfo metaInfo) { + if (metaInfo.isValid() && metaInfo.hasProperty(aliasPart.toUtf8())) { + NodeMetaInfo propertyMetaInfo = connectionView()->model()->metaInfo( + metaInfo.propertyTypeName(aliasPart.toUtf8())); + if (propertyMetaInfo.isValid()) { + return propertyNameListToStringList(propertyMetaInfo.signalNames()); + } + } + return QStringList(); + }; + if (connection.isValid()) { //separate check for singletons if (connection.hasBindingProperty("target")) { - BindingProperty bp = connection.bindingProperty("target"); + const BindingProperty bp = connection.bindingProperty("target"); if (bp.isValid()) { - if (RewriterView *rv = connectionView()->rewriterView()) { + const QString bindExpression = bp.expression(); + + if (const RewriterView * const rv = connectionView()->rewriterView()) { for (const QmlTypeData &data : rv->getQMLTypes()) { if (!data.typeName.isEmpty()) { - if (data.typeName == bp.expression()) { + if (data.typeName == bindExpression) { NodeMetaInfo metaInfo = connectionView()->model()->metaInfo(data.typeName.toUtf8()); if (metaInfo.isValid()) { - stringList.append(propertyNameListToStringList(metaInfo.signalNames())); - return stringList; + stringList << propertyNameListToStringList(metaInfo.signalNames()); + break; + } + } else if (bindExpression.contains(".")) { + //if it doesn't fit the same name, maybe it's an alias? + QStringList expression = bindExpression.split("."); + if ((expression.size() > 1) && (expression.constFirst() == data.typeName)) { + expression.removeFirst(); + + stringList << getAliasMetaSignals( + expression.join("."), + connectionView()->model()->metaInfo(data.typeName.toUtf8())); + break; } } } @@ -474,6 +504,30 @@ QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &co ModelNode targetNode = getTargetNodeForConnection(connection); if (targetNode.isValid() && targetNode.metaInfo().isValid()) { stringList.append(propertyNameListToStringList(targetNode.metaInfo().signalNames())); + } else { + //most likely it's component's internal alias: + + if (connection.hasBindingProperty("target")) { + const BindingProperty bp = connection.bindingProperty("target"); + + if (bp.isValid()) { + const QString bindExpression = bp.expression(); + QStringList expression = bp.expression().split("."); + if (expression.size() > 1) { + const QString itemId = expression.constFirst(); + if (connectionView()->hasId(itemId)) { + ModelNode parentItem = connectionView()->modelNodeForId(itemId); + if (parentItem.isValid() + && parentItem.hasMetaInfo() + && parentItem.metaInfo().isValid()) { + expression.removeFirst(); + stringList << getAliasMetaSignals(expression.join("."), + parentItem.metaInfo()); + } + } + } + } + } } } diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp index 864404047ee..83682d5d448 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp @@ -295,6 +295,35 @@ QWidget *ConnectionDelegate::createEditor(QWidget *parent, const QStyleOptionVie switch (index.column()) { case ConnectionModel::TargetModelNodeRow: { + + auto addMetaInfoProperties = [&](const NodeMetaInfo& itemMetaInfo, QString itemName){ + if (itemMetaInfo.isValid()) { + for (const PropertyName &propertyName : itemMetaInfo.directPropertyNames()) { + TypeName propertyType = itemMetaInfo.propertyTypeName(propertyName); + if (!propertyType.isEmpty()) { + //first letter is a reliable item indicator + QChar firstLetter = QString::fromUtf8(propertyType).at(0); + if (firstLetter.isLetter() && firstLetter.isUpper()) { + if (!itemMetaInfo.propertyIsEnumType(propertyName) + && !itemMetaInfo.propertyIsPrivate(propertyName) + && !itemMetaInfo.propertyIsListProperty(propertyName) + && !itemMetaInfo.propertyIsPointer(propertyName)) { + NodeMetaInfo propertyMetaInfo = + connectionModel->connectionView()->model()->metaInfo(propertyType); + if (propertyMetaInfo.isValid()) { + if (propertyMetaInfo.isQmlItem()) { + connectionComboBox->addItem(itemName + + "." + + propertyName); + } + } + } + } + } + } + } + }; + for (const ModelNode &modelNode : connectionModel->connectionView()->allModelNodes()) { if (!modelNode.id().isEmpty()) { connectionComboBox->addItem(modelNode.id()); @@ -308,25 +337,24 @@ QWidget *ConnectionDelegate::createEditor(QWidget *parent, const QStyleOptionVie } } } + + //Components + if (modelNode.isComponent()) { + NodeMetaInfo componentMetaInfo = modelNode.metaInfo(); + addMetaInfoProperties(componentMetaInfo, modelNode.id()); + } } } //singletons: if (RewriterView* rv = connectionModel->connectionView()->rewriterView()) { for (const QmlTypeData &data : rv->getQMLTypes()) { if (!data.typeName.isEmpty()) { + //singleton itself connectionComboBox->addItem(data.typeName); + //its properties, mostly looking for aliases: NodeMetaInfo metaInfo = connectionModel->connectionView()->model()->metaInfo(data.typeName.toUtf8()); - - if (metaInfo.isValid()) { - for (const PropertyName &propertyName : metaInfo.propertyNames()) { - if (metaInfo.propertyTypeName(propertyName) == "alias") { - connectionComboBox->addItem(data.typeName - + "." - + QString::fromUtf8(propertyName)); - } - } - } + addMetaInfoProperties(metaInfo, data.typeName); } } } diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index 05a08d14675..b5e2e7626ca 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -75,6 +75,7 @@ public: bool propertyIsListProperty(const PropertyName &propertyName) const; bool propertyIsEnumType(const PropertyName &propertyName) const; bool propertyIsPrivate(const PropertyName &propertyName) const; + bool propertyIsPointer(const PropertyName &propertyName) const; QString propertyEnumScope(const PropertyName &propertyName) const; QStringList propertyKeysForEnum(const PropertyName &propertyName) const; QVariant propertyCastedValue(const PropertyName &propertyName, const QVariant &value) const; @@ -99,6 +100,7 @@ public: bool isSubclassOf(const TypeName &type, int majorVersion = -1, int minorVersion = -1) const; bool isGraphicalItem() const; + bool isQmlItem() const; bool isLayoutable() const; bool isView() const; bool isTabView() const; diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index 3b64f74e0c0..afd2aaa9a59 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -427,7 +427,7 @@ QVector getQmlTypes(const CppComponentValue *objectValue, const Co PropertyMemberProcessor processor(context); objectValue->processMembers(&processor); - foreach (const PropertyInfo &property, processor.properties()) { + for (const PropertyInfo &property : processor.properties()) { const PropertyName name = property.first; const QString nameAsString = QString::fromUtf8(name); if (!objectValue->isWritable(nameAsString) && objectValue->isPointer(nameAsString)) { @@ -1500,6 +1500,11 @@ bool NodeMetaInfo::propertyIsPrivate(const PropertyName &propertyName) const return propertyName.startsWith("__"); } +bool NodeMetaInfo::propertyIsPointer(const PropertyName &propertyName) const +{ + return m_privateData->isPropertyPointer(propertyName); +} + QString NodeMetaInfo::propertyEnumScope(const PropertyName &propertyName) const { return m_privateData->propertyEnumScope(propertyName); @@ -1660,6 +1665,12 @@ bool NodeMetaInfo::isGraphicalItem() const || isSubclassOf("QtQuick.Controls.Popup"); } +bool NodeMetaInfo::isQmlItem() const +{ + return isSubclassOf("QtQuick.QtObject") + || isSubclassOf("QtQml.QtObject"); +} + void NodeMetaInfo::clearCache() { Internal::NodeMetaInfoPrivate::clearCache(); From 54b1aaed0381c0d6dce14caad2a3223620e7cc7a Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Tue, 11 Aug 2020 17:53:44 +0300 Subject: [PATCH 42/42] Android: warn about wrong ABI in issues pane instead of general messages Change-Id: I7b11b0b91f2843ee3d95d86b9afc772295dd6e94 Reviewed-by: Eike Ziller --- src/plugins/android/androiddeployqtstep.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 96be06a6962..27cd6daf0c6 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -212,12 +213,12 @@ bool AndroidDeployQtStep::init() auto selectedAbis = buildSystem()->extraData(buildKey, Constants::ANDROID_ABIS).toStringList(); if (!selectedAbis.contains(info.cpuAbi.first())) { - Core::MessageManager::write( + TaskHub::addTask(DeploymentTask( + Task::Warning, tr("Android: The main ABI of the deployment device (%1) is not selected! The app " "execution or debugging might not work properly. Add it from Projects > Build > " "Build Steps > qmake > ABIs.") - .arg(info.cpuAbi.first()), - Core::MessageManager::WithFocus); + .arg(info.cpuAbi.first()))); } m_avdName = info.avdname;