QmlDesigner: Add signal list dialog

* Add signal list dialog, delegate and view
* Add context menu entry on items which have a trigger signal
* Add specific model node operations
* Add qml Connections util class
* Add utility functions to signal handler property
* Fix property processing in node meta info
* Fix source code in connection editor and navigator

Task-number: QDS-3138
Change-Id: I362ceef230efdb14f2050995b1693937e5e73c41
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2021-01-06 14:39:21 +01:00
committed by Henning Gründl
parent 306cadc643
commit bebd70573e
22 changed files with 1079 additions and 23 deletions

View File

@@ -497,6 +497,7 @@ extend_qtc_plugin(QmlDesigner
include/qmlmodelnodefacade.h
include/qmlobjectnode.h
include/qmlstate.h
include/qmlconnections.h
include/qmltimeline.h
include/qmltimelinekeyframegroup.h
include/removebasestateexception.h
@@ -578,6 +579,7 @@ extend_qtc_plugin(QmlDesigner
model/qmlmodelnodefacade.cpp
model/qmlobjectnode.cpp
model/qmlstate.cpp
model/qmlconnections.cpp
model/qmltextgenerator.cpp model/qmltextgenerator.h
model/qmltimeline.cpp
model/qmltimelinekeyframegroup.cpp
@@ -620,6 +622,9 @@ extend_qtc_plugin(QmlDesigner
bindingeditordialog.cpp bindingeditordialog.h
bindingeditorwidget.cpp bindingeditorwidget.h
connectionvisitor.cpp connectionvisitor.h
signallist.cpp signallist.h
signallistdialog.cpp signallistdialog.h
signallistdelegate.cpp signallistdelegate.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -5,6 +5,9 @@ HEADERS += $$PWD/actioneditordialog.h
HEADERS += $$PWD/bindingeditordialog.h
HEADERS += $$PWD/bindingeditorwidget.h
HEADERS += $$PWD/connectionvisitor.h
HEADERS += $$PWD/signallist.h
HEADERS += $$PWD/signallistdialog.h
HEADERS += $$PWD/signallistdelegate.h
SOURCES += $$PWD/bindingeditor.cpp
SOURCES += $$PWD/actioneditor.cpp
@@ -13,3 +16,6 @@ SOURCES += $$PWD/actioneditordialog.cpp
SOURCES += $$PWD/bindingeditordialog.cpp
SOURCES += $$PWD/bindingeditorwidget.cpp
SOURCES += $$PWD/connectionvisitor.cpp
SOURCES += $$PWD/signallist.cpp
SOURCES += $$PWD/signallistdialog.cpp
SOURCES += $$PWD/signallistdelegate.cpp

View File

@@ -0,0 +1,328 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "signallist.h"
#include "signallistdelegate.h"
#include <qmldesignerplugin.h>
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <metainfo.h>
#include <variantproperty.h>
#include <bindingproperty.h>
#include <signalhandlerproperty.h>
#include <qmldesignerconstants.h>
#include <qmlitemnode.h>
#include <nodeabstractproperty.h>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QTableView>
namespace QmlDesigner {
SignalListModel::SignalListModel(QObject *parent)
: QStandardItemModel(0, 3, parent)
{
setHeaderData(TargetColumn, Qt::Horizontal, tr("Item ID"));
setHeaderData(SignalColumn, Qt::Horizontal, tr("Signal"));
setHeaderData(ButtonColumn, Qt::Horizontal, "");
}
void SignalListModel::setConnected(int row, bool connected)
{
for (int col = 0; col < columnCount(); ++col) {
QModelIndex idx = index(row, col);
setData(idx, connected, ConnectedRole);
}
}
SignalListFilterModel::SignalListFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool SignalListFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex targetIndex = sourceModel()->index(sourceRow, SignalListModel::TargetColumn, sourceParent);
QModelIndex signalIndex = sourceModel()->index(sourceRow, SignalListModel::SignalColumn, sourceParent);
return (sourceModel()->data(targetIndex).toString().contains(filterRegExp())
|| sourceModel()->data(signalIndex).toString().contains(filterRegExp()));
}
PropertyNameList SignalList::st_mouseSignals = { "clicked", "doubleClicked", "pressAndHold",
"pressed", "released", "wheel" };
SignalList::SignalList(QObject *)
: m_dialog(QPointer<SignalListDialog>())
, m_model(new SignalListModel(this))
, m_modelNode()
{
}
SignalList::~SignalList()
{
hideWidget();
}
void SignalList::prepareDialog()
{
m_dialog = new SignalListDialog(Core::ICore::dialogParent());
m_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_dialog->initialize(m_model);
m_dialog->setWindowTitle(tr("Signal List for ") + m_modelNode.validId());
auto *delegate = static_cast<SignalListDelegate *>(m_dialog->tableView()->itemDelegate());
connect(delegate, &SignalListDelegate::connectClicked, this, &SignalList::connectClicked);
}
void SignalList::showWidget()
{
prepareDialog();
m_dialog->show();
m_dialog->raise();
}
void SignalList::hideWidget()
{
if (m_dialog)
m_dialog->close();
m_dialog = nullptr;
}
SignalList* SignalList::showWidget(const ModelNode &modelNode)
{
auto signalList = new SignalList();
signalList->setModelNode(modelNode);
signalList->prepareSignals();
signalList->showWidget();
connect(signalList->m_dialog, &QDialog::destroyed,
[signalList]() { signalList->deleteLater(); } );
return signalList;
}
void SignalList::setModelNode(const ModelNode &modelNode)
{
if (modelNode.isValid())
m_modelNode = modelNode;
}
void SignalList::prepareSignals()
{
if (!m_modelNode.isValid())
return;
AbstractView *view = m_modelNode.view();
QList<QmlConnections> connections = getAssociatedConnections(view->allModelNodes());
for (ModelNode &node : view->allModelNodes()) {
// Collect all items which contain at least one of the specified signals
const PropertyNameList signalNames = node.metaInfo().signalNames();
// Put the signals into a QSet to avoid duplicates
auto signalNamesSet = QSet<PropertyName>(signalNames.begin(), signalNames.end());
for (const PropertyName &signal : signalNamesSet) {
if (st_mouseSignals.contains(signal))
appendSignalToModel(connections, node, signal);
}
// Gather valid properties and aliases from components
for (const PropertyName &property : node.metaInfo().propertyNames()) {
const TypeName propertyType = node.metaInfo().propertyTypeName(property);
const NodeMetaInfo info = m_modelNode.model()->metaInfo(propertyType);
// Collect all items which contain at least one of the specified signals
const PropertyNameList signalNames = info.signalNames();
// Put the signals into a QSet to avoid duplicates
auto signalNamesSet = QSet<PropertyName>(signalNames.begin(), signalNames.end());
for (const PropertyName &signal : signalNamesSet) {
if (st_mouseSignals.contains(signal))
appendSignalToModel(connections, node, signal, property);
}
}
}
}
void SignalList::connectClicked(const QModelIndex &modelIndex)
{
auto proxyModel = static_cast<const SignalListFilterModel *>(modelIndex.model());
QModelIndex mappedModelIndex = proxyModel->mapToSource(modelIndex);
bool connected = mappedModelIndex.data(SignalListModel::ConnectedRole).toBool();
if (!connected)
addConnection(mappedModelIndex);
else
removeConnection(mappedModelIndex);
}
void SignalList::appendSignalToModel(const QList<QmlConnections> &connections,
ModelNode &node,
const PropertyName &signal,
const PropertyName &property)
{
QStandardItem *idItem = new QStandardItem();
QString id(node.validId());
if (!property.isEmpty())
id += "." + QString::fromLatin1(property);
idItem->setData(id, Qt::DisplayRole);
QStandardItem *signalItem = new QStandardItem();
signalItem->setData(signal, Qt::DisplayRole);
QStandardItem *buttonItem = new QStandardItem();
idItem->setData(false, SignalListModel::ConnectedRole);
signalItem->setData(false, SignalListModel::ConnectedRole);
buttonItem->setData(false, SignalListModel::ConnectedRole);
for (const QmlConnections &connection : connections) {
if (connection.target() == id) {
for (const SignalHandlerProperty &property : connection.signalProperties()) {
auto signalWithoutPrefix = SignalHandlerProperty::prefixRemoved(property.name());
if (signalWithoutPrefix == signal) {
buttonItem->setData(connection.modelNode().internalId(),
SignalListModel::ConnectionsInternalIdRole);
idItem->setData(true, SignalListModel::ConnectedRole);
signalItem->setData(true, SignalListModel::ConnectedRole);
buttonItem->setData(true, SignalListModel::ConnectedRole);
}
}
}
}
m_model->appendRow({idItem, signalItem, buttonItem});
}
void SignalList::addConnection(const QModelIndex &modelIndex)
{
const QModelIndex targetModelIndex = modelIndex.siblingAtColumn(SignalListModel::TargetColumn);
const QModelIndex signalModelIndex = modelIndex.siblingAtColumn(SignalListModel::SignalColumn);
const QModelIndex buttonModelIndex = modelIndex.siblingAtColumn(SignalListModel::ButtonColumn);
const PropertyName signalName = m_model->data(signalModelIndex,
Qt::DisplayRole).toByteArray();
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_CONNECTION_ADDED);
AbstractView *view = m_modelNode.view();
const ModelNode rootModelNode = view->rootModelNode();
if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) {
NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections");
if (nodeMetaInfo.isValid()) {
view->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){
ModelNode newNode = view->createModelNode("QtQuick.Connections",
nodeMetaInfo.majorVersion(),
nodeMetaInfo.minorVersion());
const QString source = m_modelNode.validId() + ".trigger()";
if (QmlItemNode::isValidQmlItemNode(m_modelNode))
m_modelNode.nodeAbstractProperty("data").reparentHere(newNode);
else
rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode);
const QString expression = m_model->data(targetModelIndex, Qt::DisplayRole).toString();
newNode.bindingProperty("target").setExpression(expression);
newNode.signalHandlerProperty(SignalHandlerProperty::prefixAdded(signalName)).setSource(source);
m_model->setConnected(modelIndex.row(), true);
m_model->setData(buttonModelIndex, newNode.internalId(), SignalListModel::ConnectionsInternalIdRole);
});
}
}
}
void SignalList::removeConnection(const QModelIndex &modelIndex)
{
const QModelIndex signalModelIndex = modelIndex.siblingAtColumn(SignalListModel::SignalColumn);
const QModelIndex buttonModelIndex = modelIndex.siblingAtColumn(SignalListModel::ButtonColumn);
const PropertyName signalName = m_model->data(signalModelIndex,
Qt::DisplayRole).toByteArray();
const int connectionInternalId = m_model->data(buttonModelIndex,
SignalListModel::ConnectionsInternalIdRole).toInt();
AbstractView *view = m_modelNode.view();
const ModelNode connectionModelNode = view->modelNodeForInternalId(connectionInternalId);
SignalHandlerProperty targetSignal;
if (connectionModelNode.isValid())
targetSignal = connectionModelNode.signalHandlerProperty(signalName);
ModelNode node = targetSignal.parentModelNode();
if (node.isValid()) {
view->executeInTransaction("ConnectionModel::removeConnection", [=, &node](){
QList<SignalHandlerProperty> allSignals = node.signalProperties();
if (allSignals.size() > 1) {
const auto targetSignalWithPrefix = SignalHandlerProperty::prefixAdded(targetSignal.name());
for (const SignalHandlerProperty &signal : allSignals)
if (signal.name() == targetSignalWithPrefix)
node.removeProperty(targetSignalWithPrefix);
} else {
node.destroy();
}
m_model->setConnected(modelIndex.row(), false);
m_model->setData(buttonModelIndex, QVariant(), SignalListModel::ConnectionsInternalIdRole);
});
}
}
QList<QmlConnections> SignalList::getAssociatedConnections(const QList<ModelNode> &nodes)
{
return Utils::transform<QList<QmlConnections>>(Utils::filtered(nodes, [this](const ModelNode &node) {
const QmlConnections connection(node);
if (!connection.isValid())
return false;
for (const SignalHandlerProperty &property : connection.signalProperties()) {
auto signalWithoutPrefix = SignalHandlerProperty::prefixRemoved(property.name());
const QStringList sourceComponents = property.source().split(".");
QString sourceId;
QString sourceProperty;
if (sourceComponents.size() > 1) {
sourceId = sourceComponents[0];
sourceProperty = sourceComponents[1];
}
if (st_mouseSignals.contains(signalWithoutPrefix)
&& sourceId == m_modelNode.validId()
&& sourceProperty == "trigger()")
return true;
}
return false;
}), [](const ModelNode &node) {
return QmlConnections(node);
});
}
} // QmlDesigner namespace

