diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 96e98868bdb..bbc2444ac12 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -107,8 +107,8 @@ Rectangle { } Item { - Layout.preferredWidth: 16 - Layout.preferredHeight: 16 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 } } @@ -134,8 +134,10 @@ Rectangle { Image { visible: !modelNodeBackend.multiSelection - Layout.preferredWidth: 16 - Layout.preferredHeight: 16 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter source: hasAliasExport ? "image://icons/alias-export-checked" : "image://icons/alias-export-unchecked" ToolTipArea { enabled: !modelNodeBackend.multiSelection @@ -145,6 +147,110 @@ Rectangle { } } } + + Label { + text: qsTr("Custom id") + } + + SecondColumnLayout { + enabled: !modelNodeBackend.multiSelection + visible: enabled + spacing: 2 + + LineEdit { + id: annotationEdit + enabled: annotationEditor.hasAuxData + visible: enabled + + backendValue: backendValues.customId__AUX + placeholderText: qsTr("customId") + text: backendValue.value + Layout.fillWidth: true + Layout.preferredWidth: 240 + width: 240 + showTranslateCheckBox: false + showExtendedFunctionButton: false + + onHoveredChanged: annotationEditor.checkAux() + } + + StudioControls.AbstractButton { + id: editAnnotationButton + enabled: annotationEditor.hasAuxData + visible: enabled + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + width: 22 + + buttonIcon: StudioTheme.Constants.edit + + onClicked: annotationEditor.showWidget() + + onHoveredChanged: annotationEditor.checkAux() + } + + StudioControls.AbstractButton { + id: removeAnnotationButton + enabled: annotationEditor.hasAuxData + visible: enabled + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + width: 22 + + buttonIcon: StudioTheme.Constants.closeCross + + onClicked: annotationEditor.removeFullAnnotation() + + onHoveredChanged: annotationEditor.checkAux() + } + + StudioControls.AbstractButton { + id: addAnnotationButton + enabled: !annotationEditor.hasAuxData + visible: enabled + + buttonIcon: qsTr("Add Annotation") + iconFont: StudioTheme.Constants.font + Layout.fillWidth: true + Layout.preferredWidth: 240 + + onClicked: annotationEditor.showWidget() + + onHoveredChanged: annotationEditor.checkAux() + } + + Item { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + enabled: !annotationEditor.hasAuxData + visible: enabled + } + + AnnotationEditor { + id: annotationEditor + + modelNodeBackendProperty: modelNodeBackend + + property bool hasAuxData: (annotationEditor.hasAnnotation || annotationEditor.hasCustomId) + + onModelNodeBackendChanged: checkAux() + onCustomIdChanged: checkAux() + onAnnotationChanged: checkAux() + + function checkAux() { + hasAuxData = (annotationEditor.hasAnnotation || annotationEditor.hasCustomId) + annotationEdit.update() + } + + onAccepted: { + hideWidget() + } + + onCanceled: { + hideWidget() + } + } + } } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 4eb99638c74..ca2605877e2 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -208,6 +208,7 @@ extend_qtc_plugin(QmlDesigner controlelement.cpp controlelement.h dragtool.cpp dragtool.h formeditor.qrc + formeditorannotationicon.cpp formeditorannotationicon.h formeditorgraphicsview.cpp formeditorgraphicsview.h formeditoritem.cpp formeditoritem.h formeditorscene.cpp formeditorscene.h @@ -538,6 +539,15 @@ extend_qtc_plugin(QmlDesigner SOURCES colortool.cpp colortool.h ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/annotationeditor + SOURCES annotation.cpp annotation.h + annotationcommenttab.cpp annotationcommenttab.h annotationcommenttab.ui + annotationeditordialog.cpp annotationeditordialog.h annotationeditordialog.ui + annotationeditor.cpp annotationeditor.h + annotationtool.cpp annotationtool.h +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/connectioneditor SOURCES diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotation.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotation.cpp new file mode 100644 index 00000000000..b5419b8d956 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotation.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** 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 "annotation.h" + +#include + +namespace QmlDesigner { + +static const QString s_sep = " //;;// "; //separator + +Comment::Comment() + : m_title(QString()) + , m_author(QString()) + , m_text(QString()) + , m_timestamp(0) +{} + +Comment::Comment(const QString &title, const QString &author, const QString &text, qint64 timestamp) + : m_title(title) + , m_author(author) + , m_text(text) + , m_timestamp(timestamp) +{} + +QString Comment::title() const +{ + return m_title; +} + +void Comment::setTitle(const QString &title) +{ + m_title = title; +} + +QString Comment::author() const +{ + return m_author; +} + +void Comment::setAuthor(const QString &author) +{ + m_author = author; +} + +QString Comment::text() const +{ + return m_text; +} + +void Comment::setText(const QString &text) +{ + m_text = text; +} + +QString Comment::timestampStr() const +{ + return QDateTime::fromSecsSinceEpoch(m_timestamp).toString(); +} + +QString Comment::timestampStr(const QString &format) const +{ + return QDateTime::fromSecsSinceEpoch(m_timestamp).toString(format); +} + +qint64 Comment::timestamp() const +{ + return m_timestamp; +} + +void Comment::setTimestamp(qint64 timestamp) +{ + m_timestamp = timestamp; +} + +void Comment::updateTimestamp() +{ + m_timestamp = QDateTime::currentSecsSinceEpoch(); +} + +bool Comment::sameContent(const Comment &comment) const +{ + return sameContent(*this, comment); +} + +bool Comment::sameContent(const Comment &a, const Comment &b) +{ + return ((a.title() == b.title()) + && (a.author() == b.author()) + && (a.text() == b.text())); +} + +bool Comment::operator==(const Comment &comment) const +{ + return (sameContent(comment) && (m_timestamp == comment.timestamp())); +} + +bool Comment::isEmpty() +{ + return sameContent(Comment()); +} + +QString Comment::toQString() const +{ + QStringList result; + + result.push_back(m_title); + result.push_back(m_author); + result.push_back(m_text); + result.push_back(QString::number(m_timestamp)); + + return result.join(s_sep); +} + +QDebug &operator<<(QDebug &stream, const Comment &comment) +{ + stream << "\"title: " << comment.m_title << "\" "; + stream << "\"author: " << comment.m_author << "\" "; + stream << "\"text: " << comment.m_text << "\" "; + stream << "\"timestamp: " << comment.m_timestamp << "\" "; + stream << "\"date/time: " << QDateTime::fromSecsSinceEpoch(comment.m_timestamp).toString() << "\" "; + + return stream; +} + +QDataStream &operator<<(QDataStream &stream, const Comment &comment) +{ + stream << comment.m_title; + stream << comment.m_author; + stream << comment.m_text; + stream << comment.m_timestamp; + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, Comment &comment) +{ + stream >> comment.m_title; + stream >> comment.m_author; + stream >> comment.m_text; + stream >> comment.m_timestamp; + + return stream; +} + + +//Annotation + +Annotation::Annotation() + : m_comments() +{ + +} + +QVector Annotation::comments() const +{ + return m_comments; +} + + +bool Annotation::hasComments() const +{ + return !m_comments.isEmpty(); +} + +void Annotation::setComments(const QVector &comments) +{ + m_comments = comments; +} + +void Annotation::removeComments() +{ + m_comments.clear(); +} + +int Annotation::commentsSize() const +{ + return m_comments.size(); +} + +Comment Annotation::comment(int n) const +{ + if (m_comments.size() > n) + return m_comments.at(n); + else + return Comment(); +} + +void Annotation::addComment(const Comment &comment) +{ + m_comments.push_back(comment); +} + +bool Annotation::updateComment(const Comment &comment, int n) +{ + bool result = false; + + if ((m_comments.size() > n) && (n > 0)) { + m_comments[n] = comment; + result = true; + } + + return result; +} + +bool Annotation::removeComment(int n) +{ + bool result = false; + + if (m_comments.size() > n) { + m_comments.remove(n); + result = true; + } + + return result; +} + +QString Annotation::toQString() const +{ + QStringList result; + + result.push_back(QString::number(m_comments.size())); + + for (const Comment &com : m_comments) + result.push_back(com.toQString()); + + return result.join(s_sep); +} + +void Annotation::fromQString(const QString &str) +{ + QStringList strl (str.split(s_sep, QString::SplitBehavior::KeepEmptyParts)); + removeComments(); + + const int intro = 1; + const int comSize = 4; + + if (!strl.isEmpty()) { + + if (strl.size() >= intro) { + + int size = strl.at(0).toInt(); + + if (size > 0) { + if (strl.size() == (size*comSize) + intro) + { + for (int i = 0; i < size; i++) + { + const int offset = intro + (i * comSize); + Comment com; + com.setTitle(strl.at(offset + 0)); + com.setAuthor(strl.at(offset + 1)); + com.setText(strl.at(offset + 2)); + com.setTimestamp(strl.at(offset + 3).toLongLong()); + + m_comments.push_back(com); + } + } + } + } + } +} + +QDebug &operator<<(QDebug &stream, const Annotation &annotation) +{ + stream << "\"Annotation: " << annotation.m_comments << "\" "; + + return stream; +} + +QDataStream &operator<<(QDataStream &stream, const Annotation &annotation) +{ + stream << annotation.m_comments; + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, Annotation &annotation) +{ + stream >> annotation.m_comments; + + return stream; +} + +} // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotation.h b/src/plugins/qmldesigner/components/annotationeditor/annotation.h new file mode 100644 index 00000000000..95fcf81fa21 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotation.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "nodeinstanceglobal.h" + +namespace QmlDesigner { + +static const PropertyName customIdProperty = {("customId")}; +static const PropertyName annotationProperty = {("annotation")}; + +class Comment +{ +public: + Comment(); + Comment(const QString &title, const QString &author = QString(), const QString &text = QString(), qint64 timestamp = 0); + + ~Comment() = default; + + QString title() const; + void setTitle(const QString &title); + + QString text() const; + void setText(const QString &text); + + QString author() const; + void setAuthor(const QString &author); + + QString timestampStr() const; + QString timestampStr(const QString &format) const; + qint64 timestamp() const; + void setTimestamp(qint64 timestamp); + void updateTimestamp(); + + bool sameContent(const Comment &comment) const; //everything is similar besides timestamp + static bool sameContent(const Comment &a, const Comment &b); + bool operator==(const Comment &comment) const; //everything is similar. + + bool isEmpty(); + + QString toQString() const; + + friend QDebug &operator<<(QDebug &stream, const Comment &comment); + + friend QDataStream &operator<<(QDataStream &stream, const Comment &comment); + friend QDataStream &operator>>(QDataStream &stream, Comment &comment); + +private: + QString m_title; + QString m_author; + QString m_text; + qint64 m_timestamp; +}; + +class Annotation +{ +public: + Annotation(); + ~Annotation() = default; + + QVector comments() const; + bool hasComments() const; + void setComments(const QVector &comments); + void removeComments(); + int commentsSize() const; + + Comment comment(int n) const; + void addComment(const Comment &comment); + bool updateComment(const Comment &comment, int n); + bool removeComment(int n); + + QString toQString() const; + void fromQString(const QString &str); + + friend QDebug &operator<<(QDebug &stream, const Annotation &annotation); + + friend QDataStream &operator<<(QDataStream &stream, const Annotation &annotation); + friend QDataStream &operator>>(QDataStream &stream, Annotation &annotation); + +private: + QVector m_comments; +}; + +QDebug &operator<<(QDebug &stream, const Comment &comment); +QDebug &operator<<(QDebug &stream, const Annotation &annotation); + +QDataStream &operator<<(QDataStream &stream, const Comment &comment); +QDataStream &operator>>(QDataStream &stream, Comment &comment); +QDataStream &operator<<(QDataStream &stream, const Annotation &annotation); +QDataStream &operator>>(QDataStream &stream, Annotation &annotation); + +} + +Q_DECLARE_METATYPE(QmlDesigner::Comment); +Q_DECLARE_METATYPE(QmlDesigner::Annotation); diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp new file mode 100644 index 00000000000..bd7aed67d02 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp @@ -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 "annotationcommenttab.h" +#include "ui_annotationcommenttab.h" + +namespace QmlDesigner { + +AnnotationCommentTab::AnnotationCommentTab(QWidget *parent) : + QWidget(parent), + ui(new Ui::AnnotationCommentTab) +{ + ui->setupUi(this); + + connect(ui->titleEdit, &QLineEdit::textEdited, + this, &AnnotationCommentTab::commentTitleChanged); +} + +AnnotationCommentTab::~AnnotationCommentTab() +{ + delete ui; +} + +Comment AnnotationCommentTab::currentComment() const +{ + Comment result; + + result.setTitle(ui->titleEdit->text().trimmed()); + result.setAuthor(ui->authorEdit->text().trimmed()); + result.setText(ui->textEdit->toPlainText().trimmed()); + + if (m_comment.sameContent(result)) + result.setTimestamp(m_comment.timestamp()); + else + result.updateTimestamp(); + + return result; +} + +Comment AnnotationCommentTab::originalComment() const +{ + return m_comment; +} + +void AnnotationCommentTab::setComment(const Comment &comment) +{ + m_comment = comment; + resetUI(); +} + +void AnnotationCommentTab::resetUI() +{ + ui->titleEdit->setText(m_comment.title()); + ui->authorEdit->setText(m_comment.author()); + ui->textEdit->setText(m_comment.text()); + + if (m_comment.timestamp() > 0) + ui->timeLabel->setText(m_comment.timestampStr()); + else + ui->timeLabel->setText(""); +} + +void AnnotationCommentTab::resetComment() +{ + m_comment = currentComment(); +} + +void AnnotationCommentTab::commentTitleChanged(const QString &text) +{ + emit titleChanged(text, this); +} + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.h b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.h new file mode 100644 index 00000000000..cc8d4c3d769 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include "annotation.h" + +namespace QmlDesigner { + +namespace Ui { +class AnnotationCommentTab; +} + +class AnnotationCommentTab : public QWidget +{ + Q_OBJECT + +public: + explicit AnnotationCommentTab(QWidget *parent = nullptr); + ~AnnotationCommentTab(); + + Comment currentComment() const; + + Comment originalComment() const; + void setComment(const Comment &comment); + + void resetUI(); + void resetComment(); + +signals: + void titleChanged(const QString &text, QWidget *widget); + +private slots: + void commentTitleChanged(const QString &text); + +private: + Ui::AnnotationCommentTab *ui; + + Comment m_comment; +}; + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui new file mode 100644 index 00000000000..f6bf277eb7c --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui @@ -0,0 +1,71 @@ + + + QmlDesigner::AnnotationCommentTab + + + + 0 + 0 + 537 + 382 + + + + Form + + + + + + + + Title + + + + + + + + + + Text + + + + + + + true + + + + + + + Author + + + + + + + + + + + + + + + + + + + titleEdit + authorEdit + textEdit + + + + diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.cpp new file mode 100644 index 00000000000..46a98a07479 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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 "annotationeditor.h" + +#include "annotationeditordialog.h" +#include "annotation.h" + +#include "qmlmodelnodeproxy.h" +#include + +#include +#include +#include +#include + +namespace QmlDesigner { + +AnnotationEditor::AnnotationEditor(QObject *) +{ +} + +AnnotationEditor::~AnnotationEditor() +{ + hideWidget(); +} + +void AnnotationEditor::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "AnnotationEditor"); +} + +void AnnotationEditor::showWidget() +{ + m_dialog = new AnnotationEditorDialog(Core::ICore::dialogParent(), + modelNode().validId(), + modelNode().customId(), + modelNode().annotation()); + + QObject::connect(m_dialog, &AnnotationEditorDialog::accepted, + this, &AnnotationEditor::acceptedClicked); + QObject::connect(m_dialog, &AnnotationEditorDialog::rejected, + this, &AnnotationEditor::cancelClicked); + + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + + m_dialog->open(); +} + +void AnnotationEditor::showWidget(int x, int y) +{ + showWidget(); + m_dialog->move(x, y); +} + +void AnnotationEditor::hideWidget() +{ + if (m_dialog) + m_dialog->close(); + m_dialog = nullptr; +} + +void AnnotationEditor::setModelNode(const ModelNode &modelNode) +{ + m_modelNodeBackend = {}; + m_modelNode = modelNode; +} + +ModelNode AnnotationEditor::modelNode() const +{ + return m_modelNode; +} + +void AnnotationEditor::setModelNodeBackend(const QVariant &modelNodeBackend) +{ + if (!modelNodeBackend.isNull() && modelNodeBackend.isValid()) { + m_modelNodeBackend = modelNodeBackend; + + const auto modelNodeBackendObject = modelNodeBackend.value(); + const auto backendObjectCasted = + qobject_cast(modelNodeBackendObject); + + if (backendObjectCasted) + m_modelNode = backendObjectCasted->qmlObjectNode().modelNode(); + + emit modelNodeBackendChanged(); + } +} + +QVariant AnnotationEditor::modelNodeBackend() const +{ + return m_modelNodeBackend; +} + +bool AnnotationEditor::hasCustomId() const +{ + if (m_modelNode.isValid()) + return m_modelNode.hasCustomId(); + return false; +} + +bool AnnotationEditor::hasAnnotation() const +{ + if (m_modelNode.isValid()) + return m_modelNode.hasAnnotation(); + return false; +} + +void AnnotationEditor::removeFullAnnotation() +{ + if (!m_modelNode.isValid()) + return; + + QString dialogTitle = tr("Annotation"); + if (!m_modelNode.customId().isNull()) { + dialogTitle = m_modelNode.customId(); + } + QMessageBox *deleteDialog = new QMessageBox(Core::ICore::dialogParent()); + deleteDialog->setWindowTitle(dialogTitle); + deleteDialog->setText(tr("Delete this annotation?")); + deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + deleteDialog->setDefaultButton(QMessageBox::Yes); + + int result = deleteDialog->exec(); + if (deleteDialog) deleteDialog->deleteLater(); + + if (result == QMessageBox::Yes) { + m_modelNode.removeCustomId(); + m_modelNode.removeAnnotation(); + } + + emit customIdChanged(); + emit annotationChanged(); +} + +void AnnotationEditor::acceptedClicked() +{ + if (m_dialog) { + QString customId = m_dialog->customId(); + Annotation annotation = m_dialog->annotation(); + + m_modelNode.setCustomId(customId); + + if (annotation.comments().isEmpty()) + m_modelNode.removeAnnotation(); + else + m_modelNode.setAnnotation(annotation); + } + + hideWidget(); + + emit accepted(); + + emit customIdChanged(); + emit annotationChanged(); +} + +void AnnotationEditor::cancelClicked() +{ + hideWidget(); + + emit canceled(); + + emit customIdChanged(); + emit annotationChanged(); +} + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.h b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.h new file mode 100644 index 00000000000..85e8c8432cc --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "annotationeditordialog.h" +#include "annotation.h" + +#include "modelnode.h" + +namespace QmlDesigner { + +class AnnotationEditor : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend NOTIFY modelNodeBackendChanged) + Q_PROPERTY(bool hasCustomId READ hasCustomId NOTIFY customIdChanged) + Q_PROPERTY(bool hasAnnotation READ hasAnnotation NOTIFY annotationChanged) + +public: + explicit AnnotationEditor(QObject *parent = nullptr); + ~AnnotationEditor(); + + static void registerDeclarativeType(); + + Q_INVOKABLE void showWidget(); + Q_INVOKABLE void showWidget(int x, int y); + Q_INVOKABLE void hideWidget(); + + void setModelNode(const ModelNode &modelNode); + ModelNode modelNode() const; + + void setModelNodeBackend(const QVariant &modelNodeBackend); + QVariant modelNodeBackend() const; + + Q_INVOKABLE bool hasCustomId() const; + Q_INVOKABLE bool hasAnnotation() const; + + Q_INVOKABLE void removeFullAnnotation(); + +signals: + void accepted(); + void canceled(); + void modelNodeBackendChanged(); + + void customIdChanged(); + void annotationChanged(); + +private slots: + void acceptedClicked(); + void cancelClicked(); + +private: + QPointer m_dialog; + + ModelNode m_modelNode; + QVariant m_modelNodeBackend; +}; + +} //namespace QmlDesigner + +QML_DECLARE_TYPE(QmlDesigner::AnnotationEditor) diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.pri b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.pri new file mode 100644 index 00000000000..3cc6c6f856b --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditor.pri @@ -0,0 +1,14 @@ +HEADERS += $$PWD/annotation.h +HEADERS += $$PWD/annotationtool.h +HEADERS += $$PWD/annotationcommenttab.h +HEADERS += $$PWD/annotationeditordialog.h +HEADERS += $$PWD/annotationeditor.h + +SOURCES += $$PWD/annotation.cpp +SOURCES += $$PWD/annotationtool.cpp +SOURCES += $$PWD/annotationcommenttab.cpp +SOURCES += $$PWD/annotationeditordialog.cpp +SOURCES += $$PWD/annotationeditor.cpp + +FORMS += $$PWD/annotationcommenttab.ui +FORMS += $$PWD/annotationeditordialog.ui diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.cpp new file mode 100644 index 00000000000..ec99af6498f --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** 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 "annotationeditordialog.h" +#include "ui_annotationeditordialog.h" +#include "annotation.h" +#include "annotationcommenttab.h" + +#include "ui_annotationcommenttab.h" + +#include +#include +#include +#include + + +#include "timelineicons.h" //replace timeline icons with our own? + +namespace QmlDesigner { + +AnnotationEditorDialog::AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation) + : QDialog(parent) + , ui(new Ui::AnnotationEditorDialog) + , m_customId(customId) + , m_annotation(annotation) +{ + ui->setupUi(this); + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowFlag(Qt::Tool, true); + setWindowTitle(titleString); + + connect(this, &QDialog::accepted, this, &AnnotationEditorDialog::acceptedClicked); + + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AnnotationEditorDialog::tabChanged); + + auto *commentCornerWidget = new QToolBar; + + auto *commentAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), tr("Add Comment")); //timeline icons? + auto *commentRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(), + tr("Remove Comment")); //timeline icons? + + connect(commentAddAction, &QAction::triggered, this, [this]() { + addComment(Comment()); + }); + + connect(commentRemoveAction, &QAction::triggered, this, [this]() { + + if (ui->tabWidget->count() == 0) //it is not even supposed to happen but lets be sure + return; + + int currentIndex = ui->tabWidget->currentIndex(); + QString currentTitle = ui->tabWidget->tabText(currentIndex); + + QMessageBox *deleteDialog = new QMessageBox(this); + deleteDialog->setWindowTitle(currentTitle); + deleteDialog->setText(tr("Delete this comment?")); + deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + deleteDialog->setDefaultButton(QMessageBox::Yes); + + int result = deleteDialog->exec(); + + if (result == QMessageBox::Yes) { + removeComment(currentIndex); + } + + if (ui->tabWidget->count() == 0) //lets be sure that tabWidget is never empty + addComment(Comment()); + }); + + commentCornerWidget->addAction(commentAddAction); + commentCornerWidget->addAction(commentRemoveAction); + + ui->tabWidget->setCornerWidget(commentCornerWidget, Qt::TopRightCorner); + ui->targetIdEdit->setText(targetId); + + fillFields(); +} + +AnnotationEditorDialog::~AnnotationEditorDialog() +{ + delete ui; +} + +void AnnotationEditorDialog::setAnnotation(const Annotation &annotation) +{ + m_annotation = annotation; + fillFields(); +} + +Annotation AnnotationEditorDialog::annotation() const +{ + return m_annotation; +} + +void AnnotationEditorDialog::setCustomId(const QString &customId) +{ + m_customId = customId; + ui->customIdEdit->setText(m_customId); +} + +QString AnnotationEditorDialog::customId() const +{ + return m_customId; +} + +void AnnotationEditorDialog::acceptedClicked() +{ + m_customId = ui->customIdEdit->text(); + + Annotation annotation; + + annotation.removeComments(); + + for (int i = 0; i < ui->tabWidget->count(); i++) { + AnnotationCommentTab* tab = reinterpret_cast(ui->tabWidget->widget(i)); + if (!tab) + continue; + + Comment comment = tab->currentComment(); + + if (!comment.isEmpty()) + annotation.addComment(comment); + } + + m_annotation = annotation; + + emit AnnotationEditorDialog::accepted(); +} + +void AnnotationEditorDialog::commentTitleChanged(const QString &text, QWidget *tab) +{ + int tabIndex = ui->tabWidget->indexOf(tab); + if (tabIndex >= 0) + ui->tabWidget->setTabText(tabIndex, text); + + if (text.isEmpty()) + ui->tabWidget->setTabText(tabIndex, + (defaultTabName + " " + QString::number(tabIndex+1))); +} + +void AnnotationEditorDialog::fillFields() +{ + ui->customIdEdit->setText(m_customId); + setupComments(); +} + +void AnnotationEditorDialog::setupComments() +{ + ui->tabWidget->setUpdatesEnabled(false); + + deleteAllTabs(); + + const QVector comments = m_annotation.comments(); + + if (comments.isEmpty()) + addComment(Comment()); + + for (const Comment &comment : comments) { + addCommentTab(comment); + } + + ui->tabWidget->setUpdatesEnabled(true); +} + +void AnnotationEditorDialog::addComment(const Comment &comment) +{ + m_annotation.addComment(comment); + addCommentTab(comment); +} + +void AnnotationEditorDialog::removeComment(int index) +{ + if ((m_annotation.commentsSize() > index) && (index >= 0)) { + m_annotation.removeComment(index); + removeCommentTab(index); + } +} + +void AnnotationEditorDialog::addCommentTab(const Comment &comment) +{ + auto commentTab = new AnnotationCommentTab(); + commentTab->setComment(comment); + int tabIndex = ui->tabWidget->addTab(commentTab, comment.title()); + + if (comment.title().isEmpty()) + ui->tabWidget->setTabText(tabIndex, + (defaultTabName + " " + QString::number(tabIndex+1))); + + connect(commentTab, &AnnotationCommentTab::titleChanged, + this, &AnnotationEditorDialog::commentTitleChanged); +} + +void AnnotationEditorDialog::removeCommentTab(int index) +{ + if ((ui->tabWidget->count() > index) && (index >= 0)) { + ui->tabWidget->removeTab(index); + } +} + +void AnnotationEditorDialog::deleteAllTabs() +{ + while (ui->tabWidget->count() > 0) { + QWidget *w = ui->tabWidget->widget(0); + ui->tabWidget->removeTab(0); + delete w; + } +} + +void AnnotationEditorDialog::tabChanged(int index) +{ + QWidget *w = ui->tabWidget->widget(index); + AnnotationCommentTab *tab = nullptr; + if (w) + tab = reinterpret_cast(w); + + if (tab) { + //this tab order resetting doesn't work + QWidget::setTabOrder(ui->targetIdEdit, ui->customIdEdit); + QWidget::setTabOrder(ui->customIdEdit, ui->tabWidget); + QWidget::setTabOrder(ui->tabWidget, tab); + QWidget::setTabOrder(tab, ui->buttonBox); + } +} + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.h b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.h new file mode 100644 index 00000000000..1324115a724 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include "annotation.h" + +namespace QmlDesigner { + +namespace Ui { +class AnnotationEditorDialog; +} + +class AnnotationEditorDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation); + ~AnnotationEditorDialog(); + + void setAnnotation(const Annotation &annotation); + Annotation annotation() const; + + void setCustomId(const QString &customId); + QString customId() const; + +signals: + void accepted(); + +private slots: + void acceptedClicked(); + void tabChanged(int index); + void commentTitleChanged(const QString &text, QWidget *tab); + +private: + void fillFields(); + void setupComments(); + void addComment(const Comment &comment); + void removeComment(int index); + + void addCommentTab(const Comment &comment); + void removeCommentTab(int index); + void deleteAllTabs(); + +private: + const QString titleString = {tr("Annotation Editor")}; + const QString defaultTabName = {tr("Annotation")}; + Ui::AnnotationEditorDialog *ui; + + QString m_customId; + Annotation m_annotation; +}; + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.ui b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.ui new file mode 100644 index 00000000000..8ca9b75136c --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationeditordialog.ui @@ -0,0 +1,131 @@ + + + QmlDesigner::AnnotationEditorDialog + + + + 0 + 0 + 700 + 487 + + + + Dialog + + + + + + + + + + Selected Item + + + + + + + false + + + true + + + + + + + + + + + Custom ID + + + + + + + + + + + + + + 0 + + + true + + + + Tab 1 + + + + + Tab 2 + + + + + + + + Qt::StrongFocus + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + targetIdEdit + customIdEdit + tabWidget + + + + + buttonBox + accepted() + QmlDesigner::AnnotationEditorDialog + accept() + + + 261 + 473 + + + 157 + 274 + + + + + buttonBox + rejected() + QmlDesigner::AnnotationEditorDialog + reject() + + + 329 + 473 + + + 286 + 274 + + + + + diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationtool.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationtool.cpp new file mode 100644 index 00000000000..522505dd9ca --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationtool.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** 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 "annotationtool.h" + +#include "formeditorscene.h" +#include "formeditorview.h" +#include "formeditorwidget.h" +#include "itemutilfunctions.h" +#include "formeditoritem.h" + +#include "nodemetainfo.h" +#include "qmlitemnode.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +class AnnotationToolAction : public AbstractAction +{ +public: + AnnotationToolAction() : AbstractAction(QCoreApplication::translate("AnnotationToolAction","Edit Annotation")) + { + } + + QByteArray category() const override + { + return QByteArray(); + } + + QByteArray menuId() const override + { + return "AnnotationTool"; + } + + int priority() const override + { + return CustomActionsPriority + 5; + } + + Type type() const override + { + return FormEditorAction; + } + +protected: + bool isVisible(const SelectionContext &selectionContext) const override + { + return selectionContext.singleNodeIsSelected(); + } + + bool isEnabled(const SelectionContext &selectionContext) const override + { + return isVisible(selectionContext); + } +}; + +AnnotationTool::AnnotationTool() +{ + auto annotationToolAction = new AnnotationToolAction; + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(annotationToolAction); + connect(annotationToolAction->action(), &QAction::triggered, [=]() { + view()->changeCurrentToolTo(this); + }); +} + +AnnotationTool::~AnnotationTool() = default; + +void AnnotationTool::clear() +{ + if (m_annotationEditor) + m_annotationEditor->deleteLater(); + + AbstractFormEditorTool::clear(); +} + +void AnnotationTool::mousePressEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) +{ + AbstractFormEditorTool::mousePressEvent(itemList, event); +} + +void AnnotationTool::mouseMoveEvent(const QList & /*itemList*/, + QGraphicsSceneMouseEvent * /*event*/) +{ +} + +void AnnotationTool::hoverMoveEvent(const QList & /*itemList*/, + QGraphicsSceneMouseEvent * /*event*/) +{ +} + +void AnnotationTool::keyPressEvent(QKeyEvent * /*keyEvent*/) +{ +} + +void AnnotationTool::keyReleaseEvent(QKeyEvent * /*keyEvent*/) +{ +} + +void AnnotationTool::dragLeaveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ +} + +void AnnotationTool::dragMoveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ +} + +void AnnotationTool::mouseReleaseEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) +{ + AbstractFormEditorTool::mouseReleaseEvent(itemList, event); +} + + +void AnnotationTool::mouseDoubleClickEvent(const QList &itemList, QGraphicsSceneMouseEvent *event) +{ + AbstractFormEditorTool::mouseDoubleClickEvent(itemList, event); +} + +void AnnotationTool::itemsAboutToRemoved(const QList &removedItemList) +{ + if (m_annotationEditor.isNull()) + return; + + if (removedItemList.contains(m_formEditorItem)) + view()->changeToSelectionTool(); +} + +void AnnotationTool::selectedItemsChanged(const QList &itemList) +{ + if (!itemList.isEmpty()) { + m_formEditorItem = itemList.constFirst(); + + m_oldCustomId = m_formEditorItem->qmlItemNode().modelNode().customId(); + m_oldAnnotation = m_formEditorItem->qmlItemNode().modelNode().annotation(); + + if (m_annotationEditor.isNull()) { + m_annotationEditor = new AnnotationEditorDialog(view()->formEditorWidget()->parentWidget(), + m_formEditorItem->qmlItemNode().modelNode().displayName(), + m_oldCustomId, m_oldAnnotation); + + connect(m_annotationEditor, &AnnotationEditorDialog::accepted, this, &AnnotationTool::annotationDialogAccepted); + connect(m_annotationEditor, &QDialog::rejected, this, &AnnotationTool::annotationDialogRejected); +// connect(m_colorDialog.data(), &QColorDialog::currentColorChanged, this, &ColorTool::currentColorChanged); + + m_annotationEditor->exec(); + } + } else { + view()->changeToSelectionTool(); + } +} + +void AnnotationTool::instancesCompleted(const QList & /*itemList*/) +{ +} + +void AnnotationTool::instancesParentChanged(const QList & /*itemList*/) +{ +} + +void AnnotationTool::instancePropertyChange(const QList > & /*propertyList*/) +{ +} + +void AnnotationTool::formEditorItemsChanged(const QList & /*itemList*/) +{ +} + +int AnnotationTool::wantHandleItem(const ModelNode & /*modelNode*/) const +{ + return 10; +} + +QString AnnotationTool::name() const +{ + return tr("Annotation Tool"); +} + +void AnnotationTool::annotationDialogAccepted() +{ + if (m_annotationEditor) { + saveNewCustomId(m_annotationEditor->customId()); + saveNewAnnotation(m_annotationEditor->annotation()); + + m_annotationEditor->close(); + m_annotationEditor->deleteLater(); + } + + m_annotationEditor = nullptr; + + view()->changeToSelectionTool(); +} + +void AnnotationTool::saveNewCustomId(const QString &customId) +{ + if (m_formEditorItem) { + m_oldCustomId = customId; + m_formEditorItem->qmlItemNode().modelNode().setCustomId(customId); + } +} + +void AnnotationTool::saveNewAnnotation(const Annotation &annotation) +{ + if (m_formEditorItem) { + if (annotation.comments().isEmpty()) + m_formEditorItem->qmlItemNode().modelNode().removeAnnotation(); + else + m_formEditorItem->qmlItemNode().modelNode().setAnnotation(annotation); + + m_oldAnnotation = annotation; + } +} + +void AnnotationTool::annotationDialogRejected() +{ + if (m_annotationEditor) { + m_annotationEditor->close(); + m_annotationEditor->deleteLater(); + } + + m_annotationEditor = nullptr; + + view()->changeToSelectionTool(); +} + +} diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationtool.h b/src/plugins/qmldesigner/components/annotationeditor/annotationtool.h new file mode 100644 index 00000000000..0073286dd62 --- /dev/null +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationtool.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "annotation.h" +#include "annotationeditordialog.h" +#include "abstractcustomtool.h" +#include "selectionindicator.h" + +#include +#include +#include + +namespace QmlDesigner { + +class AnnotationTool : public QObject, public AbstractCustomTool +{ + Q_OBJECT +public: + AnnotationTool(); + ~AnnotationTool() override; + + void mousePressEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) override; + void hoverMoveEvent(const QList &itemList, + QGraphicsSceneMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + + void dragLeaveEvent(const QList &itemList, + QGraphicsSceneDragDropEvent * event) override; + void dragMoveEvent(const QList &itemList, + QGraphicsSceneDragDropEvent * event) override; + + void itemsAboutToRemoved(const QList &itemList) override; + + void selectedItemsChanged(const QList &itemList) override; //impl needed + + void instancesCompleted(const QList &itemList) override; + void instancesParentChanged(const QList &itemList) override; + void instancePropertyChange(const QList > &propertyList) override; + + void clear() override; + + void formEditorItemsChanged(const QList &itemList) override; + + int wantHandleItem(const ModelNode &modelNode) const override; + + QString name() const override; + +private: + void annotationDialogAccepted(); + void annotationDialogRejected(); + void saveNewCustomId(const QString &customId); + void saveNewAnnotation(const Annotation &annotation); + +private: + FormEditorItem *m_formEditorItem = nullptr; + QString m_oldCustomId; + Annotation m_oldAnnotation; + QPointer m_annotationEditor; +}; + +} diff --git a/src/plugins/qmldesigner/components/formeditor/annotationsIcon.png b/src/plugins/qmldesigner/components/formeditor/annotationsIcon.png new file mode 100644 index 00000000000..a18e863a4f2 Binary files /dev/null and b/src/plugins/qmldesigner/components/formeditor/annotationsIcon.png differ diff --git a/src/plugins/qmldesigner/components/formeditor/annotationsIconActive.png b/src/plugins/qmldesigner/components/formeditor/annotationsIconActive.png new file mode 100644 index 00000000000..8367d46f2a2 Binary files /dev/null and b/src/plugins/qmldesigner/components/formeditor/annotationsIconActive.png differ diff --git a/src/plugins/qmldesigner/components/formeditor/formeditor.pri b/src/plugins/qmldesigner/components/formeditor/formeditor.pri index f4e345db848..dba3a8a6b74 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditor.pri +++ b/src/plugins/qmldesigner/components/formeditor/formeditor.pri @@ -36,7 +36,8 @@ SOURCES += formeditoritem.cpp \ contentnoteditableindicator.cpp \ backgroundaction.cpp \ formeditortoolbutton.cpp \ - option3daction.cpp + option3daction.cpp \ + formeditorannotationicon.cpp HEADERS += formeditorscene.h \ formeditorwidget.h \ @@ -75,6 +76,7 @@ HEADERS += formeditorscene.h \ contentnoteditableindicator.h \ backgroundaction.h \ formeditortoolbutton.h \ - option3daction.h + option3daction.h \ + formeditorannotationicon.h RESOURCES += formeditor.qrc diff --git a/src/plugins/qmldesigner/components/formeditor/formeditor.qrc b/src/plugins/qmldesigner/components/formeditor/formeditor.qrc index 3da1cfc3ce0..45170bde01d 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditor.qrc +++ b/src/plugins/qmldesigner/components/formeditor/formeditor.qrc @@ -6,5 +6,7 @@ no_snapping@2x.png snapping_and_anchoring.png snapping_and_anchoring@2x.png + annotationsIcon.png + annotationsIconActive.png diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp new file mode 100644 index 00000000000..367635d59f0 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp @@ -0,0 +1,463 @@ +/**************************************************************************** +** +** 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 "formeditorannotationicon.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +namespace QmlDesigner { + +FormEditorAnnotationIcon::FormEditorAnnotationIcon(const ModelNode &modelNode, QGraphicsItem *parent) + : QGraphicsObject(parent) + , m_modelNode(modelNode) + , m_readerIsActive(false) + , m_customId(modelNode.customId()) + , m_annotation(modelNode.annotation()) + , m_annotationEditor(nullptr) + , m_normalIconStr(":icon/layout/annotationsIcon.png") + , m_activeIconStr(":icon/layout/annotationsIconActive.png") + , m_iconW(40) + , m_iconH(32) +{ + setAcceptHoverEvents(true); + + bool hasAuxData = modelNode.hasAnnotation() || modelNode.hasCustomId(); + + setEnabled(hasAuxData); + setVisible(hasAuxData); + + FormEditorScene *scene = qobject_cast(parentItem()->scene()); + if (scene) { + m_readerIsActive = scene->annotationVisibility(); + if (m_readerIsActive) { + drawReader(); + } + } + + setToolTip(tr("Annotation")); + setCursor(Qt::ArrowCursor); +} + +FormEditorAnnotationIcon::~FormEditorAnnotationIcon() +{ + if (m_annotationEditor) { + m_annotationEditor->deleteLater(); + } +} + +void FormEditorAnnotationIcon::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + painter->setPen(Qt::NoPen); + + if (!isEnabled()) + setOpacity(0.5); + + bool hasAuxData = m_modelNode.hasAnnotation() || m_modelNode.hasCustomId(); + + if (hasAuxData) { + FormEditorScene *scene = qobject_cast(parentItem()->scene()); + if (scene) + m_readerIsActive = scene->annotationVisibility(); + + QPixmap icon( (m_readerIsActive ? m_activeIconStr : m_normalIconStr) ); + + painter->drawPixmap(0, 0, + static_cast(m_iconW), static_cast(m_iconH), + icon); + + m_customId = m_modelNode.customId(); + m_annotation = m_modelNode.annotation(); + + if (m_readerIsActive) + resetReader(); + } + else { + hideReader(); + } + + setEnabled(hasAuxData); + setVisible(hasAuxData); + + painter->restore(); +} + +QRectF FormEditorAnnotationIcon::boundingRect() const +{ + return QRectF(0, 0, m_iconW, m_iconH); +} + +qreal FormEditorAnnotationIcon::iconWidth() +{ + return m_iconW; +} + +qreal FormEditorAnnotationIcon::iconHeight() +{ + return m_iconH; +} + +bool FormEditorAnnotationIcon::isReaderActive() +{ + return m_readerIsActive; +} + +void FormEditorAnnotationIcon::setActive(bool readerStatus) +{ + m_readerIsActive = readerStatus; + + if (m_readerIsActive) + resetReader(); + else + hideReader(); + + update(); +} + +void FormEditorAnnotationIcon::hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + QGraphicsItem::hoverEnterEvent(event); + event->accept(); + update(); +} + +void FormEditorAnnotationIcon::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + QGraphicsItem::hoverLeaveEvent(event); + event->accept(); + update(); +} + +void FormEditorAnnotationIcon::hoverMoveEvent(QGraphicsSceneHoverEvent * event) +{ + QGraphicsItem::hoverMoveEvent(event); +} + +void FormEditorAnnotationIcon::mousePressEvent(QGraphicsSceneMouseEvent * event) +{ + event->accept(); + Qt::MouseButton button = event->button(); + + if (button == Qt::LeftButton) { + if (m_readerIsActive) { + hideReader(); + m_readerIsActive = false; + } else { + drawReader(); + m_readerIsActive = true; + } + } + + FormEditorScene *scene = qobject_cast(parentItem()->scene()); + if (scene) + scene->setAnnotationVisibility(m_readerIsActive); + + update(); +} + +void FormEditorAnnotationIcon::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) +{ + event->accept(); +} + +void FormEditorAnnotationIcon::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + QMenu menu; + menu.addAction(tr("Edit Annotation"), [this]() { + createAnnotationEditor(); + }); + + menu.addAction(tr("Remove Annotation"), [this]() { + removeAnnotationDialog(); + }); + + menu.exec(event->screenPos()); + event->accept(); +} + +void FormEditorAnnotationIcon::resetReader() +{ + hideReader(); + drawReader(); +} + +void FormEditorAnnotationIcon::drawReader() +{ + const qreal width = 290; + const qreal height = 200; + const qreal offset = 5; + + const QRectF titleRect(0, 0, width, 30); + const QPointF cornerPosition(m_iconW + offset, 0); + + QGraphicsItem *titleBubble = createTitleBubble(titleRect, m_customId, this); + titleBubble->setPos(cornerPosition); + + if (m_annotation.hasComments()) { + QList comments; + + QPointF commentPosition(cornerPosition.x(), 40); + QRectF commentRect(0, 0, width, height); + + for (const Comment &comment : m_annotation.comments()) { + QGraphicsItem *commentBubble = createCommentBubble(commentRect, comment.title(), + comment.author(), comment.text(), + comment.timestampStr(), this); + commentBubble->setPos(commentPosition); + + commentPosition += QPointF(width + offset, 0); + comments.push_back(commentBubble); + } + + + int currentColumn = 0; + qreal columnHeight = 0; + const qreal maxHeight = 650; + const QPointF commentsStartPosition(cornerPosition.x(), cornerPosition.y() + titleRect.height() + (offset*2)); + QPointF newPos(commentsStartPosition); + + for (QGraphicsItem *comment : comments) { + qreal itemHeight = comment->boundingRect().height(); + + if ((columnHeight + offset + itemHeight) > maxHeight) { + // have no extra space + columnHeight = 0; + ++currentColumn; + + newPos = commentsStartPosition + QPointF(currentColumn * (offset + width), 0); + + } else { + //few normal comments, lets stack them + } + + columnHeight += itemHeight + offset; + + comment->setPos(newPos); + + newPos += QPointF(0, itemHeight + offset); + } + } +} + +void FormEditorAnnotationIcon::hideReader() +{ + if (!childItems().isEmpty()) { + for (QGraphicsItem *item : childItems()) { + delete item; + } + } +} + +QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(const QRectF &rect, const QString &title, + const QString &author, const QString &text, + const QString &date, QGraphicsItem *parent) +{ + static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor); + static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker); + static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor); + QFont font; + font.setBold(true); + + QGraphicsRectItem *frameItem = new QGraphicsRectItem(rect, parent); + + QGraphicsTextItem *titleItem = new QGraphicsTextItem(frameItem); + titleItem->setPlainText(title); + titleItem->setFont(font); + titleItem->setDefaultTextColor(textColor); + titleItem->setTextWidth(rect.width()); + titleItem->update(); + + QGraphicsTextItem *authorItem = new QGraphicsTextItem(frameItem); + authorItem->setPlainText(tr("By: ") + author); + authorItem->setDefaultTextColor(textColor); + authorItem->setTextWidth(rect.width()); + authorItem->setPos(titleItem->x(), titleItem->boundingRect().height() + titleItem->y()); + authorItem->update(); + + QGraphicsTextItem *textItem = new QGraphicsTextItem(frameItem); + textItem->setPlainText(text); + textItem->setDefaultTextColor(textColor); + textItem->setTextWidth(rect.width()); + textItem->setPos(authorItem->x(), authorItem->boundingRect().height() + authorItem->y() + 5); + textItem->update(); + + qreal contentRect = titleItem->boundingRect().height() + + authorItem->boundingRect().height() + + textItem->boundingRect().height(); + + if ((contentRect + 60) > rect.height()) { + frameItem->setRect(rect.x(), rect.y(), rect.width(), contentRect+60); + } + + QGraphicsTextItem *dateItem = new QGraphicsTextItem(frameItem); + dateItem->setPlainText(tr("Edited: ") + date); + dateItem->setDefaultTextColor(textColor); + dateItem->setTextWidth(rect.width()); + dateItem->setPos(frameItem->boundingRect().bottomLeft() + QPointF(0, -30)); + dateItem->update(); + + QPen pen; + pen.setCosmetic(true); + pen.setWidth(2); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::BevelJoin); + pen.setColor(frameColor); + + frameItem->setPen(pen); //outline + frameItem->setBrush(backgroundColor); //back + frameItem->update(); + + return frameItem; +} + +QGraphicsItem *FormEditorAnnotationIcon::createTitleBubble(const QRectF &rect, const QString &text, QGraphicsItem *parent) +{ + static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor); + static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker); + static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor); + QFont font; + font.setBold(true); + + QGraphicsRectItem *frameItem = new QGraphicsRectItem(rect, parent); + QGraphicsTextItem *titleItem = new QGraphicsTextItem(text, frameItem); + + titleItem->setDefaultTextColor(textColor); + titleItem->setFont(font); + titleItem->update(); + + if (titleItem->boundingRect().width() > rect.width()) { + frameItem->setRect(QRectF(rect.x(), rect.y(), + titleItem->boundingRect().width(), rect.height())); + } + + QPen pen; + pen.setCosmetic(true); + pen.setWidth(2); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::BevelJoin); + pen.setColor(frameColor); + + frameItem->setPen(pen); //outline + frameItem->setBrush(backgroundColor); //back + frameItem->update(); + + return frameItem; +} + +void FormEditorAnnotationIcon::createAnnotationEditor() +{ + if (m_annotationEditor) { + m_annotationEditor->close(); + m_annotationEditor->deleteLater(); + m_annotationEditor = nullptr; + } + + m_annotationEditor = new AnnotationEditorDialog(Core::ICore::dialogParent(), + m_modelNode.displayName(), + m_modelNode.customId(), + m_modelNode.annotation()); + + connect(m_annotationEditor, &AnnotationEditorDialog::accepted, + this, &FormEditorAnnotationIcon::annotationDialogAccepted); + connect(m_annotationEditor, &QDialog::rejected, + this, &FormEditorAnnotationIcon::annotationDialogRejected); + + m_annotationEditor->open(); +} + +void FormEditorAnnotationIcon::removeAnnotationDialog() +{ + QString dialogTitle = tr("Annotation"); + if (!m_customId.isNull()) { + dialogTitle = m_customId; + } + QMessageBox *deleteDialog = new QMessageBox(Core::ICore::dialogParent()); + deleteDialog->setWindowTitle(dialogTitle); + deleteDialog->setText(tr("Delete this annotation?")); + deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + deleteDialog->setDefaultButton(QMessageBox::Yes); + + int result = deleteDialog->exec(); + if (deleteDialog) deleteDialog->deleteLater(); + + if (result == QMessageBox::Yes) { + m_modelNode.removeCustomId(); + m_modelNode.removeAnnotation(); + update(); + } +} + +void FormEditorAnnotationIcon::annotationDialogAccepted() +{ + if (m_annotationEditor) { + QString customId = m_annotationEditor->customId(); + m_customId = customId; + m_modelNode.setCustomId(customId); + + Annotation annotation = m_annotationEditor->annotation(); + + if (annotation.comments().isEmpty()) + m_modelNode.removeAnnotation(); + else + m_modelNode.setAnnotation(annotation); + + m_annotation = annotation; + + m_annotationEditor->close(); + m_annotationEditor->deleteLater(); + } + + m_annotationEditor = nullptr; + + if (m_readerIsActive) + resetReader(); +} + +void FormEditorAnnotationIcon::annotationDialogRejected() +{ + if (m_annotationEditor) { + m_annotationEditor->close(); + m_annotationEditor->deleteLater(); + } + + m_annotationEditor = nullptr; +} + +} //QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.h b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.h new file mode 100644 index 00000000000..c11ea5a3b77 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +QT_END_NAMESPACE + + +namespace QmlDesigner { + +class AnnotationEditorDialog; + +class FormEditorAnnotationIcon : public QGraphicsObject +{ + Q_OBJECT + +public: + explicit FormEditorAnnotationIcon(const ModelNode &modelNode, QGraphicsItem *parent = nullptr); + ~FormEditorAnnotationIcon() override; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + QRectF boundingRect() const override; + + qreal iconWidth(); + qreal iconHeight(); + + bool isReaderActive(); + void setActive(bool readerStatus); + + void resetReader(); + +protected: + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + void drawReader(); + void hideReader(); + QGraphicsItem *createCommentBubble(const QRectF &rect, const QString &title, + const QString &author, const QString &text, + const QString &date, QGraphicsItem *parent); + QGraphicsItem *createTitleBubble(const QRectF &rect, const QString &text, QGraphicsItem *parent); + + void createAnnotationEditor(); + void removeAnnotationDialog(); + + void annotationDialogAccepted(); + void annotationDialogRejected(); + +private: + ModelNode m_modelNode; + bool m_readerIsActive; + QString m_customId; + Annotation m_annotation; + AnnotationEditorDialog *m_annotationEditor; + + QString m_normalIconStr; + QString m_activeIconStr; + + qreal m_iconW; + qreal m_iconH; +}; + +} //QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorscene.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorscene.cpp index ea38ad13682..3f2e83bf1ac 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorscene.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorscene.cpp @@ -49,9 +49,10 @@ namespace QmlDesigner { FormEditorScene::FormEditorScene(FormEditorWidget *view, FormEditorView *editorView) - : QGraphicsScene(), - m_editorView(editorView), - m_showBoundingRects(false) + : QGraphicsScene() + , m_editorView(editorView) + , m_showBoundingRects(false) + , m_annotationVisibility(false) { setupScene(); view->setScene(this); @@ -431,5 +432,15 @@ bool FormEditorScene::showBoundingRects() const return m_showBoundingRects; } +bool FormEditorScene::annotationVisibility() const +{ + return m_annotationVisibility; +} + +void FormEditorScene::setAnnotationVisibility(bool status) +{ + m_annotationVisibility = status; +} + } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorscene.h b/src/plugins/qmldesigner/components/formeditor/formeditorscene.h index 9dba7d23cb3..5c4f0875755 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorscene.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorscene.h @@ -95,6 +95,9 @@ public: void setShowBoundingRects(bool show); bool showBoundingRects() const; + bool annotationVisibility() const; + void setAnnotationVisibility(bool status); + protected: bool event(QEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent * event) override; @@ -129,6 +132,7 @@ private: QPointer m_manipulatorLayerItem; ModelNode m_dragNode; bool m_showBoundingRects; + bool m_annotationVisibility; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index c6e0516e139..44e514641a5 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -501,6 +501,7 @@ void FormEditorView::changeCurrentToolTo(AbstractFormEditorTool *newTool) m_currentTool->clear(); m_currentTool->setItems(scene()->itemsForQmlItemNodes(toQmlItemNodeList( selectedModelNodes()))); + m_currentTool->start(); } @@ -530,6 +531,10 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN FormEditorItem *editorItem = m_scene->itemForQmlItemNode(item); if (editorItem) editorItem->update(); + } else if (name == "annotation" || name == "customId") { + if (FormEditorItem *editorItem = scene()->itemForQmlItemNode(item)) { + editorItem->update(); + } } } diff --git a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp index 61dbe75ea32..f4af3f481f7 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp @@ -26,6 +26,8 @@ #include "selectionindicator.h" #include +#include "annotationeditor/annotation.h" +#include #include #include @@ -42,6 +44,7 @@ namespace QmlDesigner { SelectionIndicator::SelectionIndicator(LayerItem *layerItem) : m_layerItem(layerItem) + , m_annotationItem(nullptr) { } @@ -75,6 +78,7 @@ void SelectionIndicator::clear() } } m_labelItem.reset(nullptr); + m_annotationItem = nullptr; m_indicatorShapeHash.clear(); } @@ -119,6 +123,7 @@ void SelectionIndicator::setItems(const QList &itemList) if (checkSingleSelection(itemList)) { FormEditorItem *selectedItem = itemList.constFirst(); m_labelItem = std::make_unique(m_layerItem.data()); + const qreal scaleFactor = m_layerItem->viewportTransform().m11(); QGraphicsWidget *toolbar = DesignerActionManager::instance().createFormEditorToolBar(m_labelItem.get()); toolbar->setPos(1, -1); @@ -129,6 +134,14 @@ void SelectionIndicator::setItems(const QList &itemList) if (modelNode.hasId()) textItem->setPlainText(modelNode.id()); + if (modelNode.hasAnnotation() || modelNode.hasCustomId()) { + m_annotationItem = new FormEditorAnnotationIcon(modelNode, m_labelItem.get()); + m_annotationItem->update(); + } + else { + m_annotationItem = nullptr; + } + static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor); textItem->setDefaultTextColor(textColor); @@ -139,18 +152,25 @@ void SelectionIndicator::setItems(const QList &itemList) QPointF pos = labelRect.topLeft(); labelRect.moveTo(0, 0); m_labelItem->setPolygon(labelRect); - const int scaledHeight = labelHeight / m_layerItem->viewportTransform().m11(); + const int scaledHeight = labelHeight / scaleFactor; m_labelItem->setPos(pos + QPointF(0, -scaledHeight)); const int offset = (labelHeight - textItem->boundingRect().height()) / 2; textItem->setPos(QPointF(toolbar->size().width(), offset)); m_labelItem->setFlag(QGraphicsItem::ItemIsSelectable, false); m_labelItem->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); + QPen pen; pen.setCosmetic(true); pen.setWidth(2); pen.setCapStyle(Qt::RoundCap); pen.setJoinStyle(Qt::BevelJoin); pen.setColor(selectionColor); + + if (m_annotationItem) { + m_annotationItem->setFlags(QGraphicsItem::ItemIgnoresTransformations); + adjustAnnotationPosition(labelPolygon.boundingRect(), m_labelItem->boundingRect(), scaleFactor); + } + m_labelItem->setPen(pen); m_labelItem->setBrush(selectionColor); m_labelItem->update(); @@ -172,8 +192,14 @@ void SelectionIndicator::updateItems(const QList &itemList) QPolygonF labelPolygon = boundingRectInLayerItemSpaceForItem(selectedItem, m_layerItem.data()); QRectF labelRect = labelPolygon.boundingRect(); QPointF pos = labelRect.topLeft(); - const int scaledHeight = labelHeight / m_layerItem->viewportTransform().m11(); + const qreal scaleFactor = m_layerItem->viewportTransform().m11(); + const int scaledHeight = labelHeight / scaleFactor; m_labelItem->setPos(pos + QPointF(0, -scaledHeight)); + + if (m_annotationItem) { + adjustAnnotationPosition(labelPolygon.boundingRect(), m_labelItem->boundingRect(), scaleFactor); + } + m_layerItem->update(); } } @@ -186,5 +212,24 @@ void SelectionIndicator::setCursor(const QCursor &cursor) item->setCursor(cursor); } +void SelectionIndicator::adjustAnnotationPosition(const QRectF &itemRect, const QRectF &labelRect, qreal scaleFactor) +{ + if (!m_annotationItem) return; + + const qreal iconW = 40 * 0.5; //*0.5 for a shift of an icon outide the item + qreal iconX = 0.0; + qreal iconY = -15.0/scaleFactor; //small offset + + if (((labelRect.width() + iconW)/scaleFactor) > itemRect.width()) + iconY -= labelRect.height()/scaleFactor; + + if ((iconW/scaleFactor) > itemRect.width()) + iconX = 0.0; + else + iconX = (itemRect.width()) - (iconW/scaleFactor); + + m_annotationItem->setPos(iconX*scaleFactor, iconY*scaleFactor); +} + } diff --git a/src/plugins/qmldesigner/components/formeditor/selectionindicator.h b/src/plugins/qmldesigner/components/formeditor/selectionindicator.h index 7da3737a0f3..252020e9904 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectionindicator.h +++ b/src/plugins/qmldesigner/components/formeditor/selectionindicator.h @@ -35,6 +35,8 @@ namespace QmlDesigner { +class FormEditorAnnotationIcon; + class SelectionIndicator { public: @@ -50,12 +52,16 @@ public: void updateItems(const QList &itemList); void setCursor(const QCursor &cursor); +private: + void adjustAnnotationPosition(const QRectF &itemRect, const QRectF &labelRect, qreal scaleFactor); private: QHash m_indicatorShapeHash; + FormEditorItem *m_selectedItem; QPointer m_layerItem; QCursor m_cursor; std::unique_ptr m_labelItem; + FormEditorAnnotationIcon *m_annotationItem; //handled by m_labelItem }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 5cb3766dadc..7497879ecb5 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -159,6 +159,8 @@ QVariant properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, return 0; else if (propertyName == "break") return 50; + else if (propertyName == "customId") + return QString(); return {}; } @@ -221,6 +223,8 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml PropertyNameList propertyNames; + propertyNames.append("customId"); + if (itemNode.isFlowTransition()) { propertyNames.append({"color", "width", "inOffset", "outOffset", "dash", "break"}); } else if (itemNode.isFlowItem()) { diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 131ef0fe8a2..7f705e43c6a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -34,6 +34,7 @@ #include "simplecolorpalettemodel.h" #include "bindingeditor/bindingeditor.h" #include "bindingeditor/actioneditor.h" +#include "annotationeditor/annotationeditor.h" #include "qmlanchorbindingproxy.h" #include "theme.h" #include "aligndistribute.h" @@ -63,6 +64,7 @@ void Quick2PropertyEditorView::registerQmlTypes() Internal::QmlAnchorBindingProxy::registerDeclarativeType(); BindingEditor::registerDeclarativeType(); ActionEditor::registerDeclarativeType(); + AnnotationEditor::registerDeclarativeType(); AlignDistribute::registerDeclarativeType(); Tooltip::registerDeclarativeType(); } diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 253250af241..ac65de0240f 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -28,6 +28,7 @@ #include "qmldesignercorelib_global.h" #include #include +#include #include QT_BEGIN_NAMESPACE @@ -56,6 +57,8 @@ class NodeListProperty; class NodeProperty; class NodeAbstractProperty; class ModelNode; +class Comment; +class Annotation; QMLDESIGNERCORE_EXPORT QList toInternalNodeList(const QList &nodeList); @@ -183,6 +186,22 @@ public: bool hasAuxiliaryData(const PropertyName &name) const; QHash auxiliaryData() const; + QString customId() const; + bool hasCustomId() const; + void setCustomId(const QString &str); + void removeCustomId(); + + QVector comments() const; + bool hasComments() const; + void setComments(const QVector &coms); + void addComment(const Comment &com); + bool updateComment(const Comment &com, int position); + + Annotation annotation() const; + bool hasAnnotation() const; + void setAnnotation(const Annotation &annotation); + void removeAnnotation(); + qint32 internalId() const; void setNodeSource(const QString&); diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index 17c5a608104..7a77044e13c 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -41,6 +41,7 @@ #include "nodelistproperty.h" #include "nodeproperty.h" #include +#include "annotationeditor/annotation.h" #include @@ -1052,6 +1053,100 @@ QHash ModelNode::auxiliaryData() const return internalNode()->auxiliaryData(); } +QString ModelNode::customId() const +{ + QString result; + if (hasCustomId()) + result = auxiliaryData(customIdProperty).value(); + + return result; +} + +bool ModelNode::hasCustomId() const +{ + return hasAuxiliaryData(customIdProperty); +} + +void ModelNode::setCustomId(const QString &str) +{ + setAuxiliaryData(customIdProperty, QVariant::fromValue(str)); +} + +void ModelNode::removeCustomId() +{ + if (hasCustomId()) { + removeAuxiliaryData(customIdProperty); + } +} + +QVector ModelNode::comments() const +{ + return annotation().comments(); +} + +bool ModelNode::hasComments() const +{ + return annotation().hasComments(); +} + +void ModelNode::setComments(const QVector &coms) +{ + Annotation anno = annotation(); + anno.setComments(coms); + + setAnnotation(anno); +} + +void ModelNode::addComment(const Comment &com) +{ + Annotation anno = annotation(); + anno.addComment(com); + + setAnnotation(anno); +} + +bool ModelNode::updateComment(const Comment &com, int position) +{ + bool result = false; + if (hasAnnotation()) { + Annotation anno = annotation(); + + if (anno.updateComment(com, position)) { + setAnnotation(anno); + result = true; + } + } + + return result; +} + +Annotation ModelNode::annotation() const +{ + Annotation result; + + if (hasAnnotation()) + result.fromQString(auxiliaryData(annotationProperty).value()); + + return result; +} + +bool ModelNode::hasAnnotation() const +{ + return hasAuxiliaryData(annotationProperty); +} + +void ModelNode::setAnnotation(const Annotation &annotation) +{ + setAuxiliaryData(annotationProperty, QVariant::fromValue(annotation.toQString())); +} + +void ModelNode::removeAnnotation() +{ + if (hasAnnotation()) { + removeAuxiliaryData(annotationProperty); + } +} + void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList) { model()->d->setScriptFunctions(internalNode(), scriptFunctionList); diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index 8f8a3946023..8dad77ad44c 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -530,6 +530,14 @@ QString RewriterView::auxiliaryDataAsQML() const if (metaType == QMetaType::QString || metaType == QMetaType::QColor) { + + strValue.replace(QStringLiteral("\\"), QStringLiteral("\\\\")); + strValue.replace(QStringLiteral("\""), QStringLiteral("\\\"")); + strValue.replace(QStringLiteral("\t"), QStringLiteral("\\t")); + strValue.replace(QStringLiteral("\r"), QStringLiteral("\\r")); + strValue.replace(QStringLiteral("\n"), QStringLiteral("\\n")); + strValue.replace(QStringLiteral("*/"), QStringLiteral("*\\/")); + strValue = "\"" + strValue + "\""; } diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index 715e64a3e91..c1249f3dbdc 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -238,6 +239,7 @@ bool QmlDesignerPlugin::delayedInitialize() d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::SourceTool); d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::ColorTool); + d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::AnnotationTool); d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::TextTool); d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::PathTool); diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index 625770cb723..30c34209377 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -29,6 +29,7 @@ include(components/timelineeditor/timelineeditor.pri) include(components/connectioneditor/connectioneditor.pri) include(components/curveeditor/curveeditor.pri) include(components/bindingeditor/bindingeditor.pri) +include(components/annotationeditor/annotationeditor.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index d997b63425f..b6939808f2a 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -461,6 +461,8 @@ Project { "formeditor/dragtool.cpp", "formeditor/dragtool.h", "formeditor/formeditor.qrc", + "formeditor/formeditorannotationicon.cpp", + "formeditor/formeditorannotationicon.h", "formeditor/formeditorgraphicsview.cpp", "formeditor/formeditorgraphicsview.h", "formeditor/formeditoritem.cpp", @@ -636,6 +638,18 @@ Project { name: "extension" prefix: "components/" files: [ + "annotationeditor/annotation.cpp", + "annotationeditor/annotation.h", + "annotationeditor/annotationcommenttab.cpp", + "annotationeditor/annotationcommenttab.h", + "annotationeditor/annotationcommenttab.ui", + "annotationeditor/annotationeditor.cpp", + "annotationeditor/annotationeditor.h", + "annotationeditor/annotationeditordialog.cpp", + "annotationeditor/annotationeditordialog.h", + "annotationeditor/annotationeditordialog.ui + "annotationeditor/annotationtool.cpp", + "annotationeditor/annotationtool.h", "bindingeditor/bindingeditor.cpp", "bindingeditor/bindingeditor.h", "bindingeditor/actioneditor.cpp",