diff options
Diffstat (limited to 'src/plugins/qmldesigner/components/connectioneditor')
21 files changed, 4149 insertions, 0 deletions
diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp new file mode 100644 index 0000000000..9de928ae90 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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 "addnewbackenddialog.h" +#include "ui_addnewbackenddialog.h" + +#include <QPushButton> + +namespace QmlDesigner { + +AddNewBackendDialog::AddNewBackendDialog(QWidget *parent) : + QDialog(parent), + m_ui(new Ui::AddNewBackendDialog) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + m_ui->setupUi(this); + + connect(m_ui->comboBox, &QComboBox::currentTextChanged, this, &AddNewBackendDialog::invalidate); + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, [this]() { + m_applied = true; + close(); + }); + + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &AddNewBackendDialog::close); +} + +AddNewBackendDialog::~AddNewBackendDialog() +{ + delete m_ui; +} + +void AddNewBackendDialog::setupPossibleTypes(const QList<CppTypeData> &types) +{ + QSignalBlocker blocker(this); + m_typeData = types; + for (const CppTypeData &typeData : types) + m_ui->comboBox->addItem(typeData.typeName); + + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_ui->comboBox->count() > 0); + invalidate(); +} + +QString AddNewBackendDialog::importString() const +{ + if (m_ui->comboBox->currentIndex() < 0) + return QString(); + + CppTypeData typeData = m_typeData.at(m_ui->comboBox->currentIndex()); + + return typeData.importUrl + " " + typeData.versionString; +} + +QString AddNewBackendDialog::type() const +{ + if (m_ui->comboBox->currentIndex() < 0) + return QString(); + + return m_typeData.at(m_ui->comboBox->currentIndex()).typeName; +} + +bool AddNewBackendDialog::applied() const +{ + return m_applied; +} + +bool AddNewBackendDialog::localDefinition() const +{ + return m_ui->checkBox->isChecked(); +} + +bool AddNewBackendDialog::isSingleton() const +{ + return m_isSingleton; +} + +void AddNewBackendDialog::invalidate() +{ + if (m_ui->comboBox->currentIndex() < 0) + return; + + CppTypeData typeData = m_typeData.at(m_ui->comboBox->currentIndex()); + m_ui->importLabel->setText(importString()); + + m_ui->checkBox->setChecked(false); + if (typeData.isSingleton) + m_ui->checkBox->setEnabled(false); + + m_isSingleton = typeData.isSingleton; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h new file mode 100644 index 0000000000..4cfa46d66e --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 <QDialog> + +#include <rewriterview.h> + +namespace QmlDesigner { + +namespace Ui { +class AddNewBackendDialog; +} + +class AddNewBackendDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AddNewBackendDialog(QWidget *parent = nullptr); + ~AddNewBackendDialog() override; + void setupPossibleTypes(const QList<CppTypeData> &types); + QString importString() const; + QString type() const; + bool applied() const; + bool localDefinition() const; + bool isSingleton() const; + +private: + void invalidate(); + + Ui::AddNewBackendDialog *m_ui; + QList<CppTypeData> m_typeData; + + bool m_applied = false; + bool m_isSingleton = false; +}; + + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui new file mode 100644 index 0000000000..1da43f7526 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/addnewbackenddialog.ui @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::AddNewBackendDialog</class> + <widget class="QDialog" name="QmlDesigner::AddNewBackendDialog"> + <property name="windowModality"> + <enum>Qt::ApplicationModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>412</width> + <height>202</height> + </rect> + </property> + <property name="windowTitle"> + <string>Add New C++ Backend</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="2" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>13</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="1"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QFrame" name="frame"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="comboBox"/> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>169</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Define object locally</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QCheckBox" name="checkBox"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Required import</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QLabel" name="importLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>160</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>160</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_4"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Choose a type that is registered using qmlRegisterType or qmlRegisterSingletonType. The type will be available as a property in the current .qml file.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp new file mode 100644 index 0000000000..4a004ca575 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.cpp @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** 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 <utils/algorithm.h> + +#include "backendmodel.h" + +#include "bindingproperty.h" +#include "connectionview.h" +#include "exception.h" +#include "nodemetainfo.h" +#include "nodeproperty.h" +#include "rewriterview.h" +#include "rewritertransaction.h" + +#include "addnewbackenddialog.h" + +#include <coreplugin/icore.h> +#include <utils/qtcassert.h> + +namespace QmlDesigner { + +namespace Internal { + +BackendModel::BackendModel(ConnectionView *parent) : + QStandardItemModel(parent) + ,m_connectionView(parent) +{ + connect(this, &QStandardItemModel::dataChanged, this, &BackendModel::handleDataChanged); +} + +ConnectionView *QmlDesigner::Internal::BackendModel::connectionView() const +{ + return m_connectionView; +} + +void BackendModel::resetModel() +{ + if (!m_connectionView->model()) + return; + + RewriterView *rewriterView = m_connectionView->model()->rewriterView(); + + m_lock = true; + + beginResetModel(); + clear(); + + setHorizontalHeaderLabels(QStringList({ tr("Type"), tr("Name"), tr("Singleton"), tr("Local") })); + + ModelNode rootNode = connectionView()->rootModelNode(); + + static const PropertyTypeList simpleTypes = {"int", "real", "color", "string"}; + + if (rewriterView) + for (const CppTypeData &cppTypeData : rewriterView->getCppTypes()) + if (cppTypeData.isSingleton) { + NodeMetaInfo metaInfo = m_connectionView->model()->metaInfo(cppTypeData.typeName.toUtf8()); + if (metaInfo.isValid() && !metaInfo.isSubclassOf("QtQuick.Item")) { + auto type = new QStandardItem(cppTypeData.typeName); + type->setData(cppTypeData.typeName, Qt::UserRole + 1); + type->setData(true, Qt::UserRole + 2); + type->setEditable(false); + + auto name = new QStandardItem(cppTypeData.typeName); + name->setEditable(false); + + QStandardItem *singletonItem = new QStandardItem(""); + singletonItem->setCheckState(Qt::Checked); + + singletonItem->setCheckable(true); + singletonItem->setEnabled(false); + + QStandardItem *inlineItem = new QStandardItem(""); + + inlineItem->setCheckState(Qt::Unchecked); + + inlineItem->setCheckable(true); + inlineItem->setEnabled(false); + + appendRow({ type, name, singletonItem, inlineItem }); + } + } + + if (rootNode.isValid()) + foreach (const AbstractProperty &property ,rootNode.properties()) + if (property.isDynamic() && !simpleTypes.contains(property.dynamicTypeName())) { + + NodeMetaInfo metaInfo = m_connectionView->model()->metaInfo(property.dynamicTypeName()); + if (metaInfo.isValid() && !metaInfo.isSubclassOf("QtQuick.Item")) { + QStandardItem *type = new QStandardItem(QString::fromUtf8(property.dynamicTypeName())); + type->setEditable(false); + + type->setData(QString::fromUtf8(property.name()), Qt::UserRole + 1); + type->setData(false, Qt::UserRole + 2); + QStandardItem *name = new QStandardItem(QString::fromUtf8(property.name())); + + QStandardItem *singletonItem = new QStandardItem(""); + singletonItem->setCheckState(Qt::Unchecked); + + singletonItem->setCheckable(true); + singletonItem->setEnabled(false); + + QStandardItem *inlineItem = new QStandardItem(""); + + inlineItem->setCheckState(property.isNodeProperty() ? Qt::Checked : Qt::Unchecked); + + inlineItem->setCheckable(true); + inlineItem->setEnabled(false); + + appendRow({ type, name, singletonItem, inlineItem }); + } + } + + m_lock = false; + + endResetModel(); +} + +QStringList BackendModel::possibleCppTypes() const +{ + RewriterView *rewriterView = m_connectionView->model()->rewriterView(); + + QStringList list; + + if (rewriterView) + foreach (const CppTypeData &cppTypeData, rewriterView->getCppTypes()) + list.append(cppTypeData.typeName); + + return list; +} + +CppTypeData BackendModel::cppTypeDataForType(const QString &typeName) const +{ + RewriterView *rewriterView = m_connectionView->model()->rewriterView(); + + if (!rewriterView) + return CppTypeData(); + + return Utils::findOr(rewriterView->getCppTypes(), CppTypeData(), [&typeName](const CppTypeData &data) { + return typeName == data.typeName; + }); +} + +void BackendModel::deletePropertyByRow(int rowNumber) +{ + Model *model = m_connectionView->model(); + if (!model) + return; + + /* singleton case remove the import */ + if (data(index(rowNumber, 0), Qt::UserRole + 1).toBool()) { + const QString typeName = data(index(rowNumber, 0), Qt::UserRole + 1).toString(); + CppTypeData cppTypeData = cppTypeDataForType(typeName); + + if (cppTypeData.isSingleton) { + + Import import = Import::createLibraryImport(cppTypeData.importUrl, cppTypeData.versionString); + + try { + if (model->hasImport(import)) + model->changeImports({}, {import}); + } catch (const Exception &e) { + e.showException(); + } + } + } else { + const QString propertyName = data(index(rowNumber, 0), Qt::UserRole + 1).toString(); + + ModelNode modelNode = connectionView()->rootModelNode(); + + try { + modelNode.removeProperty(propertyName.toUtf8()); + } catch (const Exception &e) { + e.showException(); + } + } + + resetModel(); +} + +void BackendModel::addNewBackend() +{ + Model *model = m_connectionView->model(); + if (!model) + return; + + AddNewBackendDialog dialog(Core::ICore::mainWindow()); + + RewriterView *rewriterView = model->rewriterView(); + + QStringList availableTypes; + + if (rewriterView) + dialog.setupPossibleTypes(Utils::filtered(rewriterView->getCppTypes(), [model](const CppTypeData &cppTypeData) { + return !cppTypeData.isSingleton || !model->metaInfo(cppTypeData.typeName.toUtf8()).isValid(); + /* Only show singletons if the import is missing */ + })); + + dialog.exec(); + + if (dialog.applied()) { + QStringList importSplit = dialog.importString().split(" "); + if (importSplit.count() != 2) { + qWarning() << Q_FUNC_INFO << "invalid import" << importSplit; + QTC_ASSERT(false, return); + } + + QString typeName = dialog.type(); + + Import import = Import::createLibraryImport(importSplit.constFirst(), importSplit.constLast()); + + /* We cannot add an import and add a node from that import in a single transaction. + * We need the import to have the meta info available. + */ + + if (!model->hasImport(import)) + model->changeImports({import}, {}); + + QString propertyName = m_connectionView->generateNewId(typeName); + + NodeMetaInfo metaInfo = model->metaInfo(typeName.toUtf8()); + + QTC_ASSERT(metaInfo.isValid(), return); + + /* Add a property for non singleton types. For singletons just adding the import is enough. */ + if (!dialog.isSingleton()) { + m_connectionView->executeInTransaction("BackendModel::addNewBackend", [=, &dialog](){ + int minorVersion = metaInfo.minorVersion(); + int majorVersion = metaInfo.majorVersion(); + + if (dialog.localDefinition()) { + ModelNode newNode = m_connectionView->createModelNode(metaInfo.typeName(), majorVersion, minorVersion); + + m_connectionView->rootModelNode().nodeProperty(propertyName.toUtf8()).setDynamicTypeNameAndsetModelNode( + typeName.toUtf8(), newNode); + } else { + m_connectionView->rootModelNode().bindingProperty( + propertyName.toUtf8()).setDynamicTypeNameAndExpression(typeName.toUtf8(), "null"); + } + }); + } + } + resetModel(); +} + +void BackendModel::updatePropertyName(int rowNumber) +{ + const PropertyName newName = data(index(rowNumber, 1)).toString().toUtf8(); + const PropertyName oldName = data(index(rowNumber, 0), Qt::UserRole + 1).toString().toUtf8(); + + m_connectionView->executeInTransaction("BackendModel::updatePropertyName", [this, newName, oldName](){ + + ModelNode rootModelNode = m_connectionView->rootModelNode(); + if (rootModelNode.property(oldName).isNodeProperty()) { + + const TypeName typeName = rootModelNode.nodeProperty(oldName).dynamicTypeName(); + const ModelNode targetModelNode = rootModelNode.nodeProperty(oldName).modelNode(); + const TypeName fullTypeName = targetModelNode.type(); + const int majorVersion = targetModelNode.majorVersion(); + const int minorVersion = targetModelNode.minorVersion(); + + rootModelNode.removeProperty(oldName); + ModelNode newNode = m_connectionView->createModelNode(fullTypeName, majorVersion, minorVersion); + m_connectionView->rootModelNode().nodeProperty(newName).setDynamicTypeNameAndsetModelNode(typeName, newNode); + + } else if (rootModelNode.property(oldName).isBindingProperty()) { + const QString expression = rootModelNode.bindingProperty(oldName).expression(); + const TypeName typeName = rootModelNode.bindingProperty(oldName).dynamicTypeName(); + + rootModelNode.removeProperty(oldName); + rootModelNode.bindingProperty(newName).setDynamicTypeNameAndExpression(typeName, expression); + } else { + qWarning() << Q_FUNC_INFO << oldName << newName << "failed..."; + QTC_ASSERT(false, return); + } + }); +} + +void BackendModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (m_lock) + return; + + if (topLeft != bottomRight) { + qWarning() << "BackendModel::handleDataChanged multi edit?"; + return; + } + + m_lock = true; + + int currentColumn = topLeft.column(); + int currentRow = topLeft.row(); + + switch (currentColumn) { + case 0: { + //updating user data + } break; + case 1: { + updatePropertyName(currentRow); + } break; + + default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn; + } + + m_lock = false; +} + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h new file mode 100644 index 0000000000..8abbbe77fa --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/backendmodel.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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 <QStandardItemModel> + +#include "rewriterview.h" + +namespace QmlDesigner { + +namespace Internal { + +class ConnectionView; + +class BackendModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum ColumnRoles { + TypeNameColumn = 0, + PropertyNameColumn = 1, + IsSingletonColumn = 2, + IsLocalColumn = 3, + }; + + BackendModel(ConnectionView *parent); + + ConnectionView *connectionView() const; + + void resetModel(); + + QStringList possibleCppTypes() const; + CppTypeData cppTypeDataForType(const QString &typeName) const; + + void deletePropertyByRow(int rowNumber); + + void addNewBackend(); + +protected: + void updatePropertyName(int rowNumber); + +private: + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); + +private: + ConnectionView *m_connectionView; + bool m_lock = false; +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp new file mode 100644 index 0000000000..2cff12b044 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** 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 "bindingmodel.h" + +#include "connectionview.h" + +#include <nodemetainfo.h> +#include <nodeproperty.h> +#include <bindingproperty.h> +#include <variantproperty.h> +#include <rewritingexception.h> +#include <rewritertransaction.h> + +#include <QMessageBox> +#include <QTimer> + +namespace QmlDesigner { + +namespace Internal { + +BindingModel::BindingModel(ConnectionView *parent) + : QStandardItemModel(parent) + , m_connectionView(parent) +{ + connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged); +} + +void BindingModel::resetModel() +{ + beginResetModel(); + clear(); + setHorizontalHeaderLabels(QStringList({ tr("Item"), tr("Property"), tr("Source Item"), + tr("Source Property") })); + + foreach (const ModelNode modelNode, m_selectedModelNodes) + addModelNode(modelNode); + + endResetModel(); +} + +void BindingModel::bindingChanged(const BindingProperty &bindingProperty) +{ + m_handleDataChanged = false; + + QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes(); + if (!selectedNodes.contains(bindingProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForBinding(bindingProperty); + + if (rowNumber == -1) { + addBindingProperty(bindingProperty); + } else { + updateBindingProperty(rowNumber); + } + } + + m_handleDataChanged = true; +} + +void BindingModel::bindingRemoved(const BindingProperty &bindingProperty) +{ + m_handleDataChanged = false; + + QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes(); + if (!selectedNodes.contains(bindingProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForBinding(bindingProperty); + removeRow(rowNumber); + } + + m_handleDataChanged = true; +} + +void BindingModel::selectionChanged(const QList<ModelNode> &selectedNodes) +{ + m_handleDataChanged = false; + m_selectedModelNodes = selectedNodes; + resetModel(); + m_handleDataChanged = true; +} + +ConnectionView *BindingModel::connectionView() const +{ + return m_connectionView; +} + +BindingProperty BindingModel::bindingPropertyForRow(int rowNumber) const +{ + + const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + + ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + + if (modelNode.isValid()) + return modelNode.bindingProperty(targetPropertyName.toLatin1()); + + return BindingProperty(); +} + +QStringList BindingModel::possibleTargetProperties(const BindingProperty &bindingProperty) const +{ + const ModelNode modelNode = bindingProperty.parentModelNode(); + + if (!modelNode.isValid()) { + qWarning() << " BindingModel::possibleTargetPropertiesForRow invalid model node"; + return QStringList(); + } + + NodeMetaInfo metaInfo = modelNode.metaInfo(); + + if (metaInfo.isValid()) { + QStringList possibleProperties; + foreach (const PropertyName &propertyName, metaInfo.propertyNames()) { + if (metaInfo.propertyIsWritable(propertyName)) + possibleProperties << QString::fromUtf8(propertyName); + } + + return possibleProperties; + } + + return QStringList(); +} + +QStringList BindingModel::possibleSourceProperties(const BindingProperty &bindingProperty) const +{ + const QString expression = bindingProperty.expression(); + const QStringList stringlist = expression.split(QLatin1String(".")); + + TypeName typeName; + + if (bindingProperty.parentModelNode().metaInfo().isValid()) { + typeName = bindingProperty.parentModelNode().metaInfo().propertyTypeName(bindingProperty.name()); + } else { + qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for target node"; + } + + const QString &id = stringlist.constFirst(); + + ModelNode modelNode = getNodeByIdOrParent(id, bindingProperty.parentModelNode()); + + if (!modelNode.isValid()) { + qWarning() << " BindingModel::possibleSourcePropertiesForRow invalid model node"; + return QStringList(); + } + + NodeMetaInfo metaInfo = modelNode.metaInfo(); + + QStringList possibleProperties; + + foreach (VariantProperty variantProperty, modelNode.variantProperties()) { + if (variantProperty.isDynamic()) + possibleProperties << QString::fromUtf8(variantProperty.name()); + } + + foreach (BindingProperty bindingProperty, modelNode.bindingProperties()) { + if (bindingProperty.isDynamic()) + possibleProperties << QString::fromUtf8((bindingProperty.name())); + } + + if (metaInfo.isValid()) { + foreach (const PropertyName &propertyName, metaInfo.propertyNames()) { + if (metaInfo.propertyTypeName(propertyName) == typeName) //### todo proper check + possibleProperties << QString::fromUtf8(propertyName); + } + } else { + qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for source node"; + } + + return possibleProperties; +} + +void BindingModel::deleteBindindByRow(int rowNumber) +{ + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + if (bindingProperty.isValid()) { + bindingProperty.parentModelNode().removeProperty(bindingProperty.name()); + } + + resetModel(); +} + +static PropertyName unusedProperty(const ModelNode &modelNode) +{ + PropertyName propertyName = "none"; + if (modelNode.metaInfo().isValid()) { + foreach (const PropertyName &propertyName, modelNode.metaInfo().propertyNames()) { + if (modelNode.metaInfo().propertyIsWritable(propertyName) && !modelNode.hasProperty(propertyName)) + return propertyName; + } + } + + return propertyName; +} + +void BindingModel::addBindingForCurrentNode() +{ + if (connectionView()->selectedModelNodes().count() == 1) { + const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (modelNode.isValid()) { + try { + modelNode.bindingProperty(unusedProperty(modelNode)).setExpression(QLatin1String("none.none")); + } catch (RewritingException &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &BindingModel::handleException); + } + } + } else { + qWarning() << " BindingModel::addBindingForCurrentNode not one node selected"; + } +} + +void BindingModel::addBindingProperty(const BindingProperty &property) +{ + QStandardItem *idItem; + QStandardItem *targetPropertyNameItem; + QStandardItem *sourceIdItem; + QStandardItem *sourcePropertyNameItem; + + QString idLabel = property.parentModelNode().id(); + if (idLabel.isEmpty()) + idLabel = property.parentModelNode().simplifiedTypeName(); + idItem = new QStandardItem(idLabel); + updateCustomData(idItem, property); + targetPropertyNameItem = new QStandardItem(QString::fromUtf8(property.name())); + QList<QStandardItem*> items; + + items.append(idItem); + items.append(targetPropertyNameItem); + + QString sourceNodeName; + QString sourcePropertyName; + getExpressionStrings(property, &sourceNodeName, &sourcePropertyName); + + sourceIdItem = new QStandardItem(sourceNodeName); + sourcePropertyNameItem = new QStandardItem(sourcePropertyName); + + items.append(sourceIdItem); + items.append(sourcePropertyNameItem); + appendRow(items); +} + +void BindingModel::updateBindingProperty(int rowNumber) +{ + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + if (bindingProperty.isValid()) { + QString targetPropertyName = QString::fromUtf8(bindingProperty.name()); + updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName); + QString sourceNodeName; + QString sourcePropertyName; + getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName); + updateDisplayRole(rowNumber, SourceModelNodeRow, sourceNodeName); + updateDisplayRole(rowNumber, SourcePropertyNameRow, sourcePropertyName); + } +} + +void BindingModel::addModelNode(const ModelNode &modelNode) +{ + foreach (const BindingProperty &bindingProperty, modelNode.bindingProperties()) { + addBindingProperty(bindingProperty); + } +} + +void BindingModel::updateExpression(int row) +{ + const QString sourceNode = data(index(row, SourceModelNodeRow)).toString().trimmed(); + const QString sourceProperty = data(index(row, SourcePropertyNameRow)).toString().trimmed(); + + QString expression; + if (sourceProperty.isEmpty()) { + expression = sourceNode; + } else { + expression = sourceNode + QLatin1String(".") + sourceProperty; + } + + connectionView()->executeInTransaction("BindingModel::updateExpression", [this, row, expression](){ + BindingProperty bindingProperty = bindingPropertyForRow(row); + bindingProperty.setExpression(expression.trimmed()); + }); +} + +void BindingModel::updatePropertyName(int rowNumber) +{ + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + const PropertyName newName = data(index(rowNumber, TargetPropertyNameRow)).toString().toUtf8(); + const QString expression = bindingProperty.expression(); + const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); + ModelNode targetNode = bindingProperty.parentModelNode(); + + if (!newName.isEmpty()) { + RewriterTransaction transaction = + connectionView()->beginRewriterTransaction(QByteArrayLiteral("BindingModel::updatePropertyName")); + try { + if (bindingProperty.isDynamic()) { + targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, expression); + } else { + targetNode.bindingProperty(newName).setExpression(expression); + } + targetNode.removeProperty(bindingProperty.name()); + transaction.commit(); //committing in the try block + } catch (Exception &e) { //better save then sorry + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &BindingModel::handleException); + } + + QStandardItem* idItem = item(rowNumber, 0); + BindingProperty newBindingProperty = targetNode.bindingProperty(newName); + updateCustomData(idItem, newBindingProperty); + + } else { + qWarning() << "BindingModel::updatePropertyName invalid property name"; + } +} + +ModelNode BindingModel::getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const +{ + ModelNode modelNode; + + if (id != QLatin1String("parent")) { + modelNode = connectionView()->modelNodeForId(id); + } else { + if (targetNode.hasParentProperty()) { + modelNode = targetNode.parentProperty().parentModelNode(); + } + } + return modelNode; +} + +void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty) +{ + item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1); + item->setData(bindingProperty.name(), Qt::UserRole + 2); +} + +int BindingModel::findRowForBinding(const BindingProperty &bindingProperty) +{ + for (int i=0; i < rowCount(); i++) { + if (compareBindingProperties(bindingPropertyForRow(i), bindingProperty)) + return i; + } + //not found + return -1; +} + +bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty) +{ + //### todo we assume no expressions yet + + const QString expression = bindingProperty.expression(); + + if (true) { + const QStringList stringList = expression.split(QLatin1String(".")); + + *sourceNode = stringList.constFirst(); + + QString propertyName; + + for (int i=1; i < stringList.count(); i++) { + propertyName += stringList.at(i); + if (i != stringList.count() - 1) + propertyName += QLatin1String("."); + } + *sourceProperty = propertyName; + } + return true; +} + +void BindingModel::updateDisplayRole(int row, int columns, const QString &string) +{ + QModelIndex modelIndex = index(row, columns); + if (data(modelIndex).toString() != string) + setData(modelIndex, string); +} + +void BindingModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!m_handleDataChanged) + return; + + if (topLeft != bottomRight) { + qWarning() << "BindingModel::handleDataChanged multi edit?"; + return; + } + + m_lock = true; + + int currentColumn = topLeft.column(); + int currentRow = topLeft.row(); + + switch (currentColumn) { + case TargetModelNodeRow: { + //updating user data + } break; + case TargetPropertyNameRow: { + updatePropertyName(currentRow); + } break; + case SourceModelNodeRow: { + updateExpression(currentRow); + } break; + case SourcePropertyNameRow: { + updateExpression(currentRow); + } break; + + default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn; + } + + m_lock = false; +} + +void BindingModel::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + resetModel(); +} + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h new file mode 100644 index 0000000000..480ba254ad --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 <modelnode.h> +#include <bindingproperty.h> +#include <variantproperty.h> + +#include <QStandardItemModel> + +namespace QmlDesigner { + +namespace Internal { + +class ConnectionView; + +class BindingModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum ColumnRoles { + TargetModelNodeRow = 0, + TargetPropertyNameRow = 1, + SourceModelNodeRow = 2, + SourcePropertyNameRow = 3 + }; + BindingModel(ConnectionView *parent = nullptr); + void bindingChanged(const BindingProperty &bindingProperty); + void bindingRemoved(const BindingProperty &bindingProperty); + void selectionChanged(const QList<ModelNode> &selectedNodes); + + ConnectionView *connectionView() const; + BindingProperty bindingPropertyForRow(int rowNumber) const; + QStringList possibleTargetProperties(const BindingProperty &bindingProperty) const; + QStringList possibleSourceProperties(const BindingProperty &bindingProperty) const; + void deleteBindindByRow(int rowNumber); + void addBindingForCurrentNode(); + void resetModel(); + +protected: + void addBindingProperty(const BindingProperty &property); + void updateBindingProperty(int rowNumber); + void addModelNode(const ModelNode &modelNode); + void updateExpression(int row); + void updatePropertyName(int rowNumber); + ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const; + void updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty); + int findRowForBinding(const BindingProperty &bindingProperty); + + bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); + + void updateDisplayRole(int row, int columns, const QString &string); + +private: + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); + void handleException(); + +private: + QList<ModelNode> m_selectedModelNodes; + ConnectionView *m_connectionView; + bool m_lock = false; + bool m_handleDataChanged = false; + QString m_exceptionError; + +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri new file mode 100644 index 0000000000..5dfa3160ab --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.pri @@ -0,0 +1,26 @@ +VPATH += $$PWD +INCLUDEPATH += $$PWD + +HEADERS += delegates.h \ + connectionview.h \ + connectionviewwidget.h \ + connectionmodel.h \ + bindingmodel.h \ + dynamicpropertiesmodel.h \ + backendmodel.h \ + $$PWD/addnewbackenddialog.h + +SOURCES += delegates.cpp \ + connectionview.cpp \ + connectionviewwidget.cpp \ + connectionmodel.cpp \ + bindingmodel.cpp \ + dynamicpropertiesmodel.cpp \ + backendmodel.cpp \ + $$PWD/addnewbackenddialog.cpp + +FORMS += \ + connectionviewwidget.ui \ + $$PWD/addnewbackenddialog.ui + +RESOURCES += connectioneditor.qrc diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc new file mode 100644 index 0000000000..a313b6648d --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditor.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/connectionview"> + <file>stylesheet.css</file> + </qresource> +</RCC> diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp new file mode 100644 index 0000000000..80caf51ce0 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** 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 "connectionmodel.h" +#include "connectionview.h" + +#include <bindingproperty.h> +#include <variantproperty.h> +#include <signalhandlerproperty.h> +#include <rewritertransaction.h> +#include <nodeabstractproperty.h> +#include <exception.h> +#include <nodemetainfo.h> + +#include <QStandardItemModel> +#include <QMessageBox> +#include <QTableView> +#include <QTimer> + +namespace { + +QStringList propertyNameListToStringList(const QmlDesigner::PropertyNameList &propertyNameList) +{ + QStringList stringList; + foreach (QmlDesigner::PropertyName propertyName, propertyNameList) { + stringList << QString::fromUtf8(propertyName); + } + return stringList; +} + +bool isConnection(const QmlDesigner::ModelNode &modelNode) +{ + return (modelNode.type() == "Connections" + || modelNode.type() == "QtQuick.Connections" + || modelNode.type() == "Qt.Connections"); + +} + +} //namespace + +namespace QmlDesigner { + +namespace Internal { + +ConnectionModel::ConnectionModel(ConnectionView *parent) + : QStandardItemModel(parent) + , m_connectionView(parent) +{ + connect(this, &QStandardItemModel::dataChanged, this, &ConnectionModel::handleDataChanged); +} + +void ConnectionModel::resetModel() +{ + beginResetModel(); + clear(); + setHorizontalHeaderLabels(QStringList({ tr("Target"), tr("Signal Handler"), tr("Action") })); + + if (connectionView()->isAttached()) { + foreach (const ModelNode modelNode, connectionView()->allModelNodes()) + addModelNode(modelNode); + } + + const int columnWidthTarget = connectionView()->connectionTableView()->columnWidth(0); + connectionView()->connectionTableView()->setColumnWidth(0, columnWidthTarget - 80); + + endResetModel(); +} + +SignalHandlerProperty ConnectionModel::signalHandlerPropertyForRow(int rowNumber) const +{ + const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + + ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + + if (modelNode.isValid()) + return modelNode.signalHandlerProperty(targetPropertyName.toUtf8()); + + return SignalHandlerProperty(); +} + +void ConnectionModel::addModelNode(const ModelNode &modelNode) +{ + if (isConnection(modelNode)) + addConnection(modelNode); +} + +void ConnectionModel::addConnection(const ModelNode &modelNode) +{ + foreach (const AbstractProperty &property, modelNode.properties()) { + if (property.isSignalHandlerProperty() && property.name() != "target") { + addSignalHandler(property.toSignalHandlerProperty()); + } + } +} + +void ConnectionModel::addSignalHandler(const SignalHandlerProperty &signalHandlerProperty) +{ + QStandardItem *targetItem; + QStandardItem *signalItem; + QStandardItem *actionItem; + + QString idLabel; + + ModelNode connectionsModelNode = signalHandlerProperty.parentModelNode(); + + if (connectionsModelNode.bindingProperty("target").isValid()) { + idLabel =connectionsModelNode.bindingProperty("target").expression(); + } + + targetItem = new QStandardItem(idLabel); + updateCustomData(targetItem, signalHandlerProperty); + const QString propertyName = QString::fromUtf8(signalHandlerProperty.name()); + const QString source = signalHandlerProperty.source(); + + signalItem = new QStandardItem(propertyName); + QList<QStandardItem*> items; + + items.append(targetItem); + items.append(signalItem); + + actionItem = new QStandardItem(source); + + items.append(actionItem); + + appendRow(items); +} + +void ConnectionModel::removeModelNode(const ModelNode &modelNode) +{ + if (isConnection(modelNode)) + removeConnection(modelNode); +} + +void ConnectionModel::removeConnection(const ModelNode & /*modelNode*/) +{ + Q_ASSERT_X(false, "not implemented", Q_FUNC_INFO); +} + +void ConnectionModel::updateSource(int row) +{ + SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(row); + + const QString sourceString = data(index(row, SourceRow)).toString(); + + RewriterTransaction transaction = + connectionView()->beginRewriterTransaction(QByteArrayLiteral("ConnectionModel::updateSource")); + + try { + signalHandlerProperty.setSource(sourceString); + transaction.commit(); + } + catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &ConnectionModel::handleException); + } + +} + +void ConnectionModel::updateSignalName(int rowNumber) +{ + SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(rowNumber); + ModelNode connectionNode = signalHandlerProperty.parentModelNode(); + + const PropertyName newName = data(index(rowNumber, TargetPropertyNameRow)).toString().toUtf8(); + if (!newName.isEmpty()) { + connectionView()->executeInTransaction("ConnectionModel::updateSignalName", [=, &connectionNode](){ + + const QString source = signalHandlerProperty.source(); + + connectionNode.signalHandlerProperty(newName).setSource(source); + connectionNode.removeProperty(signalHandlerProperty.name()); + }); + + QStandardItem* idItem = item(rowNumber, 0); + SignalHandlerProperty newSignalHandlerProperty = connectionNode.signalHandlerProperty(newName); + updateCustomData(idItem, newSignalHandlerProperty); + } else { + qWarning() << "BindingModel::updatePropertyName invalid property name"; + } +} + +void ConnectionModel::updateTargetNode(int rowNumber) +{ + SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(rowNumber); + const QString newTarget = data(index(rowNumber, TargetModelNodeRow)).toString(); + ModelNode connectionNode = signalHandlerProperty.parentModelNode(); + + if (!newTarget.isEmpty()) { + connectionView()->executeInTransaction("ConnectionModel::updateTargetNode", [= ,&connectionNode](){ + connectionNode.bindingProperty("target").setExpression(newTarget); + }); + + QStandardItem* idItem = item(rowNumber, 0); + updateCustomData(idItem, signalHandlerProperty); + + } else { + qWarning() << "BindingModel::updatePropertyName invalid target id"; + } +} + +void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty) +{ + item->setData(signalHandlerProperty.parentModelNode().internalId(), Qt::UserRole + 1); + item->setData(signalHandlerProperty.name(), Qt::UserRole + 2); +} + +ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const +{ + BindingProperty bindingProperty = connection.bindingProperty("target"); + + if (bindingProperty.isValid()) { + if (bindingProperty.expression() == QLatin1String("parent")) + return connection.parentProperty().parentModelNode(); + return connectionView()->modelNodeForId(bindingProperty.expression()); + } + + return ModelNode(); +} + +void ConnectionModel::addConnection() +{ + ModelNode rootModelNode = connectionView()->rootModelNode(); + + if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) { + + NodeMetaInfo nodeMetaInfo = connectionView()->model()->metaInfo("QtQuick.Connections"); + + if (nodeMetaInfo.isValid()) { + connectionView()->executeInTransaction("ConnectionModel::addConnection", [=](){ + ModelNode newNode = connectionView()->createModelNode("QtQuick.Connections", + nodeMetaInfo.majorVersion(), + nodeMetaInfo.minorVersion()); + + rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode); + newNode.signalHandlerProperty("onClicked").setSource(QLatin1String("print(\"clicked\")")); + + if (connectionView()->selectedModelNodes().count() == 1 + && !connectionView()->selectedModelNodes().constFirst().id().isEmpty()) { + const ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst(); + newNode.bindingProperty("target").setExpression(selectedNode.id()); + } else { + newNode.bindingProperty("target").setExpression(QLatin1String("parent")); + } + }); + } + } +} + +void ConnectionModel::bindingPropertyChanged(const BindingProperty &bindingProperty) +{ + if (isConnection(bindingProperty.parentModelNode())) + resetModel(); +} + +void ConnectionModel::variantPropertyChanged(const VariantProperty &variantProperty) +{ + if (isConnection(variantProperty.parentModelNode())) + resetModel(); +} + +void ConnectionModel::deleteConnectionByRow(int currentRow) +{ + signalHandlerPropertyForRow(currentRow).parentModelNode().destroy(); +} + +void ConnectionModel::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + resetModel(); +} + +void ConnectionModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (topLeft != bottomRight) { + qWarning() << "ConnectionModel::handleDataChanged multi edit?"; + return; + } + + m_lock = true; + + int currentColumn = topLeft.column(); + int currentRow = topLeft.row(); + + switch (currentColumn) { + case TargetModelNodeRow: { + updateTargetNode(currentRow); + } break; + case TargetPropertyNameRow: { + updateSignalName(currentRow); + } break; + case SourceRow: { + updateSource(currentRow); + } break; + + default: qWarning() << "ConnectionModel::handleDataChanged column" << currentColumn; + } + + m_lock = false; +} + +ConnectionView *ConnectionModel::connectionView() const +{ + return m_connectionView; +} + +QStringList ConnectionModel::getSignalsForRow(int row) const +{ + QStringList stringList; + SignalHandlerProperty signalHandlerProperty = signalHandlerPropertyForRow(row); + + if (signalHandlerProperty.isValid()) { + stringList.append(getPossibleSignalsForConnection(signalHandlerProperty.parentModelNode())); + } + + return stringList; +} + +QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &connection) const +{ + QStringList stringList; + + if (connection.isValid()) { + ModelNode targetNode = getTargetNodeForConnection(connection); + if (targetNode.isValid() && targetNode.metaInfo().isValid()) { + stringList.append(propertyNameListToStringList(targetNode.metaInfo().signalNames())); + } + } + + return stringList; +} + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h new file mode 100644 index 0000000000..2c66b2ef25 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 <QStandardItemModel> + +namespace QmlDesigner { + +class ModelNode; +class BindingProperty; +class SignalHandlerProperty; +class VariantProperty; + +namespace Internal { + +class ConnectionView; + +class ConnectionModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum ColumnRoles { + TargetModelNodeRow = 0, + TargetPropertyNameRow = 1, + SourceRow = 2 + }; + ConnectionModel(ConnectionView *parent = nullptr); + void resetModel(); + SignalHandlerProperty signalHandlerPropertyForRow(int rowNumber) const; + ConnectionView *connectionView() const; + + QStringList getSignalsForRow(int row) const; + ModelNode getTargetNodeForConnection(const ModelNode &connection) const; + + void addConnection(); + + void bindingPropertyChanged(const BindingProperty &bindingProperty); + void variantPropertyChanged(const VariantProperty &variantProperty); + + void deleteConnectionByRow(int currentRow); + +protected: + void addModelNode(const ModelNode &modelNode); + void addConnection(const ModelNode &modelNode); + void addSignalHandler(const SignalHandlerProperty &bindingProperty); + void removeModelNode(const ModelNode &modelNode); + void removeConnection(const ModelNode &modelNode); + void updateSource(int row); + void updateSignalName(int rowNumber); + void updateTargetNode(int rowNumber); + void updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty); + QStringList getPossibleSignalsForConnection(const ModelNode &connection) const; + +private: + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); + void handleException(); + +private: + ConnectionView *m_connectionView; + bool m_lock = false; + QString m_exceptionError; +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp new file mode 100644 index 0000000000..9aef01259e --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** 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 "connectionview.h" +#include "connectionviewwidget.h" + +#include "backendmodel.h" +#include "bindingmodel.h" +#include "connectionmodel.h" +#include "dynamicpropertiesmodel.h" + +#include <bindingproperty.h> +#include <nodeabstractproperty.h> +#include <variantproperty.h> + +namespace QmlDesigner { + +namespace Internal { + +ConnectionView::ConnectionView(QObject *parent) : AbstractView(parent), + m_connectionViewWidget(new ConnectionViewWidget()), + m_connectionModel(new ConnectionModel(this)), + m_bindingModel(new BindingModel(this)), + m_dynamicPropertiesModel(new DynamicPropertiesModel(this)), + m_backendModel(new BackendModel(this)) +{ + connectionViewWidget()->setBindingModel(m_bindingModel); + connectionViewWidget()->setConnectionModel(m_connectionModel); + connectionViewWidget()->setDynamicPropertiesModel(m_dynamicPropertiesModel); + connectionViewWidget()->setBackendModel(m_backendModel); +} + +ConnectionView::~ConnectionView() = default; + +void ConnectionView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + bindingModel()->selectionChanged(QList<ModelNode>()); + dynamicPropertiesModel()->selectionChanged(QList<ModelNode>()); + connectionModel()->resetModel(); + connectionViewWidget()->resetItemViews(); + backendModel()->resetModel(); +} + +void ConnectionView::modelAboutToBeDetached(Model *model) +{ + AbstractView::modelAboutToBeDetached(model); + bindingModel()->selectionChanged(QList<ModelNode>()); + dynamicPropertiesModel()->selectionChanged(QList<ModelNode>()); + connectionModel()->resetModel(); + connectionViewWidget()->resetItemViews(); +} + +void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/) +{ +//bindings + connectionModel()->resetModel(); +} + +void ConnectionView::nodeRemoved(const ModelNode & /*removedNode*/, + const NodeAbstractProperty & /*parentProperty*/, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + connectionModel()->resetModel(); +} + +void ConnectionView::nodeReparented(const ModelNode & /*node*/, const NodeAbstractProperty & /*newPropertyParent*/, + const NodeAbstractProperty & /*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + connectionModel()->resetModel(); +} + +void ConnectionView::nodeIdChanged(const ModelNode & /*node*/, const QString & /*newId*/, const QString & /*oldId*/) +{ + connectionModel()->resetModel(); + bindingModel()->resetModel(); + dynamicPropertiesModel()->resetModel(); +} + +void ConnectionView::propertiesAboutToBeRemoved(const QList<AbstractProperty> & propertyList) +{ + foreach (const AbstractProperty &property, propertyList) { + if (property.isBindingProperty()) { + bindingModel()->bindingRemoved(property.toBindingProperty()); + dynamicPropertiesModel()->bindingRemoved(property.toBindingProperty()); + } else if (property.isVariantProperty()) { + //### dynamicPropertiesModel->bindingRemoved(property.toVariantProperty()); + } + } +} + +void ConnectionView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + foreach (const VariantProperty &variantProperty, propertyList) { + if (variantProperty.isDynamic()) + dynamicPropertiesModel()->variantPropertyChanged(variantProperty); + if (variantProperty.isDynamic() && variantProperty.parentModelNode().isRootNode()) + backendModel()->resetModel(); + + connectionModel()->variantPropertyChanged(variantProperty); + } + +} + +void ConnectionView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + foreach (const BindingProperty &bindingProperty, propertyList) { + bindingModel()->bindingChanged(bindingProperty); + if (bindingProperty.isDynamic()) + dynamicPropertiesModel()->bindingPropertyChanged(bindingProperty); + if (bindingProperty.isDynamic() && bindingProperty.parentModelNode().isRootNode()) + backendModel()->resetModel(); + + connectionModel()->bindingPropertyChanged(bindingProperty); + } +} + +void ConnectionView::selectedNodesChanged(const QList<ModelNode> & selectedNodeList, + const QList<ModelNode> & /*lastSelectedNodeList*/) +{ + bindingModel()->selectionChanged(selectedNodeList); + dynamicPropertiesModel()->selectionChanged(selectedNodeList); + connectionViewWidget()->bindingTableViewSelectionChanged(QModelIndex(), QModelIndex()); + connectionViewWidget()->dynamicPropertiesTableViewSelectionChanged(QModelIndex(), QModelIndex()); + + if (connectionViewWidget()->currentTab() == ConnectionViewWidget::BindingTab + || connectionViewWidget()->currentTab() == ConnectionViewWidget::DynamicPropertiesTab) + emit connectionViewWidget()->setEnabledAddButton(selectedNodeList.count() == 1); +} + +void ConnectionView::importsChanged(const QList<Import> & /*addedImports*/, const QList<Import> & /*removedImports*/) +{ + backendModel()->resetModel(); +} + +WidgetInfo ConnectionView::widgetInfo() +{ + return createWidgetInfo(m_connectionViewWidget.data(), + new WidgetInfo::ToolBarWidgetDefaultFactory<ConnectionViewWidget>(connectionViewWidget()), + QLatin1String("ConnectionView"), + WidgetInfo::LeftPane, + 0, + tr("Connection View")); +} + +bool ConnectionView::hasWidget() const +{ + return true; +} + +QTableView *ConnectionView::connectionTableView() const +{ + return connectionViewWidget()->connectionTableView(); +} + +QTableView *ConnectionView::bindingTableView() const +{ + return connectionViewWidget()->bindingTableView(); +} + +QTableView *ConnectionView::dynamicPropertiesTableView() const +{ + return connectionViewWidget()->dynamicPropertiesTableView(); +} + +QTableView *ConnectionView::backendView() const +{ + return connectionViewWidget()->backendView(); +} + +ConnectionViewWidget *ConnectionView::connectionViewWidget() const +{ + return m_connectionViewWidget.data(); +} + +ConnectionModel *ConnectionView::connectionModel() const +{ + return m_connectionModel; +} + +BindingModel *ConnectionView::bindingModel() const +{ + return m_bindingModel; +} + +DynamicPropertiesModel *ConnectionView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + +BackendModel *ConnectionView::backendModel() const +{ + return m_backendModel; +} + +} // namesapce Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h new file mode 100644 index 0000000000..da7623375a --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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 <abstractview.h> +#include <qmlitemnode.h> + +#include <QPointer> + +QT_BEGIN_NAMESPACE +class QTableView; +class QListView; +QT_END_NAMESPACE + +namespace QmlDesigner { + +namespace Internal { + +class ConnectionViewWidget; +class BindingModel; +class ConnectionModel; +class DynamicPropertiesModel; +class BackendModel; + +class ConnectionView : public AbstractView +{ + Q_OBJECT + +public: + ConnectionView(QObject *parent = nullptr); + ~ConnectionView() override; + + // AbstractView + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + + void nodeCreated(const ModelNode &createdNode) override; + void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; + void nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId) override; + void propertiesAboutToBeRemoved(const QList<AbstractProperty>& propertyList) override; + void variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange) override; + void bindingPropertiesChanged(const QList<BindingProperty>& propertyList, PropertyChangeFlags propertyChange) override; + + void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, + const QList<ModelNode> &lastSelectedNodeList) override; + + void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override; + + WidgetInfo widgetInfo() override; + bool hasWidget() const override; + + QTableView *connectionTableView() const; + QTableView *bindingTableView() const; + QTableView *dynamicPropertiesTableView() const; + QTableView *backendView() const; + +protected: + ConnectionViewWidget *connectionViewWidget() const; + ConnectionModel *connectionModel() const; + BindingModel *bindingModel() const; + DynamicPropertiesModel *dynamicPropertiesModel() const; + BackendModel *backendModel() const; + + +private: //variables + QPointer<ConnectionViewWidget> m_connectionViewWidget; + ConnectionModel *m_connectionModel; + BindingModel *m_bindingModel; + DynamicPropertiesModel *m_dynamicPropertiesModel; + BackendModel *m_backendModel; +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp new file mode 100644 index 0000000000..c9b1277ce4 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** 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 "connectionviewwidget.h" +#include "connectionview.h" +#include "ui_connectionviewwidget.h" + +#include "delegates.h" +#include "backendmodel.h" +#include "bindingmodel.h" +#include "connectionmodel.h" +#include "dynamicpropertiesmodel.h" +#include "theme.h" + +#include <designersettings.h> +#include <qmldesignerplugin.h> + +#include <coreplugin/coreconstants.h> +#include <utils/fileutils.h> +#include <utils/utilsicons.h> + +#include <QToolButton> +#include <QStyleFactory> + +namespace QmlDesigner { + +namespace Internal { + +ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : + QFrame(parent), + ui(new Ui::ConnectionViewWidget) +{ + + setWindowTitle(tr("Connections", "Title of connection view")); + ui->setupUi(this); + + QStyle *style = QStyleFactory::create("fusion"); + setStyle(style); + + setStyleSheet(Theme::replaceCssColors(QLatin1String(Utils::FileReader::fetchQrc(QLatin1String(":/connectionview/stylesheet.css"))))); + + //ui->tabWidget->tabBar()->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + ui->tabBar->setUsesScrollButtons(true); + ui->tabBar->setElideMode(Qt::ElideRight); + + ui->tabBar->addTab(tr("Connections", "Title of connection view")); + ui->tabBar->addTab(tr("Bindings", "Title of connection view")); + ui->tabBar->addTab(tr("Properties", "Title of dynamic properties view")); + + auto settings = QmlDesignerPlugin::instance()->settings(); + + if (!settings.value(DesignerSettingsKey::STANDALONE_MODE).toBool()) + ui->tabBar->addTab(tr("Backends", "Title of dynamic properties view")); + + ui->tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + + const QString themedScrollBarCss = Theme::replaceCssColors( + QLatin1String(Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css")))); + + ui->connectionView->setStyleSheet(themedScrollBarCss); + ui->bindingView->setStyleSheet(themedScrollBarCss); + ui->dynamicPropertiesView->setStyleSheet(themedScrollBarCss); + ui->backendView->setStyleSheet(themedScrollBarCss); + + connect(ui->tabBar, &QTabBar::currentChanged, + ui->stackedWidget, &QStackedWidget::setCurrentIndex); + + connect(ui->tabBar, &QTabBar::currentChanged, + this, &ConnectionViewWidget::handleTabChanged); + + ui->stackedWidget->setCurrentIndex(0); +} + +ConnectionViewWidget::~ConnectionViewWidget() +{ + delete ui; +} + +void ConnectionViewWidget::setBindingModel(BindingModel *model) +{ + ui->bindingView->setModel(model); + ui->bindingView->verticalHeader()->hide(); + ui->bindingView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->bindingView->setItemDelegate(new BindingDelegate); + connect(ui->bindingView->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &ConnectionViewWidget::bindingTableViewSelectionChanged); +} + +void ConnectionViewWidget::setConnectionModel(ConnectionModel *model) +{ + ui->connectionView->setModel(model); + ui->connectionView->verticalHeader()->hide(); + ui->connectionView->horizontalHeader()->setDefaultSectionSize(160); + ui->connectionView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->connectionView->setItemDelegate(new ConnectionDelegate); + connect(ui->connectionView->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &ConnectionViewWidget::connectionTableViewSelectionChanged); + +} + +void ConnectionViewWidget::setDynamicPropertiesModel(DynamicPropertiesModel *model) +{ + ui->dynamicPropertiesView->setModel(model); + ui->dynamicPropertiesView->verticalHeader()->hide(); + ui->dynamicPropertiesView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->dynamicPropertiesView->setItemDelegate(new DynamicPropertiesDelegate); + connect(ui->dynamicPropertiesView->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &ConnectionViewWidget::dynamicPropertiesTableViewSelectionChanged); +} + +void ConnectionViewWidget::setBackendModel(BackendModel *model) +{ + ui->backendView->setModel(model); + ui->backendView->verticalHeader()->hide(); + ui->backendView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->backendView->setItemDelegate(new BackendDelegate); + model->resetModel(); + connect(ui->backendView->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &ConnectionViewWidget::backendTableViewSelectionChanged); +} + +QList<QToolButton *> ConnectionViewWidget::createToolBarWidgets() +{ + QList<QToolButton *> buttons; + + buttons << new QToolButton(); + buttons.constLast()->setIcon(Utils::Icons::PLUS_TOOLBAR.icon()); + buttons.constLast()->setToolTip(tr("Add binding or connection.")); + connect(buttons.constLast(), &QAbstractButton::clicked, this, &ConnectionViewWidget::addButtonClicked); + connect(this, &ConnectionViewWidget::setEnabledAddButton, buttons.constLast(), &QWidget::setEnabled); + + buttons << new QToolButton(); + buttons.constLast()->setIcon(Utils::Icons::MINUS.icon()); + buttons.constLast()->setToolTip(tr("Remove selected binding or connection.")); + buttons.constLast()->setShortcut(QKeySequence(Qt::Key_Delete)); + connect(buttons.constLast(), &QAbstractButton::clicked, this, &ConnectionViewWidget::removeButtonClicked); + connect(this, &ConnectionViewWidget::setEnabledRemoveButton, buttons.constLast(), &QWidget::setEnabled); + + return buttons; +} + +ConnectionViewWidget::TabStatus ConnectionViewWidget::currentTab() const +{ + switch (ui->stackedWidget->currentIndex()) { + case 0: return ConnectionTab; + case 1: return BindingTab; + case 2: return DynamicPropertiesTab; + case 3: return BackendTab; + default: return InvalidTab; + } +} + +void ConnectionViewWidget::resetItemViews() +{ + if (currentTab() == ConnectionTab) { + ui->connectionView->selectionModel()->clear(); + + } else if (currentTab() == BindingTab) { + ui->bindingView->selectionModel()->clear(); + + } else if (currentTab() == DynamicPropertiesTab) { + ui->dynamicPropertiesView->selectionModel()->clear(); + } else if (currentTab() == BackendTab) { + ui->backendView->selectionModel()->clear(); + } + invalidateButtonStatus(); +} + +void ConnectionViewWidget::invalidateButtonStatus() +{ + if (currentTab() == ConnectionTab) { + emit setEnabledRemoveButton(ui->connectionView->selectionModel()->hasSelection()); + emit setEnabledAddButton(true); + } else if (currentTab() == BindingTab) { + emit setEnabledRemoveButton(ui->bindingView->selectionModel()->hasSelection()); + auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model()); + emit setEnabledAddButton(bindingModel->connectionView()->model() && + bindingModel->connectionView()->selectedModelNodes().count() == 1); + + } else if (currentTab() == DynamicPropertiesTab) { + emit setEnabledRemoveButton(ui->dynamicPropertiesView->selectionModel()->hasSelection()); + auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model()); + emit setEnabledAddButton(dynamicPropertiesModel->connectionView()->model() && + dynamicPropertiesModel->connectionView()->selectedModelNodes().count() == 1); + } else if (currentTab() == BackendTab) { + emit setEnabledAddButton(true); + emit setEnabledRemoveButton(ui->backendView->selectionModel()->hasSelection()); + } +} + +QTableView *ConnectionViewWidget::connectionTableView() const +{ + return ui->connectionView; +} + +QTableView *ConnectionViewWidget::bindingTableView() const +{ + return ui->bindingView; +} + +QTableView *ConnectionViewWidget::dynamicPropertiesTableView() const +{ + return ui->dynamicPropertiesView; +} + +QTableView *ConnectionViewWidget::backendView() const +{ + return ui->backendView; +} + +void ConnectionViewWidget::handleTabChanged(int) +{ + invalidateButtonStatus(); +} + +void ConnectionViewWidget::removeButtonClicked() +{ + if (currentTab() == ConnectionTab) { + if (ui->connectionView->selectionModel()->selectedRows().isEmpty()) + return; + int currentRow = ui->connectionView->selectionModel()->selectedRows().constFirst().row(); + auto connectionModel = qobject_cast<ConnectionModel*>(ui->connectionView->model()); + if (connectionModel) { + connectionModel->deleteConnectionByRow(currentRow); + } + } else if (currentTab() == BindingTab) { + if (ui->bindingView->selectionModel()->selectedRows().isEmpty()) + return; + int currentRow = ui->bindingView->selectionModel()->selectedRows().constFirst().row(); + auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model()); + if (bindingModel) { + bindingModel->deleteBindindByRow(currentRow); + } + } else if (currentTab() == DynamicPropertiesTab) { + if (ui->dynamicPropertiesView->selectionModel()->selectedRows().isEmpty()) + return; + int currentRow = ui->dynamicPropertiesView->selectionModel()->selectedRows().constFirst().row(); + auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model()); + if (dynamicPropertiesModel) + dynamicPropertiesModel->deleteDynamicPropertyByRow(currentRow); + } else if (currentTab() == BackendTab) { + int currentRow = ui->backendView->selectionModel()->selectedRows().constFirst().row(); + auto backendModel = qobject_cast<BackendModel*>(ui->backendView->model()); + if (backendModel) + backendModel->deletePropertyByRow(currentRow); + } + + invalidateButtonStatus(); +} + +void ConnectionViewWidget::addButtonClicked() +{ + + if (currentTab() == ConnectionTab) { + auto connectionModel = qobject_cast<ConnectionModel*>(ui->connectionView->model()); + if (connectionModel) { + connectionModel->addConnection(); + } + } else if (currentTab() == BindingTab) { + auto bindingModel = qobject_cast<BindingModel*>(ui->bindingView->model()); + if (bindingModel) { + bindingModel->addBindingForCurrentNode(); + } + + } else if (currentTab() == DynamicPropertiesTab) { + auto dynamicPropertiesModel = qobject_cast<DynamicPropertiesModel*>(ui->dynamicPropertiesView->model()); + if (dynamicPropertiesModel) + dynamicPropertiesModel->addDynamicPropertyForCurrentNode(); + } else if (currentTab() == BackendTab) { + auto backendModel = qobject_cast<BackendModel*>(ui->backendView->model()); + if (backendModel) + backendModel->addNewBackend(); + } + + invalidateButtonStatus(); +} + +void ConnectionViewWidget::bindingTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + if (currentTab() == BindingTab) { + if (current.isValid()) { + emit setEnabledRemoveButton(true); + } else { + emit setEnabledRemoveButton(false); + } + } +} + +void ConnectionViewWidget::connectionTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + if (currentTab() == ConnectionTab) { + if (current.isValid()) { + emit setEnabledRemoveButton(true); + } else { + emit setEnabledRemoveButton(false); + } + } +} + +void ConnectionViewWidget::dynamicPropertiesTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + if (currentTab() == DynamicPropertiesTab) { + if (current.isValid()) { + emit setEnabledRemoveButton(true); + } else { + emit setEnabledRemoveButton(false); + } + } +} + +void ConnectionViewWidget::backendTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*revious*/) +{ + if (currentTab() == BackendTab) { + if (current.isValid()) { + emit setEnabledRemoveButton(true); + } else { + emit setEnabledRemoveButton(false); + } + } + +} + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h new file mode 100644 index 0000000000..2bcc3932c1 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 <QFrame> +#include <QAbstractItemView> + +QT_BEGIN_NAMESPACE +class QToolButton; +class QTableView; +class QListView; +QT_END_NAMESPACE + +namespace QmlDesigner { + +namespace Ui { class ConnectionViewWidget; } + +namespace Internal { + +class BindingModel; +class ConnectionModel; +class DynamicPropertiesModel; +class BackendModel; + +class ConnectionViewWidget : public QFrame +{ + Q_OBJECT + +public: + + enum TabStatus { + ConnectionTab, + BindingTab, + DynamicPropertiesTab, + BackendTab, + InvalidTab + }; + + explicit ConnectionViewWidget(QWidget *parent = nullptr); + ~ConnectionViewWidget() override; + + void setBindingModel(BindingModel *model); + void setConnectionModel(ConnectionModel *model); + void setDynamicPropertiesModel(DynamicPropertiesModel *model); + void setBackendModel(BackendModel *model); + + QList<QToolButton*> createToolBarWidgets(); + + TabStatus currentTab() const; + + void resetItemViews(); + void invalidateButtonStatus(); + + QTableView *connectionTableView() const; + QTableView *bindingTableView() const; + QTableView *dynamicPropertiesTableView() const; + QTableView *backendView() const; + + void bindingTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void connectionTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void dynamicPropertiesTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void backendTableViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + +signals: + void setEnabledAddButton(bool enabled); + void setEnabledRemoveButton(bool enabled); + +private: + void handleTabChanged(int i); + void removeButtonClicked(); + void addButtonClicked(); + +private: + Ui::ConnectionViewWidget *ui; +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui new file mode 100644 index 0000000000..18df078ec6 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.ui @@ -0,0 +1,273 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::ConnectionViewWidget</class> + <widget class="QWidget" name="QmlDesigner::ConnectionViewWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>994</width> + <height>611</height> + </rect> + </property> + <property name="windowTitle"> + <string>Connections</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="1" column="0"> + <widget class="QWidget" name="widgetSpacer" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>4</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>4</height> + </size> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QTabBar" name="tabBar" native="true"/> + </item> + <item row="2" column="0"> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>3</number> + </property> + <widget class="QWidget" name="connectionViewPage"> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="3" column="0" colspan="5"> + <widget class="QTableView" name="connectionView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="bindingViewPage"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="2" column="0" colspan="3"> + <widget class="QTableView" name="bindingView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="dynamicPropertiesPage"> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QTableView" name="dynamicPropertiesView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="backendViewPage"> + <layout class="QGridLayout" name="gridLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QTableView" name="backendView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + <zorder>stackedWidget</zorder> + <zorder>tabBar</zorder> + <zorder>widgetSpacer</zorder> + </widget> + <customwidgets> + <customwidget> + <class>QTabBar</class> + <extends>QWidget</extends> + <header location="global">qtabbar.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp new file mode 100644 index 0000000000..555f448858 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.cpp @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** 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 "delegates.h" + +#include "backendmodel.h" +#include "connectionmodel.h" +#include "bindingmodel.h" +#include "dynamicpropertiesmodel.h" +#include "connectionview.h" + +#include <bindingproperty.h> + +#include <utils/qtcassert.h> + +#include <QStyleFactory> +#include <QItemEditorFactory> +#include <QDebug> + +namespace QmlDesigner { + +namespace Internal { + +QStringList prependOnForSignalHandler(const QStringList &signalNames) +{ + QStringList signalHandlerNames; + foreach (const QString &signalName, signalNames) { + QString signalHandlerName = signalName; + if (!signalHandlerName.isEmpty()) { + QChar firstChar = signalHandlerName.at(0).toUpper(); + signalHandlerName[0] = firstChar; + signalHandlerName.prepend(QLatin1String("on")); + signalHandlerNames.append(signalHandlerName); + } + } + return signalHandlerNames; +} + +PropertiesComboBox::PropertiesComboBox(QWidget *parent) : QComboBox(parent) +{ + setEditable(true); + setValidator(new QRegularExpressionValidator(QRegularExpression(QLatin1String("[a-z|A-Z|0-9|._-]*")), this)); +} + +QString PropertiesComboBox::text() const +{ + return currentText(); +} + +void PropertiesComboBox::setText(const QString &text) +{ + setEditText(text); +} + +void PropertiesComboBox::disableValidator() +{ + setValidator(nullptr); +} + +ConnectionComboBox::ConnectionComboBox(QWidget *parent) : PropertiesComboBox(parent) +{ +} + +QString ConnectionComboBox::text() const +{ + int index = findText(currentText()); + if (index > -1) { + QVariant variantData = itemData(index); + if (variantData.isValid()) + return variantData.toString(); + } + + return currentText(); +} + +ConnectionEditorDelegate::ConnectionEditorDelegate(QWidget *parent) + : QStyledItemDelegate(parent) +{ +} + +void ConnectionEditorDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + opt.state &= ~QStyle::State_HasFocus; + QStyledItemDelegate::paint(painter, opt, index); +} + +BindingDelegate::BindingDelegate(QWidget *parent) : ConnectionEditorDelegate(parent) +{ + static QItemEditorFactory *factory = nullptr; + if (factory == nullptr) { + factory = new QItemEditorFactory; + QItemEditorCreatorBase *creator + = new QItemEditorCreator<PropertiesComboBox>("text"); + factory->registerEditor(QVariant::String, creator); + } + + setItemEditorFactory(factory); +} + +QWidget *BindingDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); + + const auto model = qobject_cast<const BindingModel*>(index.model()); + if (!model) { + qWarning() << "BindingDelegate::createEditor no model"; + return widget; + } + if (!model->connectionView()) { + qWarning() << "BindingDelegate::createEditor no connection view"; + return widget; + } + + model->connectionView()->allModelNodes(); + + auto bindingComboBox = qobject_cast<PropertiesComboBox*>(widget); + if (!bindingComboBox) { + qWarning() << "BindingDelegate::createEditor no bindingComboBox"; + return widget; + } + + BindingProperty bindingProperty = model->bindingPropertyForRow(index.row()); + + switch (index.column()) { + case BindingModel::TargetModelNodeRow: + return nullptr; //no editor + case BindingModel::TargetPropertyNameRow: { + bindingComboBox->addItems(model->possibleTargetProperties(bindingProperty)); + } break; + case BindingModel::SourceModelNodeRow: { + foreach (const ModelNode &modelNode, model->connectionView()->allModelNodes()) { + if (!modelNode.id().isEmpty()) { + bindingComboBox->addItem(modelNode.id()); + } + } + if (!bindingProperty.parentModelNode().isRootNode()) + bindingComboBox->addItem(QLatin1String("parent")); + } break; + case BindingModel::SourcePropertyNameRow: { + bindingComboBox->addItems(model->possibleSourceProperties(bindingProperty)); + bindingComboBox->disableValidator(); + } break; + default: qWarning() << "BindingDelegate::createEditor column" << index.column(); + } + + connect(bindingComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() { + auto delegate = const_cast<BindingDelegate*>(this); + emit delegate->commitData(bindingComboBox); + }); + + return widget; +} + +DynamicPropertiesDelegate::DynamicPropertiesDelegate(QWidget *parent) : ConnectionEditorDelegate(parent) +{ +// static QItemEditorFactory *factory = 0; +// if (factory == 0) { +// factory = new QItemEditorFactory; +// QItemEditorCreatorBase *creator +// = new QItemEditorCreator<DynamicPropertiesComboBox>("text"); +// factory->registerEditor(QVariant::String, creator); +// } + +// setItemEditorFactory(factory); +} + +QWidget *DynamicPropertiesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); + + const auto model = qobject_cast<const DynamicPropertiesModel*>(index.model()); + if (!model) { + qWarning() << "BindingDelegate::createEditor no model"; + return widget; + } + + if (!model->connectionView()) { + qWarning() << "BindingDelegate::createEditor no connection view"; + return widget; + } + model->connectionView()->allModelNodes(); + + switch (index.column()) { + case DynamicPropertiesModel::TargetModelNodeRow: { + return nullptr; //no editor + }; + case DynamicPropertiesModel::PropertyNameRow: { + return QStyledItemDelegate::createEditor(parent, option, index); + }; + case DynamicPropertiesModel::PropertyTypeRow: { + + auto dynamicPropertiesComboBox = new PropertiesComboBox(parent); + connect(dynamicPropertiesComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() { + auto delegate = const_cast<DynamicPropertiesDelegate*>(this); + emit delegate->commitData(dynamicPropertiesComboBox); + }); + + dynamicPropertiesComboBox->addItem(QLatin1String("alias")); + //dynamicPropertiesComboBox->addItem(QLatin1String("Item")); + dynamicPropertiesComboBox->addItem(QLatin1String("real")); + dynamicPropertiesComboBox->addItem(QLatin1String("int")); + dynamicPropertiesComboBox->addItem(QLatin1String("string")); + dynamicPropertiesComboBox->addItem(QLatin1String("bool")); + dynamicPropertiesComboBox->addItem(QLatin1String("url")); + dynamicPropertiesComboBox->addItem(QLatin1String("color")); + dynamicPropertiesComboBox->addItem(QLatin1String("variant")); + return dynamicPropertiesComboBox; + }; + case DynamicPropertiesModel::PropertyValueRow: { + return QStyledItemDelegate::createEditor(parent, option, index); + }; + default: qWarning() << "BindingDelegate::createEditor column" << index.column(); + } + + return nullptr; +} + +ConnectionDelegate::ConnectionDelegate(QWidget *parent) : ConnectionEditorDelegate(parent) +{ + static QItemEditorFactory *factory = nullptr; + if (factory == nullptr) { + factory = new QItemEditorFactory; + QItemEditorCreatorBase *creator + = new QItemEditorCreator<ConnectionComboBox>("text"); + factory->registerEditor(QVariant::String, creator); + } + + setItemEditorFactory(factory); +} + +QWidget *ConnectionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + + QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); + + const auto connectionModel = qobject_cast<const ConnectionModel*>(index.model()); + + auto connectionComboBox = qobject_cast<ConnectionComboBox*>(widget); + + if (!connectionModel) { + qWarning() << "ConnectionDelegate::createEditor no model"; + return widget; + } + + if (!connectionModel->connectionView()) { + qWarning() << "ConnectionDelegate::createEditor no connection view"; + return widget; + } + + if (!connectionComboBox) { + qWarning() << "ConnectionDelegate::createEditor no bindingComboBox"; + return widget; + } + + switch (index.column()) { + case ConnectionModel::TargetModelNodeRow: { + foreach (const ModelNode &modelNode, connectionModel->connectionView()->allModelNodes()) { + if (!modelNode.id().isEmpty()) { + connectionComboBox->addItem(modelNode.id()); + } + } + } break; + case ConnectionModel::TargetPropertyNameRow: { + connectionComboBox->addItems(prependOnForSignalHandler(connectionModel->getSignalsForRow(index.row()))); + } break; + case ConnectionModel::SourceRow: { + ModelNode rootModelNode = connectionModel->connectionView()->rootModelNode(); + if (QmlItemNode::isValidQmlItemNode(rootModelNode) && !rootModelNode.id().isEmpty()) { + + QString itemText = tr("Change to default state"); + QString source = QString::fromLatin1("{ %1.state = \"\" }").arg(rootModelNode.id()); + connectionComboBox->addItem(itemText, source); + connectionComboBox->disableValidator(); + + foreach (const QmlModelState &state, QmlItemNode(rootModelNode).states().allStates()) { + QString itemText = tr("Change state to %1").arg(state.name()); + QString source = QString::fromLatin1("{ %1.state = \"%2\" }").arg(rootModelNode.id()).arg(state.name()); + connectionComboBox->addItem(itemText, source); + } + } + connectionComboBox->disableValidator(); + } break; + + default: qWarning() << "ConnectionDelegate::createEditor column" << index.column(); + } + + connect(connectionComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() { + auto delegate = const_cast<ConnectionDelegate*>(this); + emit delegate->commitData(connectionComboBox); + }); + + return widget; +} + +BackendDelegate::BackendDelegate(QWidget *parent) : ConnectionEditorDelegate(parent) +{ +} + +QWidget *BackendDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const auto model = qobject_cast<const BackendModel*>(index.model()); + + model->connectionView()->allModelNodes(); + + QWidget *widget = QStyledItemDelegate::createEditor(parent, option, index); + + QTC_ASSERT(model, return widget); + QTC_ASSERT(model->connectionView(), return widget); + + switch (index.column()) { + case BackendModel::TypeNameColumn: { + auto backendComboBox = new PropertiesComboBox(parent); + backendComboBox->addItems(model->possibleCppTypes()); + connect(backendComboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() { + auto delegate = const_cast<BackendDelegate*>(this); + emit delegate->commitData(backendComboBox); + }); + return backendComboBox; + }; + case BackendModel::PropertyNameColumn: { + return widget; + }; + case BackendModel::IsSingletonColumn: { + return nullptr; //no editor + }; + case BackendModel::IsLocalColumn: { + return nullptr; //no editor + }; + default: qWarning() << "BackendDelegate::createEditor column" << index.column(); + } + + return widget; +} + +} // namesapce Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/delegates.h b/src/plugins/qmldesigner/components/connectioneditor/delegates.h new file mode 100644 index 0000000000..b9792293ac --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/delegates.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 <QStandardItem> +#include <QStyledItemDelegate> +#include <QComboBox> + +namespace QmlDesigner { + +namespace Internal { + +class PropertiesComboBox : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText USER true) +public: + PropertiesComboBox(QWidget *parent = nullptr); + + virtual QString text() const; + void setText(const QString &text); + void disableValidator(); +}; + +class ConnectionComboBox : public PropertiesComboBox +{ + Q_OBJECT +public: + ConnectionComboBox(QWidget *parent = nullptr); + QString text() const override; +}; + +class ConnectionEditorDelegate : public QStyledItemDelegate +{ +public: + ConnectionEditorDelegate(QWidget *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +class BindingDelegate : public ConnectionEditorDelegate +{ +public: + BindingDelegate(QWidget *parent = nullptr); + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +class DynamicPropertiesDelegate : public ConnectionEditorDelegate +{ +public: + DynamicPropertiesDelegate(QWidget *parent = nullptr); + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + + +class ConnectionDelegate : public ConnectionEditorDelegate +{ + Q_OBJECT +public: + ConnectionDelegate(QWidget *parent = nullptr); + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +class BackendDelegate : public ConnectionEditorDelegate +{ + Q_OBJECT +public: + BackendDelegate(QWidget *parent = nullptr); + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp new file mode 100644 index 0000000000..0a08e5c883 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -0,0 +1,677 @@ +/**************************************************************************** +** +** 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 "dynamicpropertiesmodel.h" + +#include "connectionview.h" + +#include <nodemetainfo.h> +#include <nodeproperty.h> +#include <variantproperty.h> +#include <bindingproperty.h> +#include <rewritingexception.h> +#include <rewritertransaction.h> + +#include <utils/fileutils.h> + +#include <QMessageBox> +#include <QTimer> +#include <QUrl> + +namespace { + +bool compareVariantProperties(const QmlDesigner::VariantProperty &variantProperty01, const QmlDesigner::VariantProperty &variantProperty02) +{ + if (variantProperty01.parentModelNode() != variantProperty02.parentModelNode()) + return false; + if (variantProperty01.name() != variantProperty02.name()) + return false; + return true; +} + +QString idOrTypeNameForNode(const QmlDesigner::ModelNode &modelNode) +{ + QString idLabel = modelNode.id(); + if (idLabel.isEmpty()) + idLabel = modelNode.simplifiedTypeName(); + + return idLabel; +} + +QmlDesigner::PropertyName unusedProperty(const QmlDesigner::ModelNode &modelNode) +{ + QmlDesigner::PropertyName propertyName = "property"; + int i = 0; + if (modelNode.metaInfo().isValid()) { + while (true) { + const QmlDesigner::PropertyName currentPropertyName = propertyName + QString::number(i).toLatin1(); + if (!modelNode.hasProperty(currentPropertyName) && !modelNode.metaInfo().hasProperty(currentPropertyName)) + return currentPropertyName; + i++; + } + } + + return propertyName; +} + +QVariant convertVariantForTypeName(const QVariant &variant, const QmlDesigner::TypeName &typeName) +{ + QVariant returnValue = variant; + + if (typeName == "int") { + bool ok; + returnValue = variant.toInt(&ok); + if (!ok) + returnValue = 0; + } else if (typeName == "real") { + bool ok; + returnValue = variant.toReal(&ok); + if (!ok) + returnValue = 0.0; + + } else if (typeName == "string") { + returnValue = variant.toString(); + + } else if (typeName == "bool") { + returnValue = variant.toBool(); + } else if (typeName == "url") { + returnValue = variant.toUrl(); + } else if (typeName == "color") { + if (QColor::isValidColor(variant.toString())) { + returnValue = variant.toString(); + } else { + returnValue = QColor(Qt::black); + } + } else if (typeName == "Item") { + returnValue = 0; + } + + return returnValue; +} + +} //internal namespace + +namespace QmlDesigner { + +namespace Internal { + +DynamicPropertiesModel::DynamicPropertiesModel(ConnectionView *parent) + : QStandardItemModel(parent) + , m_connectionView(parent) +{ + connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged); +} + +void DynamicPropertiesModel::resetModel() +{ + beginResetModel(); + clear(); + setHorizontalHeaderLabels(QStringList({ tr("Item"), tr("Property"), tr("Property Type"), + tr("Property Value") })); + + foreach (const ModelNode modelNode, m_selectedModelNodes) + addModelNode(modelNode); + + endResetModel(); +} + +void DynamicPropertiesModel::bindingPropertyChanged(const BindingProperty &bindingProperty) +{ + if (!bindingProperty.isDynamic()) + return; + + m_handleDataChanged = false; + + QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes(); + if (!selectedNodes.contains(bindingProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForBindingProperty(bindingProperty); + + if (rowNumber == -1) { + addBindingProperty(bindingProperty); + } else { + updateBindingProperty(rowNumber); + } + } + + m_handleDataChanged = true; +} + +void DynamicPropertiesModel::variantPropertyChanged(const VariantProperty &variantProperty) +{ + if (!variantProperty.isDynamic()) + return; + + m_handleDataChanged = false; + + QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes(); + if (!selectedNodes.contains(variantProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForVariantProperty(variantProperty); + + if (rowNumber == -1) { + addVariantProperty(variantProperty); + } else { + updateVariantProperty(rowNumber); + } + } + + m_handleDataChanged = true; +} + +void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProperty) +{ + m_handleDataChanged = false; + + QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes(); + if (!selectedNodes.contains(bindingProperty.parentModelNode())) + return; + if (!m_lock) { + int rowNumber = findRowForBindingProperty(bindingProperty); + removeRow(rowNumber); + } + + m_handleDataChanged = true; +} + +void DynamicPropertiesModel::selectionChanged(const QList<ModelNode> &selectedNodes) +{ + m_handleDataChanged = false; + m_selectedModelNodes = selectedNodes; + resetModel(); + m_handleDataChanged = true; +} + +ConnectionView *DynamicPropertiesModel::connectionView() const +{ + return m_connectionView; +} + +BindingProperty DynamicPropertiesModel::bindingPropertyForRow(int rowNumber) const +{ + + const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + + ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + + if (modelNode.isValid()) + return modelNode.bindingProperty(targetPropertyName.toUtf8()); + + return BindingProperty(); +} + +VariantProperty DynamicPropertiesModel::variantPropertyForRow(int rowNumber) const +{ + const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + + ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); + + if (modelNode.isValid()) + return modelNode.variantProperty(targetPropertyName.toUtf8()); + + return VariantProperty(); +} + +QStringList DynamicPropertiesModel::possibleTargetProperties(const BindingProperty &bindingProperty) const +{ + const ModelNode modelNode = bindingProperty.parentModelNode(); + + if (!modelNode.isValid()) { + qWarning() << " BindingModel::possibleTargetPropertiesForRow invalid model node"; + return QStringList(); + } + + NodeMetaInfo metaInfo = modelNode.metaInfo(); + + if (metaInfo.isValid()) { + QStringList possibleProperties; + foreach (const PropertyName &propertyName, metaInfo.propertyNames()) { + if (metaInfo.propertyIsWritable(propertyName)) + possibleProperties << QString::fromUtf8(propertyName); + } + + return possibleProperties; + } + + return QStringList(); +} + +void DynamicPropertiesModel::addDynamicPropertyForCurrentNode() +{ + if (connectionView()->selectedModelNodes().count() == 1) { + const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst(); + if (modelNode.isValid()) { + try { + modelNode.variantProperty(unusedProperty(modelNode)).setDynamicTypeNameAndValue("string", QLatin1String("none.none")); + } catch (RewritingException &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException); + } + } + } else { + qWarning() << " BindingModel::addBindingForCurrentNode not one node selected"; + } +} + +QStringList DynamicPropertiesModel::possibleSourceProperties(const BindingProperty &bindingProperty) const +{ + const QString expression = bindingProperty.expression(); + const QStringList stringlist = expression.split(QLatin1String(".")); + + PropertyName typeName; + + if (bindingProperty.parentModelNode().metaInfo().isValid()) { + typeName = bindingProperty.parentModelNode().metaInfo().propertyTypeName(bindingProperty.name()); + } else { + qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for target node"; + } + + const QString &id = stringlist.constFirst(); + + ModelNode modelNode = getNodeByIdOrParent(id, bindingProperty.parentModelNode()); + + if (!modelNode.isValid()) { + qWarning() << " BindingModel::possibleSourcePropertiesForRow invalid model node"; + return QStringList(); + } + + NodeMetaInfo metaInfo = modelNode.metaInfo(); + + if (metaInfo.isValid()) { + QStringList possibleProperties; + foreach (const PropertyName &propertyName, metaInfo.propertyNames()) { + if (metaInfo.propertyTypeName(propertyName) == typeName) //### todo proper check + possibleProperties << QString::fromUtf8(propertyName); + } + + return possibleProperties; + } else { + qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for source node"; + } + + return QStringList(); +} + +void DynamicPropertiesModel::deleteDynamicPropertyByRow(int rowNumber) +{ + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + if (bindingProperty.isValid()) { + bindingProperty.parentModelNode().removeProperty(bindingProperty.name()); + } + + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + + if (variantProperty.isValid()) { + variantProperty.parentModelNode().removeProperty(variantProperty.name()); + } + + resetModel(); +} + +void DynamicPropertiesModel::addProperty(const QVariant &propertyValue, + const QString &propertyType, + const AbstractProperty &abstractProperty) +{ + QList<QStandardItem*> items; + + QStandardItem *idItem; + QStandardItem *propertyNameItem; + QStandardItem *propertyTypeItem; + QStandardItem *propertyValueItem; + + idItem = new QStandardItem(idOrTypeNameForNode(abstractProperty.parentModelNode())); + updateCustomData(idItem, abstractProperty); + + propertyNameItem = new QStandardItem(QString::fromUtf8(abstractProperty.name())); + + items.append(idItem); + items.append(propertyNameItem); + + + propertyTypeItem = new QStandardItem(propertyType); + items.append(propertyTypeItem); + + propertyValueItem = new QStandardItem(); + propertyValueItem->setData(propertyValue, Qt::DisplayRole); + items.append(propertyValueItem); + + appendRow(items); +} + +void DynamicPropertiesModel::addBindingProperty(const BindingProperty &property) +{ + QVariant value = property.expression(); + QString type = QString::fromLatin1(property.dynamicTypeName()); + addProperty(value, type, property); +} + +void DynamicPropertiesModel::addVariantProperty(const VariantProperty &property) +{ + QVariant value = property.value(); + QString type = QString::fromLatin1(property.dynamicTypeName()); + addProperty(value, type, property); +} + +void DynamicPropertiesModel::updateBindingProperty(int rowNumber) +{ + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + if (bindingProperty.isValid()) { + QString propertyName = QString::fromUtf8(bindingProperty.name()); + updateDisplayRole(rowNumber, PropertyNameRow, propertyName); + QString value = bindingProperty.expression(); + QString type = QString::fromUtf8(bindingProperty.dynamicTypeName()); + updateDisplayRole(rowNumber, PropertyTypeRow, type); + updateDisplayRole(rowNumber, PropertyValueRow, value); + } +} + +void DynamicPropertiesModel::updateVariantProperty(int rowNumber) +{ + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + + if (variantProperty.isValid()) { + QString propertyName = QString::fromUtf8(variantProperty.name()); + updateDisplayRole(rowNumber, PropertyNameRow, propertyName); + QVariant value = variantProperty.value(); + QString type = QString::fromUtf8(variantProperty.dynamicTypeName()); + updateDisplayRole(rowNumber, PropertyTypeRow, type); + + updateDisplayRoleFromVariant(rowNumber, PropertyValueRow, value); + } +} + +void DynamicPropertiesModel::addModelNode(const ModelNode &modelNode) +{ + foreach (const BindingProperty &bindingProperty, modelNode.bindingProperties()) { + if (bindingProperty.isDynamic()) + addBindingProperty(bindingProperty); + } + + foreach (const VariantProperty &variantProperty, modelNode.variantProperties()) { + if (variantProperty.isDynamic()) + addVariantProperty(variantProperty); + } +} + +void DynamicPropertiesModel::updateValue(int row) +{ + BindingProperty bindingProperty = bindingPropertyForRow(row); + + if (bindingProperty.isBindingProperty()) { + const QString expression = data(index(row, PropertyValueRow)).toString(); + + RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + try { + bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(), expression); + transaction.commit(); //committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException); + } + return; + } + + VariantProperty variantProperty = variantPropertyForRow(row); + + if (variantProperty.isVariantProperty()) { + const QVariant value = data(index(row, PropertyValueRow)); + + RewriterTransaction transaction = connectionView()->beginRewriterTransaction(QByteArrayLiteral("DynamicPropertiesModel::updateValue")); + try { + variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(), value); + transaction.commit(); //committing in the try block + } catch (Exception &e) { + m_exceptionError = e.description(); + QTimer::singleShot(200, this, &DynamicPropertiesModel::handleException); + } + } +} + +void DynamicPropertiesModel::updatePropertyName(int rowNumber) +{ + const PropertyName newName = data(index(rowNumber, PropertyNameRow)).toString().toUtf8(); + if (newName.isEmpty()) { + qWarning() << "DynamicPropertiesModel::updatePropertyName invalid property name"; + return; + } + + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + ModelNode targetNode = bindingProperty.parentModelNode(); + + if (bindingProperty.isBindingProperty()) { + connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [bindingProperty, newName, &targetNode](){ + const QString expression = bindingProperty.expression(); + const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName(); + + targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, expression); + targetNode.removeProperty(bindingProperty.name()); + }); + + updateCustomData(rowNumber, targetNode.bindingProperty(newName)); + return; + } + + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + + if (variantProperty.isVariantProperty()) { + const QVariant value = variantProperty.value(); + const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName(); + ModelNode targetNode = variantProperty.parentModelNode(); + + connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyName", [=](){ + targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType, value); + targetNode.removeProperty(variantProperty.name()); + }); + + updateCustomData(rowNumber, targetNode.variantProperty(newName)); + } +} + +void DynamicPropertiesModel::updatePropertyType(int rowNumber) +{ + + const TypeName newType = data(index(rowNumber, PropertyTypeRow)).toString().toLatin1(); + + if (newType.isEmpty()) { + qWarning() << "DynamicPropertiesModel::updatePropertyName invalid property type"; + return; + } + + BindingProperty bindingProperty = bindingPropertyForRow(rowNumber); + + if (bindingProperty.isBindingProperty()) { + const QString expression = bindingProperty.expression(); + const PropertyName propertyName = bindingProperty.name(); + ModelNode targetNode = bindingProperty.parentModelNode(); + + connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + targetNode.removeProperty(bindingProperty.name()); + targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, expression); + }); + + updateCustomData(rowNumber, targetNode.bindingProperty(propertyName)); + return; + } + + VariantProperty variantProperty = variantPropertyForRow(rowNumber); + + if (variantProperty.isVariantProperty()) { + const QVariant value = variantProperty.value(); + ModelNode targetNode = variantProperty.parentModelNode(); + const PropertyName propertyName = variantProperty.name(); + + connectionView()->executeInTransaction("DynamicPropertiesModel::updatePropertyType", [=](){ + targetNode.removeProperty(variantProperty.name()); + if (newType == "alias") { //alias properties have to be bindings + targetNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression(newType, QLatin1String("none.none")); + } else { + targetNode.variantProperty(propertyName).setDynamicTypeNameAndValue(newType, convertVariantForTypeName(value, newType)); + } + }); + + updateCustomData(rowNumber, targetNode.variantProperty(propertyName)); + + if (variantProperty.isVariantProperty()) { + updateVariantProperty(rowNumber); + } else if (bindingProperty.isBindingProperty()) { + updateBindingProperty(rowNumber); + } + } +} + +ModelNode DynamicPropertiesModel::getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const +{ + ModelNode modelNode; + + if (id != QLatin1String("parent")) { + modelNode = connectionView()->modelNodeForId(id); + } else { + if (targetNode.hasParentProperty()) { + modelNode = targetNode.parentProperty().parentModelNode(); + } + } + return modelNode; +} + +void DynamicPropertiesModel::updateCustomData(QStandardItem *item, const AbstractProperty &property) +{ + item->setData(property.parentModelNode().internalId(), Qt::UserRole + 1); + item->setData(property.name(), Qt::UserRole + 2); +} + +void DynamicPropertiesModel::updateCustomData(int row, const AbstractProperty &property) +{ + QStandardItem* idItem = item(row, 0); + updateCustomData(idItem, property); +} + +int DynamicPropertiesModel::findRowForBindingProperty(const BindingProperty &bindingProperty) const +{ + for (int i=0; i < rowCount(); i++) { + if (compareBindingProperties(bindingPropertyForRow(i), bindingProperty)) + return i; + } + //not found + return -1; +} + +int DynamicPropertiesModel::findRowForVariantProperty(const VariantProperty &variantProperty) const +{ + for (int i=0; i < rowCount(); i++) { + if (compareVariantProperties(variantPropertyForRow(i), variantProperty)) + return i; + } + //not found + return -1; +} + +bool DynamicPropertiesModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty) +{ + //### todo we assume no expressions yet + + const QString expression = bindingProperty.expression(); + + if (true) { + const QStringList stringList = expression.split(QLatin1String(".")); + + *sourceNode = stringList.constFirst(); + + QString propertyName; + + for (int i=1; i < stringList.count(); i++) { + propertyName += stringList.at(i); + if (i != stringList.count() - 1) + propertyName += QLatin1String("."); + } + *sourceProperty = propertyName; + } + return true; +} + +void DynamicPropertiesModel::updateDisplayRole(int row, int columns, const QString &string) +{ + QModelIndex modelIndex = index(row, columns); + if (data(modelIndex).toString() != string) + setData(modelIndex, string); +} + +void DynamicPropertiesModel::updateDisplayRoleFromVariant(int row, int columns, const QVariant &variant) +{ + QModelIndex modelIndex = index(row, columns); + if (data(modelIndex) != variant) + setData(modelIndex, variant); +} + + +void DynamicPropertiesModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!m_handleDataChanged) + return; + + if (topLeft != bottomRight) { + qWarning() << "BindingModel::handleDataChanged multi edit?"; + return; + } + + m_lock = true; + + int currentColumn = topLeft.column(); + int currentRow = topLeft.row(); + + switch (currentColumn) { + case TargetModelNodeRow: { + //updating user data + } break; + case PropertyNameRow: { + updatePropertyName(currentRow); + } break; + case PropertyTypeRow: { + updatePropertyType(currentRow); + } break; + case PropertyValueRow: { + updateValue(currentRow); + } break; + + default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn; + } + + m_lock = false; +} + +void DynamicPropertiesModel::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); + resetModel(); +} + +} // namespace Internal + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h new file mode 100644 index 0000000000..e0c9617fed --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 <modelnode.h> +#include <bindingproperty.h> +#include <variantproperty.h> + +#include <QStandardItemModel> + +namespace QmlDesigner { + +namespace Internal { + +class ConnectionView; + +class DynamicPropertiesModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum ColumnRoles { + TargetModelNodeRow = 0, + PropertyNameRow = 1, + PropertyTypeRow = 2, + PropertyValueRow = 3 + }; + DynamicPropertiesModel(ConnectionView *parent = nullptr); + void bindingPropertyChanged(const BindingProperty &bindingProperty); + void variantPropertyChanged(const VariantProperty &variantProperty); + void bindingRemoved(const BindingProperty &bindingProperty); + void selectionChanged(const QList<ModelNode> &selectedNodes); + + ConnectionView *connectionView() const; + BindingProperty bindingPropertyForRow(int rowNumber) const; + VariantProperty variantPropertyForRow(int rowNumber) const; + QStringList possibleTargetProperties(const BindingProperty &bindingProperty) const; + QStringList possibleSourceProperties(const BindingProperty &bindingProperty) const; + void deleteDynamicPropertyByRow(int rowNumber); + + void updateDisplayRoleFromVariant(int row, int columns, const QVariant &variant); + void addDynamicPropertyForCurrentNode(); + void resetModel(); + +protected: + void addProperty(const QVariant &propertyValue, + const QString &propertyType, + const AbstractProperty &abstractProperty); + void addBindingProperty(const BindingProperty &property); + void addVariantProperty(const VariantProperty &property); + void updateBindingProperty(int rowNumber); + void updateVariantProperty(int rowNumber); + void addModelNode(const ModelNode &modelNode); + void updateValue(int row); + void updatePropertyName(int rowNumber); + void updatePropertyType(int rowNumber); + ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const; + void updateCustomData(QStandardItem *item, const AbstractProperty &property); + void updateCustomData(int row, const AbstractProperty &property); + int findRowForBindingProperty(const BindingProperty &bindingProperty) const; + int findRowForVariantProperty(const VariantProperty &variantProperty) const; + + bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty); + + void updateDisplayRole(int row, int columns, const QString &string); + +private: + void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); + void handleException(); + +private: + QList<ModelNode> m_selectedModelNodes; + ConnectionView *m_connectionView; + bool m_lock = false; + bool m_handleDataChanged = false; + QString m_exceptionError; + +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css b/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css new file mode 100644 index 0000000000..aeacc63733 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/stylesheet.css @@ -0,0 +1,123 @@ +QFrame +{ + background-color: creatorTheme.QmlDesigner_BackgroundColorDarkAlternate; + color: creatorTheme.PanelTextColorLight; + font-size: creatorTheme.captionFontPixelSize; + border-radius: 0px; +} + +QTableView { + color: creatorTheme.PanelTextColorLight; + selection-color: creatorTheme.PanelTextColorLight; + selection-background-color: creatorTheme.QmlDesigner_HighlightColor; + +} + +QTabBar QToolButton { + background-color: creatorTheme.QmlDesigner_BackgroundColorDarkAlternate; + border: 1px solid creatorTheme.QmlDesigner_BackgroundColorDarker; + border-radius: 0px; +} + +QTableView::item +{ + border: 0px; + padding-left: 4px; +} + +QTableView::item:focus +{ + border: none; + background-color: transparent; +} + +QTableView::item:selected +{ + border: none +} + +QHeaderView::section { + background-color: #494949; + padding: 4px; + border: 1px solid black; + color: #cacaca; + margin: 2px +} + +QTableView { + alternate-background-color: #414141; +} + +QWidget#widgetSpacer { + background-color: creatorTheme.QmlDesigner_TabLight; +} + +QStackedWidget { + border: 0px; + background-color: #4f4f4f; +} + +QTabBar::tab:selected { + border: none; + border-image: none; + image: none; + + background-color: creatorTheme.QmlDesigner_TabLight; + color: creatorTheme.QmlDesigner_TabDark; +} + + +QTabBar::tab { + width: 92px; + height: 22px; + margin-top: 0x; + margin-bottom: 0px; + margin-left: 0px; + margin-right: 0px; + font: bold; + font-size: creatorTheme.captionFontPixelSize; + background-color: creatorTheme.QmlDesigner_TabDark; + color: creatorTheme.QmlDesigner_TabLight; +} + +QSpinBox +{ + font-size: creatorTheme.captionFontPixelSize; + color: white; + padding-right: 2px; + padding-top: 2px; + padding-bottom: 2px; + padding-left: 12px; + border: 2px solid #0F0F0F; + border-width: 1; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #2c2c2c, stop: 1 #333333); + + min-height: 22px; +} + + QDoubleSpinBox + { + font-size: creatorTheme.captionFontPixelSize; + color: white; + padding-right: 2px; + padding-top: 2px; + padding-bottom: 2px; + padding-left: 12px; + border: 2px solid #0F0F0F; + border-width: 1; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #2c2c2c, stop: 1 #333333); + min-height: 22px; + } + +QLineEdit +{ + color: white; + font-size: creatorTheme.captionFontPixelSize; + border: 2px solid #0F0F0F; + border-width: 1; + min-height: 26px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #2c2c2c, stop: 1 #333333); +} |