View File

@@ -0,0 +1,108 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <bindingeditor/signallistdialog.h>
#include <qmldesignercorelib_global.h>
#include <modelnode.h>
#include <qmlconnections.h>
#include <QtQml>
#include <QObject>
#include <QPointer>
#include <QStandardItemModel>
namespace QmlDesigner {
class SignalListModel : public QStandardItemModel
{
Q_OBJECT
public:
enum ColumnRoles : unsigned int {
TargetColumn = 0,
SignalColumn = 1,
ButtonColumn = 2
};
enum UserRoles : unsigned int {
ConnectionsInternalIdRole = Qt::UserRole + 1,
ConnectedRole
};
SignalListModel(QObject *parent = nullptr);
void setConnected(int row, bool connected);
};
class SignalListFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
SignalListFilterModel(QObject *parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
class SignalList : public QObject
{
Q_OBJECT
public:
explicit SignalList(QObject *parent = nullptr);
~SignalList();
static SignalList* showWidget(const ModelNode &modelNode);
void setModelNode(const ModelNode &modelNode);
void connectClicked(const QModelIndex &modelIndex);
private:
void prepareDialog();
void showWidget();
void hideWidget();
void prepareSignals();
void appendSignalToModel(const QList<QmlConnections> &connections,
ModelNode &node,
const PropertyName &signal,
const PropertyName &property = "");
void addConnection(const QModelIndex &modelIndex);
void removeConnection(const QModelIndex &modelIndex);
QList<QmlConnections> getAssociatedConnections(const QList<ModelNode> &nodes);
private:
static PropertyNameList st_mouseSignals;
QPointer<SignalListDialog> m_dialog;
SignalListModel *m_model;
ModelNode m_modelNode;
};
} // QmlDesigner namespace

View File

@@ -0,0 +1,95 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "signallistdelegate.h"
#include "signallist.h"
#include <QApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOptionButton>
namespace QmlDesigner {
SignalListDelegate::SignalListDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{}
QWidget *SignalListDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == SignalListModel::ButtonColumn)
return nullptr;
return QStyledItemDelegate::createEditor(parent, option, index);
}
QRect connectButtonRect(const QStyleOptionViewItem &option)
{
return option.rect.adjusted(3, 3, -3, -3);
}
void SignalListDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
const bool connected = index.data(SignalListModel::ConnectedRole).toBool();
if (connected) {
QStyleOptionViewItem opt(option);
opt.state = QStyle::State_Selected;
QStyledItemDelegate::paint(painter, opt, index);
if (index.column() != SignalListModel::ButtonColumn)
return;
}
if (index.column() == SignalListModel::ButtonColumn) {
QStyleOptionButton button;
button.rect = connectButtonRect(option);
button.text = connected ? tr("Release") : tr("Connect");
button.state = QStyle::State_Enabled;
QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter);
return;
}
QStyledItemDelegate::paint(painter, option, index);
}
bool SignalListDelegate::editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_UNUSED(model)
if (index.column() == SignalListModel::ButtonColumn
&& event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (connectButtonRect(option).contains(mouseEvent->pos()))
emit connectClicked(index);
}
return true;
}
} // QmlDesigner namespace

