// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #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 "signalhandlerproperty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace QmlDesigner { ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : QFrame(parent), ui(new Ui::ConnectionViewWidget) { m_connectionEditor = new QmlDesigner::ActionEditor(this); m_bindingEditor = new QmlDesigner::BindingEditor(this); m_dynamicEditor = new QmlDesigner::BindingEditor(this); editorForConnection(); editorForBinding(); editorForDynamic(); setWindowTitle(tr("Connections", "Title of connections window")); ui->setupUi(this); QStyle *style = QStyleFactory::create("fusion"); ui->stackedWidget->setStyle(style); //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 tab")); ui->tabBar->addTab(tr("Bindings", "Title of connection tab")); ui->tabBar->addTab(tr("Properties", "Title of dynamic properties tab")); const Qt::Alignment headerAlignment = Qt::AlignLeft | Qt::AlignVCenter; ui->connectionView->horizontalHeader()->setDefaultAlignment(headerAlignment); ui->bindingView->horizontalHeader()->setDefaultAlignment(headerAlignment); ui->dynamicPropertiesView->horizontalHeader()->setDefaultAlignment(headerAlignment); ui->backendView->horizontalHeader()->setDefaultAlignment(headerAlignment); const QList buttons = createToolBarWidgets(); ui->toolBar->setFixedHeight(41); for (auto toolButton : buttons) ui->toolBar->addWidget(toolButton); if (!QmlProjectManager::QmlProject::isQtDesignStudio()) ui->tabBar->addTab(tr("Backends", "Title of dynamic properties view")); ui->tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); QByteArray sheet = Utils::FileReader::fetchQrc(":/connectionview/stylesheet.css"); sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); connect(ui->tabBar, &QTabBar::currentChanged, ui->stackedWidget, &QStackedWidget::setCurrentIndex); connect(ui->tabBar, &QTabBar::currentChanged, this, &ConnectionViewWidget::handleTabChanged); ui->stackedWidget->setCurrentIndex(0); } ConnectionViewWidget::~ConnectionViewWidget() { delete m_connectionEditor; delete m_bindingEditor; delete m_dynamicEditor; 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::contextMenuEvent(QContextMenuEvent *event) { auto tablePos = [&](QTableView *targetView) { // adjusting qpoint to the qtableview entrances: QPoint posInTable(targetView->mapFromGlobal(mapToGlobal(event->pos()))); posInTable.ry() -= targetView->horizontalHeader()->height(); return posInTable; }; switch (currentTab()) { case ConnectionTab: if (ui->connectionView != nullptr) { QTableView *targetView = ui->connectionView; // making sure that we have source column in our hands: const QModelIndex index = targetView->indexAt(tablePos(targetView)).siblingAtColumn(ConnectionModel::SourceRow); if (!index.isValid()) return; QMenu menu(this); menu.addAction(tr("Open Connection Editor"), this, [&]() { auto *connectionModel = qobject_cast(targetView->model()); const SignalHandlerProperty property = connectionModel->signalHandlerPropertyForRow(index.row()); const ModelNode node = property.parentModelNode(); const QString targetName = index.siblingAtColumn(ConnectionModel::TargetModelNodeRow).data().toString() + "." + property.name(); m_connectionEditor->showWidget(); m_connectionEditor->setConnectionValue(index.data().toString()); m_connectionEditor->setModelIndex(index); m_connectionEditor->setModelNode(node); m_connectionEditor->prepareConnections(); m_connectionEditor->updateWindowName(targetName); }); QMap data; data["ModelNode"] = index.siblingAtColumn(ConnectionModel::TargetModelNodeRow).data(); data["Signal"] = index.siblingAtColumn(ConnectionModel::TargetPropertyNameRow).data(); DesignerActionManager &designerActionManager = QmlDesignerPlugin::instance()->designerActionManager(); const auto actions = designerActionManager.actionsForTargetView( ActionInterface::TargetView::ConnectionEditor); for (const auto &actionInterface : actions) { auto *action = actionInterface->action(); action->setData(data); menu.addAction(action); } menu.exec(event->globalPos()); } break; case BindingTab: if (ui->bindingView != nullptr) { QTableView *targetView = bindingTableView(); const QModelIndex index = targetView->indexAt(tablePos(targetView)).siblingAtColumn(BindingModel::SourcePropertyNameRow); if (!index.isValid()) return; QMenu menu(this); menu.addAction(tr("Open Binding Editor"), this, [&]() { BindingModel *bindingModel = qobject_cast(targetView->model()); const BindingProperty property = bindingModel->bindingPropertyForRow(index.row()); if (!property.isValid() || !property.isBindingProperty()) return; const ModelNode node = property.parentModelNode(); const TypeName typeName = property.isDynamic() ? property.dynamicTypeName() : node.metaInfo() .property(property.name()) .propertyType() .typeName(); const QString targetName = node.displayName() + "." + property.name(); m_bindingEditor->showWidget(); m_bindingEditor->setBindingValue(property.expression()); m_bindingEditor->setModelNode(node); m_bindingEditor->setBackendValueTypeName(typeName); m_bindingEditor->setTargetName(targetName); m_bindingEditor->prepareBindings(); m_bindingEditor->updateWindowName(); m_bindingIndex = index; }); menu.exec(event->globalPos()); } break; case DynamicPropertiesTab: if (ui->dynamicPropertiesView != nullptr) { QTableView *targetView = dynamicPropertiesTableView(); const QModelIndex index = targetView->indexAt(tablePos(targetView)).siblingAtColumn(DynamicPropertiesModel::PropertyValueRow); if (!index.isValid()) return; DynamicPropertiesModel *propertiesModel = qobject_cast(targetView->model()); QMenu menu(this); menu.addAction(tr("Open Binding Editor"), this, [&]() { AbstractProperty abstractProperty = propertiesModel->abstractPropertyForRow(index.row()); if (!abstractProperty.isValid()) return; const ModelNode node = abstractProperty.parentModelNode(); QString newExpression; if (abstractProperty.isBindingProperty()) newExpression = abstractProperty.toBindingProperty().expression(); else if (abstractProperty.isVariantProperty()) newExpression = abstractProperty.toVariantProperty().value().toString(); else return; const QString targetName = node.displayName() + "." + abstractProperty.name(); m_dynamicEditor->showWidget(); m_dynamicEditor->setBindingValue(newExpression); m_dynamicEditor->setModelNode(node); m_dynamicEditor->setBackendValueTypeName(abstractProperty.dynamicTypeName()); m_dynamicEditor->setTargetName(targetName); m_dynamicEditor->prepareBindings(); m_dynamicEditor->updateWindowName(); m_dynamicIndex = index; }); menu.addAction(tr("Reset Property"), this, [&]() { propertiesModel->resetProperty(propertiesModel->abstractPropertyForRow(index.row()).name()); }); menu.exec(event->globalPos()); } break; default: break; } } 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 ConnectionViewWidget::createToolBarWidgets() { QList 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_TOOLBAR.icon()); buttons.constLast()->setToolTip(tr("Remove selected binding or connection.")); connect(buttons.constLast(), &QAbstractButton::clicked, this, &ConnectionViewWidget::removeButtonClicked); connect(this, &ConnectionViewWidget::setEnabledRemoveButton, buttons.constLast(), &QWidget::setEnabled); QAction *deleteShortcut = new QAction(this); this->addAction(deleteShortcut); deleteShortcut->setShortcuts({QKeySequence::Delete, QKeySequence::Backspace}); deleteShortcut->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(deleteShortcut, &QAction::triggered, this, &ConnectionViewWidget::removeButtonClicked); 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(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(ui->dynamicPropertiesView->model()); emit setEnabledAddButton(dynamicPropertiesModel->view()->model() && dynamicPropertiesModel->selectedNodes().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(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(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(ui->dynamicPropertiesView->model()); if (dynamicPropertiesModel) dynamicPropertiesModel->deleteDynamicPropertyByRow(currentRow); } else if (currentTab() == BackendTab) { int currentRow = ui->backendView->selectionModel()->selectedRows().constFirst().row(); auto backendModel = qobject_cast(ui->backendView->model()); if (backendModel) backendModel->deletePropertyByRow(currentRow); } invalidateButtonStatus(); } void ConnectionViewWidget::addButtonClicked() { if (currentTab() == ConnectionTab) { auto connectionModel = qobject_cast(ui->connectionView->model()); if (connectionModel) { connectionModel->addConnection(); } } else if (currentTab() == BindingTab) { auto bindingModel = qobject_cast(ui->bindingView->model()); if (bindingModel) { bindingModel->addBindingForCurrentNode(); } } else if (currentTab() == DynamicPropertiesTab) { auto dynamicPropertiesModel = qobject_cast(ui->dynamicPropertiesView->model()); if (dynamicPropertiesModel) dynamicPropertiesModel->addDynamicPropertyForCurrentNode(); } else if (currentTab() == BackendTab) { auto backendModel = qobject_cast(ui->backendView->model()); if (backendModel) backendModel->addNewBackend(); } invalidateButtonStatus(); } void ConnectionViewWidget::editorForConnection() { QObject::connect(m_connectionEditor, &QmlDesigner::ActionEditor::accepted, [&]() { if (m_connectionEditor->hasModelIndex()) { ConnectionModel *connectionModel = qobject_cast(ui->connectionView->model()); if (connectionModel->connectionView()->isWidgetEnabled() && (connectionModel->rowCount() > m_connectionEditor->modelIndex().row())) { connectionModel->connectionView() ->executeInTransaction("ConnectionView::setSignal", [this, connectionModel]() { SignalHandlerProperty signalHandler = connectionModel->signalHandlerPropertyForRow( m_connectionEditor->modelIndex().row()); signalHandler.setSource(m_connectionEditor->connectionValue()); }); } m_connectionEditor->resetModelIndex(); } m_connectionEditor->hideWidget(); }); QObject::connect(m_connectionEditor, &QmlDesigner::ActionEditor::rejected, [&]() { m_connectionEditor->resetModelIndex(); m_connectionEditor->hideWidget(); }); } void ConnectionViewWidget::editorForBinding() { QObject::connect(m_bindingEditor, &QmlDesigner::BindingEditor::accepted, [&]() { BindingModel *bindingModel = qobject_cast(bindingTableView()->model()); QString newValue = m_bindingEditor->bindingValue().trimmed(); if (m_bindingIndex.isValid()) { if (bindingModel->connectionView()->isWidgetEnabled() && (bindingModel->rowCount() > m_bindingIndex.row())) { bindingModel->connectionView()->executeInTransaction( "ConnectionView::setBindingProperty", [this, bindingModel, newValue]() { BindingProperty property = bindingModel->bindingPropertyForRow( m_bindingIndex.row()); if (property.isValid()) { if (property.isBindingProperty()) { if (property.isDynamic()) { property .setDynamicTypeNameAndExpression(property.dynamicTypeName(), newValue); } else { property.setExpression(newValue); } } } }); } } m_bindingIndex = QModelIndex(); m_bindingEditor->hideWidget(); }); QObject::connect(m_bindingEditor, &QmlDesigner::BindingEditor::rejected, [&]() { m_bindingIndex = QModelIndex(); //invalidating index m_bindingEditor->hideWidget(); }); } void ConnectionViewWidget::editorForDynamic() { QObject::connect(m_dynamicEditor, &QmlDesigner::BindingEditor::accepted, [&]() { DynamicPropertiesModel *propertiesModel = qobject_cast(dynamicPropertiesTableView()->model()); QString newValue = m_dynamicEditor->bindingValue().trimmed(); if (m_dynamicIndex.isValid()) { if (qobject_cast(propertiesModel->view())->isWidgetEnabled() && (propertiesModel->rowCount() > m_dynamicIndex.row())) { propertiesModel->view()->executeInTransaction( "ConnectionView::setBinding", [this, propertiesModel, newValue]() { AbstractProperty abProp = propertiesModel->abstractPropertyForRow( m_dynamicIndex.row()); if (abProp.isValid()) { if (abProp.isBindingProperty()) { BindingProperty property = abProp.toBindingProperty(); property.setDynamicTypeNameAndExpression(property.dynamicTypeName(), newValue); } //if it's a variant property, then we remove it and replace with binding else if (abProp.isVariantProperty()) { VariantProperty property = abProp.toVariantProperty(); PropertyName name = property.name(); TypeName type = property.dynamicTypeName(); BindingProperty newProperty = propertiesModel ->replaceVariantWithBinding(name); if (newProperty.isValid()) { newProperty.setDynamicTypeNameAndExpression(type, newValue); } } } }); } } m_dynamicIndex = QModelIndex(); m_dynamicEditor->hideWidget(); }); QObject::connect(m_dynamicEditor, &QmlDesigner::BindingEditor::rejected, [&]() { m_dynamicIndex = QModelIndex(); //invalidating index m_dynamicEditor->hideWidget(); }); } 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 QmlDesigner