View File

@@ -0,0 +1,54 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QStyledItemDelegate>
namespace QmlDesigner {
class SignalListDelegate : public QStyledItemDelegate
{
Q_OBJECT
signals:
void connectClicked(const QModelIndex &modelIndex) const;
public:
SignalListDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
bool editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index) override;
};
} // QmlDesigner namespace

View File

@@ -0,0 +1,132 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "signallistdialog.h"
#include "signallist.h"
#include "signallistdelegate.h"
#include <qmldesignerplugin.h>
#include <theme.h>
#include <utils/fancylineedit.h>
#include <utils/stylehelper.h>
#include <utils/utilsicons.h>
#include <QStandardItemModel>
#include <QPainter>
#include <QTableView>
#include <QHeaderView>
#include <QVBoxLayout>
#include <QSortFilterProxyModel>
namespace QmlDesigner {
QWidget *createFilterWidget(Utils::FancyLineEdit *lineEdit)
{
const QString unicode = Theme::getIconUnicode(Theme::Icon::search);
const QString fontName = "qtds_propertyIconFont.ttf";
QIcon icon = Utils::StyleHelper::getIconFromIconFont(fontName, unicode, 28, 28);
auto *label = new QLabel;
label->setPixmap(icon.pixmap(QSize(24, 24)));
label->setAlignment(Qt::AlignCenter);
lineEdit->setPlaceholderText(QObject::tr("<Filter>", "Library search input hint text"));
lineEdit->setDragEnabled(false);
lineEdit->setMinimumWidth(75);
lineEdit->setTextMargins(0, 0, 20, 0);
lineEdit->setFiltering(true);
auto *box = new QHBoxLayout;
box->addWidget(label);
box->addWidget(lineEdit);
auto *widget = new QWidget;
widget->setLayout(box);
return widget;
}
void modifyPalette(QTableView *view, const QColor &selectionColor)
{
QPalette p = view->palette();
p.setColor(QPalette::AlternateBase, p.color(QPalette::Base).lighter(120));
p.setColor(QPalette::Highlight, selectionColor);
view->setPalette(p);
view->setAlternatingRowColors(true);
}
SignalListDialog::SignalListDialog(QWidget *parent)
: QDialog(parent)
, m_table(new QTableView())
, m_searchLine(new Utils::FancyLineEdit())
{
auto *signalListDelegate = new SignalListDelegate(m_table);
m_table->setItemDelegate(signalListDelegate);
m_table->setFocusPolicy(Qt::NoFocus);
m_table->setSelectionMode(QAbstractItemView::NoSelection);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->verticalHeader()->hide();
modifyPalette(m_table, QColor("#d87b00"));
auto *layout = new QVBoxLayout;
layout->addWidget(createFilterWidget(m_searchLine));
layout->addWidget(m_table);
setLayout(layout);
setWindowFlag(Qt::Tool, true);
setModal(true);
resize(600, 480);
}
SignalListDialog::~SignalListDialog()
{
}
void SignalListDialog::initialize(QStandardItemModel *model)
{
m_searchLine->clear();
auto *proxyModel = new SignalListFilterModel(this);
proxyModel->setSourceModel(model);
m_table->setModel(proxyModel);
QHeaderView *header = m_table->horizontalHeader();
header->setSectionResizeMode(SignalListModel::TargetColumn, QHeaderView::Stretch);
header->setSectionResizeMode(SignalListModel::SignalColumn, QHeaderView::Stretch);
header->resizeSection(SignalListModel::ButtonColumn, 120);
header->setStretchLastSection(false);
auto eventFilterFun = [this](const QString &str) {
if (auto *fm = qobject_cast<SignalListFilterModel *>(m_table->model()))
fm->setFilterFixedString(str);
};
connect(m_searchLine, &Utils::FancyLineEdit::filterChanged, eventFilterFun);
}
QTableView *SignalListDialog::tableView() const
{
return m_table;
}
} // QmlDesigner namespace

View File

@@ -0,0 +1,58 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QDialog>
QT_BEGIN_NAMESPACE
class QStandardItemModel;
class QTableView;
QT_END_NAMESPACE
namespace Utils {
class FancyLineEdit;
}
namespace QmlDesigner {
class SignalListDialog : public QDialog
{
Q_OBJECT
public:
SignalListDialog(QWidget *parent = nullptr);
~SignalListDialog() override;
void initialize(QStandardItemModel *model);
QTableView *tableView() const;
private:
QTableView *m_table;
Utils::FancyLineEdit *m_searchLine;
};
} // QmlDesigner namespace

View File

@@ -89,6 +89,8 @@ const char fitRootToScreenCommandId[] = "FitRootToScreen";
const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
const char editAnnotationCommandId[] = "EditAnnotation";
const char openSignalDialogCommandId[] = "OpenSignalDialog";
const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection");
const char flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect");
const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select Effect");
@@ -128,6 +130,8 @@ const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContext
const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File");
const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation");
const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog");
const char setIdDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Id");
const char resetZDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset z Property");

View File

@@ -564,6 +564,7 @@ const char yProperty[] = "y";
const char zProperty[] = "z";
const char widthProperty[] = "width";
const char heightProperty[] = "height";
const char triggerSlot[] = "trigger";
using namespace SelectionContextFunctors;
@@ -644,6 +645,14 @@ bool selectionNotEmptyAndHasXorYProperty(const SelectionContext &context)
&& selectionHasProperty1or2(context, xProperty, yProperty);
}
bool singleSelectionAndHasSlotTrigger(const SelectionContext &context)
{
if (!singleSelection(context))
return false;
return selectionHasSlot(context, triggerSlot);
}
bool singleSelectionAndInQtQuickLayout(const SelectionContext &context)
{
if (!singleSelection(context))
@@ -1384,6 +1393,16 @@ void DesignerActionManager::createDefaultDesignerActions()
addDesignerAction(new ChangeStyleAction());
addDesignerAction(new EditListModelAction);
addDesignerAction(new ModelNodeContextMenuAction(
openSignalDialogCommandId,
openSignalDialogDisplayName,
{},
rootCategory,
QKeySequence(),
66,
&openSignalDialog,
&singleSelectionAndHasSlotTrigger));
}
void DesignerActionManager::createDefaultAddResourceHandler()

View File

@@ -30,6 +30,7 @@
#include "abstractactiongroup.h"
#include "qmlitemnode.h"
#include <qmldesignerplugin.h>
#include <nodemetainfo.h>
#include <coreplugin/actionmanager/command.h>
@@ -80,12 +81,24 @@ inline bool singleSelectionNotRoot(const SelectionContext &selectionState)
inline bool selectionHasProperty(const SelectionContext &selectionState, const char *property)
{
foreach (const ModelNode &modelNode, selectionState.selectedModelNodes())
for (const ModelNode &modelNode : selectionState.selectedModelNodes())
if (modelNode.hasProperty(PropertyName(property)))
return true;
return false;
}
inline bool selectionHasSlot(const SelectionContext &selectionState, const char *property)
{
for (const ModelNode &modelNode : selectionState.selectedModelNodes()) {
for (const PropertyName &slotName : modelNode.metaInfo().slotNames()) {
if (slotName == property)
return true;
}
}
return false;
}
inline bool singleSelectedItem(const SelectionContext &selectionState)
{
QmlItemNode itemNode(selectionState.currentSingleSelectedNode());
@@ -94,7 +107,6 @@ inline bool singleSelectedItem(const SelectionContext &selectionState)
bool selectionHasSameParent(const SelectionContext &selectionState);
bool selectionIsComponent(const SelectionContext &selectionState);
bool selectionIsComponent(const SelectionContext &selectionState);
bool singleSelectionItemIsAnchored(const SelectionContext &selectionState);
bool singleSelectionItemIsNotAnchored(const SelectionContext &selectionState);

View File

@@ -87,6 +87,8 @@
#include <functional>
#include <cmath>
#include <bindingeditor/signallist.h>
namespace QmlDesigner {
const PropertyName auxDataString("anchors_");
@@ -1550,6 +1552,14 @@ QVariant previewImageDataForImageNode(const ModelNode &modelNode)
return {};
}
void openSignalDialog(const SelectionContext &selectionContext)
{
if (!selectionContext.view() || !selectionContext.hasSingleSelectedModelNode())
return;
SignalList::showWidget(selectionContext.currentSingleSelectedNode());
}
} // namespace ModelNodeOperations
} //QmlDesigner

View File

@@ -86,6 +86,8 @@ void mergeWithTemplate(const SelectionContext &selectionContext);
void removeGroup(const SelectionContext &selectionContext);
void editAnnotation(const SelectionContext &selectionContext);
void openSignalDialog(const SelectionContext &selectionContext);
// ModelNodePreviewImageOperations
QVariant previewImageDataForGenericNode(const ModelNode &modelNode);
QVariant previewImageDataForImageNode(const ModelNode &modelNode);

View File

@@ -49,9 +49,9 @@ namespace {
QStringList propertyNameListToStringList(const QmlDesigner::PropertyNameList &propertyNameList)
{
QStringList stringList;
for (const QmlDesigner::PropertyName &propertyName : propertyNameList) {
for (const QmlDesigner::PropertyName &propertyName : propertyNameList)
stringList << QString::fromUtf8(propertyName);
}
stringList.removeDuplicates();
return stringList;
}

View File

@@ -133,8 +133,6 @@ static QRect drawText(QPainter *painter,
int iconOffset)
{
QString displayString = modelIndex.data(Qt::DisplayRole).toString();
QPoint displayStringOffset;
int width = 0;
// Check text length does not exceed available space
const int extraSpace = 12 + iconOffset;
@@ -142,10 +140,8 @@ static QRect drawText(QPainter *painter,
displayString = styleOption.fontMetrics.elidedText(displayString,
Qt::ElideMiddle,
styleOption.rect.width() - extraSpace);
displayStringOffset = QPoint(5 + iconOffset, -5 - delegateMargin);
width = styleOption.fontMetrics.horizontalAdvance(displayString);
const QPoint displayStringOffset = QPoint(5 + iconOffset, -5 - delegateMargin);
const int width = styleOption.fontMetrics.horizontalAdvance(displayString);
const QPoint textPosition = styleOption.rect.bottomLeft() + displayStringOffset;
painter->drawText(textPosition, displayString);

View File

@@ -69,6 +69,7 @@ SOURCES += $$PWD/model/abstractview.cpp \
$$PWD/model/qmlmodelnodefacade.cpp \
$$PWD/model/qmlobjectnode.cpp \
$$PWD/model/qmlanchors.cpp \
$$PWD/model/qmlconnections.cpp \
$$PWD/rewritertransaction.cpp \
$$PWD/model/rewriteaction.cpp \
$$PWD/model/modelnodepositionstorage.cpp \
@@ -151,6 +152,7 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \
$$PWD/include/forwardview.h \
$$PWD/include/qmlobjectnode.h \
$$PWD/include/qmlanchors.h \
$$PWD/include/qmlconnections.h \
$$PWD/rewritertransaction.h \
$$PWD/model/rewriteaction.h \
$$PWD/include/modelnodepositionstorage.h \

View File

@@ -0,0 +1,57 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <qmldesignercorelib_global.h>
#include "qmlmodelnodefacade.h"
#include <signalhandlerproperty.h>
namespace QmlDesigner {
class QMLDESIGNERCORE_EXPORT QmlConnections : public QmlModelNodeFacade
{
friend class StatesEditorView;
public:
QmlConnections();
QmlConnections(const ModelNode &modelNode);
bool isValid() const override;
static bool isValidQmlConnections(const ModelNode &modelNode);
void destroy();
QString target() const;
void setTarget(const QString &target);
ModelNode getTargetNode() const;
QList<SignalHandlerProperty> signalProperties() const;
static ModelNode createQmlConnections(AbstractView *view);
};
} //QmlDesigner

View File

@@ -43,6 +43,9 @@ public:
SignalHandlerProperty();
SignalHandlerProperty(const SignalHandlerProperty &property, AbstractView *view);
static PropertyName prefixAdded(const PropertyName &propertyName);
static PropertyName prefixRemoved(const PropertyName &propertyName);
protected:
SignalHandlerProperty(const PropertyName &propertyName, const Internal::InternalNodePointer &internalNode, Model* model, AbstractView *view);
};

View File

@@ -82,7 +82,7 @@ using PropertyInfo = QPair<PropertyName, TypeName>;
QVector<PropertyInfo> getObjectTypes(const ObjectValue *ov, const ContextPtr &context, bool local = false, int rec = 0);
static TypeName resolveTypeName(const ASTPropertyReference *ref, const ContextPtr &context, QVector<PropertyInfo> &dotProperties)
static TypeName resolveTypeName(const ASTPropertyReference *ref, const ContextPtr &context, QVector<PropertyInfo> &dotProperties)
{
TypeName type = "unknown";
@@ -295,7 +295,7 @@ public:
const TypeName type = resolveTypeName(ref, m_context, dotProperties);
m_properties.append({propertyName, type});
if (!dotProperties.isEmpty()) {
foreach (const PropertyInfo &propertyInfo, dotProperties) {
for (const PropertyInfo &propertyInfo : qAsConst(dotProperties)) {
PropertyName dotName = propertyInfo.first;
TypeName type = propertyInfo.second;
dotName = propertyName + '.' + dotName;
@@ -303,7 +303,6 @@ public:
}
}
} else {
if (const CppComponentValue * cppComponentValue = value_cast<CppComponentValue>(value)) {
TypeName qualifiedTypeName = qualifiedTypeNameForContext(cppComponentValue,
m_context->viewerContext(), *m_context->snapshot().importDependencies()).toUtf8();
@@ -311,13 +310,13 @@ public:
} else {
TypeId typeId;
TypeName typeName = typeId(value).toUtf8();
if (typeName == "number") {
if (value->asIntValue()) {
typeName = "int";
} else {
typeName = "real";
}
}
if (typeName == "Function")
return processSlot(name, value);
if (typeName == "number")
typeName = value->asIntValue() ? "int" : "real";
m_properties.append({propertyName, typeName});
}
}
@@ -488,7 +487,7 @@ PropertyNameList getSignals(const ObjectValue *objectValue, const ContextPtr &co
QList<const ObjectValue *> objects = prototypeIterator.all();
if (!local) {
foreach (const ObjectValue *prototype, objects)
for (const ObjectValue *prototype : objects)
signalList.append(getSignals(prototype, context, true));
}
@@ -507,6 +506,9 @@ PropertyNameList getSlots(const ObjectValue *objectValue, const ContextPtr &cont
PropertyMemberProcessor processor(context);
objectValue->processMembers(&processor);
if (const ASTObjectValue *astObjectValue = objectValue->asAstObjectValue())
astObjectValue->processMembers(&processor);
slotList.append(processor.slotList());
PrototypeIterator prototypeIterator(objectValue, context);
@@ -534,6 +536,7 @@ QVector<PropertyInfo> getObjectTypes(const ObjectValue *objectValue, const Conte
PropertyMemberProcessor processor(context);
objectValue->processMembers(&processor);
const auto props = processor.properties();
for (const PropertyInfo &property : props) {

View File

@@ -0,0 +1,130 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qmlconnections.h"
#include <metainfo.h>
#include <abstractview.h>
#include <bindingproperty.h>
#include <utils/qtcassert.h>
namespace QmlDesigner {
QmlConnections::QmlConnections()
{
}
QmlConnections::QmlConnections(const ModelNode &modelNode)
: QmlModelNodeFacade(modelNode)
{
}
bool QmlConnections::isValid() const
{
return isValidQmlConnections(modelNode());
}
bool QmlConnections::isValidQmlConnections(const ModelNode &modelNode)
{
return isValidQmlModelNodeFacade(modelNode)
&& modelNode.metaInfo().isValid()
&& (modelNode.type() == "Connections"
|| modelNode.type() == "QtQuick.Connections"
|| modelNode.type() == "Qt.Connections"
|| modelNode.type() == "QtQml.Connections");
}
/*!
Removes connections node.
*/
void QmlConnections::destroy()
{
Q_ASSERT(isValid());
modelNode().destroy();
}
QString QmlConnections::target() const
{
if (modelNode().isValid()) {
const BindingProperty bindingproperty = modelNode().bindingProperty("target");
if (bindingproperty.isValid())
return bindingproperty.expression();
}
return QString();
}
void QmlConnections::setTarget(const QString &target)
{
modelNode().bindingProperty("target").setExpression(target);
}
ModelNode QmlConnections::getTargetNode() const
{
ModelNode result;
if (!modelNode().isValid())
return result;
const BindingProperty bindingProperty = modelNode().bindingProperty("target");
const QString bindExpression = bindingProperty.expression();
if (bindingProperty.isValid()) {
AbstractView *view = modelNode().view();
if (bindExpression.contains(".")) {
QStringList substr = bindExpression.split(".");
const QString itemId = substr.constFirst();
if (substr.size() > 1) {
const ModelNode aliasParent = view->modelNodeForId(itemId);
substr.removeFirst(); // remove id, only alias pieces left
const QString aliasBody = substr.join(".");
if (aliasParent.isValid() && aliasParent.hasBindingProperty(aliasBody.toUtf8())) {
const BindingProperty binding = aliasParent.bindingProperty(aliasBody.toUtf8());
if (binding.isValid() && view->hasId(binding.expression()))
result = view->modelNodeForId(binding.expression());
}
}
} else {
result = view->modelNodeForId(bindExpression);
}
}
return result;
}
QList<SignalHandlerProperty> QmlConnections::signalProperties() const
{
return modelNode().signalProperties();
}
ModelNode QmlConnections::createQmlConnections(AbstractView *view)
{
NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections");
return view->createModelNode("QtQuick.Connections",
nodeMetaInfo.majorVersion(),
nodeMetaInfo.minorVersion());
}
} // QmlDesigner

View File

@@ -40,13 +40,11 @@ SignalHandlerProperty::SignalHandlerProperty(const SignalHandlerProperty &proper
{
}
SignalHandlerProperty::SignalHandlerProperty(const PropertyName &propertyName, const Internal::InternalNodePointer &internalNode, Model* model, AbstractView *view)
: AbstractProperty(propertyName, internalNode, model, view)
{
}
void SignalHandlerProperty::setSource(const QString &source)
{
Internal::WriteLocker locker(model());
@@ -83,4 +81,30 @@ QString SignalHandlerProperty::source() const
return QString();
}
PropertyName SignalHandlerProperty::prefixAdded(const PropertyName &propertyName)
{
QString nameAsString = QString::fromUtf8(propertyName);
if (nameAsString.startsWith("on"))
return propertyName;
QChar firstChar = nameAsString.at(0).toUpper();
nameAsString[0] = firstChar;
nameAsString.prepend("on");
return nameAsString.toLatin1();
}
PropertyName SignalHandlerProperty::prefixRemoved(const PropertyName &propertyName)
{
QString nameAsString = QString::fromUtf8(propertyName);
if (!nameAsString.startsWith("on"))
return propertyName;
nameAsString.remove(0, 2);
QChar firstChar = nameAsString.at(0).toLower();
nameAsString[0] = firstChar;
return nameAsString.toLatin1();
}
} // namespace QmlDesigner

View File

@@ -302,6 +302,7 @@ Project {
"include/qmlmodelnodefacade.h",
"include/qmlobjectnode.h",
"include/qmlstate.h",
"include/qmlconnections.h",
"include/removebasestateexception.h",
"include/documentmessage.h",
"include/rewriterview.h",
@@ -391,6 +392,7 @@ Project {
"model/qmlmodelnodefacade.cpp",
"model/qmlobjectnode.cpp",
"model/qmlstate.cpp",
"model/qmlconnections.cpp",
"model/qmltextgenerator.cpp",
"model/qmltextgenerator.h",
"model/rewriteaction.cpp",
@@ -739,6 +741,12 @@ Project {
"bindingeditor/bindingeditorwidget.h",
"bindingeditor/connectionvisitor.cpp",
"bindingeditor/connectionvisitor.h",
"bindingeditor/signallist.cpp",
"bindingeditor/signallist.h",
"bindingeditor/signallistdialog.cpp",
"bindingeditor/signallistdialog.h",
"bindingeditor/signallistdelegate.cpp",
"bindingeditor/signallistdelegate.h",
"colortool/colortool.cpp",
"colortool/colortool.h",
"connectioneditor/addnewbackenddialog.h",