Refactor AnnotationEditors to support different views

Change-Id: I67797e911c320d77b8d6a2eba75de69546b30546
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Michael Winkelmann
2021-03-09 12:07:05 +01:00
parent 3f265c37c6
commit bd9a61fa27
22 changed files with 1395 additions and 399 deletions

View File

@@ -637,6 +637,10 @@ extend_qtc_plugin(QmlDesigner
globalannotationeditordialog.cpp globalannotationeditordialog.h globalannotationeditordialog.ui
annotationeditor.cpp annotationeditor.h
globalannotationeditor.cpp globalannotationeditor.h
defaultannotations.cpp defaultannotations.h
annotationtableview.cpp annotationtableview.h
annotationtabwidget.cpp annotationtabwidget.h
annotationeditor.qrc
)
extend_qtc_plugin(QmlDesigner

View File

@@ -24,12 +24,12 @@
****************************************************************************/
#include "annotationcommenttab.h"
#include "defaultannotations.h"
#include "ui_annotationcommenttab.h"
#include "richtexteditor/richtexteditor.h"
#include <QCryptographicHash>
#include "QStringListModel"
#include "projectexplorer/session.h"
#include "projectexplorer/target.h"
@@ -40,7 +40,7 @@ namespace QmlDesigner {
AnnotationCommentTab::AnnotationCommentTab(QWidget *parent)
: QWidget(parent)
, ui(new Ui::AnnotationCommentTab)
, ui(std::make_unique<Ui::AnnotationCommentTab>())
{
ui->setupUi(this);
@@ -57,33 +57,12 @@ AnnotationCommentTab::AnnotationCommentTab(QWidget *parent)
ui->formLayout->setWidget(3, QFormLayout::FieldRole, m_editor);
ui->titleEdit->setModel(new QStringListModel{QStringList{"Description",
"Display Condition",
"helper lines",
"position marker",
"highlight",
"project author",
"project confirmed",
"project developer",
"project distributor",
"project modified",
"project type",
"project version",
"Screen Description",
"Section",
"normalcolor",
"focuscolor",
"selectedcolor",
"pressedcolor"}});
connect(ui->titleEdit, &QComboBox::currentTextChanged,
this, &AnnotationCommentTab::commentTitleChanged);
connect(ui->titleEdit, &QComboBox::currentTextChanged, this, [this](QString const &text) {
emit titleChanged(text, this);
});
}
AnnotationCommentTab::~AnnotationCommentTab()
{
delete ui;
}
AnnotationCommentTab::~AnnotationCommentTab() {}
Comment AnnotationCommentTab::currentComment() const
{
@@ -91,6 +70,9 @@ Comment AnnotationCommentTab::currentComment() const
result.setTitle(ui->titleEdit->currentText().trimmed());
result.setAuthor(ui->authorEdit->text().trimmed());
if (defaultAnnotations() && !defaultAnnotations()->isRichText(result)) {
result.setText(m_editor->plainText().trimmed());
} else
result.setText(m_editor->richText().trimmed());
if (m_comment.sameContent(result))
@@ -129,9 +111,15 @@ void AnnotationCommentTab::resetComment()
m_comment = currentComment();
}
void AnnotationCommentTab::commentTitleChanged(const QString &text)
DefaultAnnotationsModel *AnnotationCommentTab::defaultAnnotations() const
{
emit titleChanged(text, this);
return m_defaults;
}
void AnnotationCommentTab::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
ui->titleEdit->setModel(m_defaults);
}
QString AnnotationCommentTab::backupFile(const QString &filePath)
@@ -153,10 +141,8 @@ QString AnnotationCommentTab::backupFile(const QString &filePath)
if (!newFile.exists()) {
QFile(oldFile.absoluteFilePath()).copy(newFile.absoluteFilePath());
break;
} else if (compareFileChecksum(oldFile.absoluteFilePath(),
newFile.absoluteFilePath()) == 0) {
} else if (compareFileChecksum(oldFile.absoluteFilePath(), newFile.absoluteFilePath()) == 0)
break;
}
newFile.setFile(imgDir, newName.arg(i));
}
@@ -166,9 +152,8 @@ QString AnnotationCommentTab::backupFile(const QString &filePath)
void AnnotationCommentTab::ensureDir(const QDir &dir)
{
if (!dir.exists()) {
if (!dir.exists())
dir.mkdir(".");
}
}
int AnnotationCommentTab::compareFileChecksum(const QString &firstFile, const QString &secondFile)

View File

@@ -25,10 +25,13 @@
#pragma once
#include <QWidget>
#include "annotation.h"
#include <QWidget>
#include <QPointer>
#include <memory>
QT_BEGIN_NAMESPACE
class QDir;
QT_END_NAMESPACE
@@ -40,6 +43,7 @@ class AnnotationCommentTab;
}
class RichTextEditor;
class DefaultAnnotationsModel;
class AnnotationCommentTab : public QWidget
{
@@ -57,17 +61,18 @@ public:
void resetUI();
void resetComment();
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
signals:
void titleChanged(const QString &text, QWidget *widget);
private slots:
void commentTitleChanged(const QString &text);
private:
Ui::AnnotationCommentTab *ui;
std::unique_ptr<Ui::AnnotationCommentTab> ui;
RichTextEditor *m_editor;
Comment m_comment;
QPointer<DefaultAnnotationsModel> m_defaults;
QString backupFile(const QString &filePath);
void ensureDir(const QDir &dir);

View File

@@ -61,8 +61,8 @@ void AnnotationEditor::showWidget()
{
m_dialog = new AnnotationEditorDialog(Core::ICore::dialogParent(),
m_modelNode.id(),
m_modelNode.customId(),
m_modelNode.annotation());
m_modelNode.customId());
m_dialog->setAnnotation(m_modelNode.annotation());
QObject::connect(m_dialog, &AnnotationEditorDialog::acceptedDialog,
this, &AnnotationEditor::acceptedClicked);

View File

@@ -3,13 +3,21 @@ HEADERS += $$PWD/annotationeditordialog.h
HEADERS += $$PWD/annotationeditor.h
HEADERS += $$PWD/globalannotationeditor.h
HEADERS += $$PWD/globalannotationeditordialog.h
HEADERS += $$PWD/defaultannotations.h
HEADERS += $$PWD/annotationtableview.h
HEADERS += $$PWD/annotationtabwidget.h
SOURCES += $$PWD/annotationcommenttab.cpp
SOURCES += $$PWD/annotationeditordialog.cpp
SOURCES += $$PWD/annotationeditor.cpp
SOURCES += $$PWD/globalannotationeditor.cpp
SOURCES += $$PWD/globalannotationeditordialog.cpp
SOURCES += $$PWD/defaultannotations.cpp
SOURCES += $$PWD/annotationtableview.cpp
SOURCES += $$PWD/annotationtabwidget.cpp
FORMS += $$PWD/annotationcommenttab.ui
FORMS += $$PWD/annotationeditordialog.ui
FORMS += $$PWD/globalannotationeditordialog.ui
RESOURCES += $$PWD/annotationeditor.qrc

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/annotationeditor">
<file>defaultannotations.json</file>
</qresource>
</RCC>

View File

@@ -24,80 +24,65 @@
****************************************************************************/
#include "annotationeditordialog.h"
#include "ui_annotationeditordialog.h"
#include "annotation.h"
#include "annotationcommenttab.h"
#include "defaultannotations.h"
#include "ui_annotationcommenttab.h"
#include "ui_annotationeditordialog.h"
#include <timelineicons.h>
#include <utils/qtcassert.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
#include <QObject>
#include <QToolBar>
namespace QmlDesigner {
AnnotationEditorDialog::AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation)
BasicAnnotationEditorDialog::BasicAnnotationEditorDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::AnnotationEditorDialog)
, m_customId(customId)
, m_annotation(annotation)
, m_defaults(std::make_unique<DefaultAnnotationsModel>())
{
ui->setupUi(this);
setWindowFlag(Qt::Tool, true);
setModal(true);
loadDefaultAnnotations(DefaultAnnotationsModel::defaultJsonFilePath());
connect(this, &QDialog::accepted, this, &AnnotationEditorDialog::acceptedClicked);
connect(this, &QDialog::accepted, this, &BasicAnnotationEditorDialog::acceptedClicked);
}
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AnnotationEditorDialog::tabChanged);
BasicAnnotationEditorDialog::~BasicAnnotationEditorDialog() {}
auto *commentCornerWidget = new QToolBar;
Annotation const &BasicAnnotationEditorDialog::annotation() const
{
return m_annotation;
}
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?
void BasicAnnotationEditorDialog::setAnnotation(const Annotation &annotation)
{
m_annotation = annotation;
fillFields();
}
connect(commentAddAction, &QAction::triggered, this, [this]() {
addComment(Comment());
});
void BasicAnnotationEditorDialog::loadDefaultAnnotations(QString const &filename)
{
m_defaults->loadFromFile(filename);
}
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
DefaultAnnotationsModel *BasicAnnotationEditorDialog::defaultAnnotations() const
{
return m_defaults.get();
}
if (ui->tabWidget->count() == 0) { //it is not even supposed to happen but lets be sure
QTC_ASSERT(true, return);
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);
AnnotationEditorDialog::AnnotationEditorDialog(QWidget *parent,
const QString &targetId,
const QString &customId)
: BasicAnnotationEditorDialog(parent)
, ui(new Ui::AnnotationEditorDialog)
, m_customId(customId)
{
ui->setupUi(this);
ui->targetIdEdit->setText(targetId);
fillFields();
setWindowTitle(annotationEditorTitle);
}
@@ -106,17 +91,6 @@ 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;
@@ -130,117 +104,34 @@ QString AnnotationEditorDialog::customId() const
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<AnnotationCommentTab*>(ui->tabWidget->widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
annotation.addComment(comment);
}
m_annotation = annotation;
updateAnnotation();
emit AnnotationEditorDialog::acceptedDialog();
}
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();
ui->tabWidget->setupComments(m_annotation.comments());
}
void AnnotationEditorDialog::setupComments()
void AnnotationEditorDialog::updateAnnotation()
{
ui->tabWidget->setUpdatesEnabled(false);
deleteAllTabs();
const QVector<Comment> comments = m_annotation.comments();
if (comments.isEmpty())
addComment(Comment());
for (const Comment &comment : comments) {
addCommentTab(comment);
}
ui->tabWidget->setUpdatesEnabled(true);
m_customId = ui->customIdEdit->text();
Annotation annotation;
annotation.setComments(ui->tabWidget->fetchComments());
m_annotation = annotation;
}
void AnnotationEditorDialog::addComment(const Comment &comment)
{
m_annotation.addComment(comment);
addCommentTab(comment);
ui->tabWidget->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);
QString tabTitle(comment.title());
int tabIndex = ui->tabWidget->addTab(commentTab, tabTitle);
ui->tabWidget->setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex+1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
ui->tabWidget->setTabText(tabIndex, tabTitle);
}
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)
{
(void) index;
}
} //namespace QmlDesigner

View File

@@ -34,46 +34,62 @@ namespace QmlDesigner {
namespace Ui {
class AnnotationEditorDialog;
}
class DefaultAnnotationsModel;
class AnnotationEditorDialog : public QDialog
class BasicAnnotationEditorDialog : public QDialog
{
Q_OBJECT
public:
explicit AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation);
~AnnotationEditorDialog();
explicit BasicAnnotationEditorDialog(QWidget *parent);
~BasicAnnotationEditorDialog();
Annotation const &annotation() const;
void setAnnotation(const Annotation &annotation);
Annotation annotation() const;
void setCustomId(const QString &customId);
QString customId() const;
void loadDefaultAnnotations(QString const &filename);
DefaultAnnotationsModel *defaultAnnotations() const;
signals:
void acceptedDialog(); //use instead of QDialog::accepted
protected:
virtual void fillFields() = 0;
virtual void acceptedClicked() = 0;
Annotation m_annotation;
std::unique_ptr<DefaultAnnotationsModel> m_defaults;
};
class AnnotationEditorDialog : public BasicAnnotationEditorDialog
{
Q_OBJECT
public:
explicit AnnotationEditorDialog(QWidget *parent,
const QString &targetId,
const QString &customId);
~AnnotationEditorDialog();
void setCustomId(const QString &customId);
QString customId() const;
private slots:
void acceptedClicked();
void tabChanged(int index);
void commentTitleChanged(const QString &text, QWidget *tab);
void acceptedClicked() override;
private:
void fillFields();
void setupComments();
void fillFields() override;
void updateAnnotation();
void addComment(const Comment &comment);
void removeComment(int index);
void addCommentTab(const Comment &comment);
void removeCommentTab(int index);
void deleteAllTabs();
private:
const QString annotationEditorTitle = {tr("Annotation Editor")};
const QString defaultTabName = {tr("Annotation")};
Ui::AnnotationEditorDialog *ui;
QString m_customId;
Annotation m_annotation;
};
} //namespace QmlDesigner

View File

@@ -56,7 +56,7 @@
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="AnnotationTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
@@ -90,6 +90,14 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AnnotationTabWidget</class>
<extends>QTabWidget</extends>
<header>annotationtabwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>targetIdEdit</tabstop>
<tabstop>customIdEdit</tabstop>

View File

@@ -0,0 +1,446 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "annotationtableview.h"
#include "defaultannotations.h"
#include <utils/qtcolorbutton.h>
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QDoubleSpinBox>
#include <QHeaderView>
#include <QItemEditorFactory>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QPushButton>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QStringListModel>
#include <QStyle>
#include <QTextEdit>
namespace QmlDesigner {
struct ColumnId
{
enum Column {
Title = 0,
Author = 1,
Value = 2,
};
};
CommentDelegate::CommentDelegate(QObject *parent)
: QItemDelegate(parent)
, m_completer(std::make_unique<QCompleter>())
{}
CommentDelegate::~CommentDelegate() {}
DefaultAnnotationsModel *CommentDelegate::defaultAnnotations() const
{
return m_defaults;
}
void CommentDelegate::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
m_completer->setModel(m_defaults);
}
QCompleter *CommentDelegate::completer() const
{
return m_completer.get();
}
void CommentDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
Comment CommentDelegate::comment(QModelIndex const &index)
{
auto *model = index.model();
return model->data(model->index(index.row(), ColumnId::Title), CommentRole).value<Comment>();
}
CommentTitleDelegate::CommentTitleDelegate(QObject *parent)
: CommentDelegate(parent)
{}
CommentTitleDelegate::~CommentTitleDelegate() {}
QWidget *CommentTitleDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
auto *editor = new QComboBox(parent);
editor->setEditable(true);
editor->setCompleter(completer());
editor->setFrame(false);
editor->setFocusPolicy(Qt::StrongFocus);
return editor;
}
void CommentTitleDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data(index, Qt::DisplayRole).toString();
auto *comboBox = qobject_cast<QComboBox *>(editor);
comboBox->setModel(defaultAnnotations());
comboBox->setCurrentText(text);
}
void CommentTitleDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
auto *comboBox = qobject_cast<QComboBox *>(editor);
auto oldText = model->data(index, Qt::EditRole).toString();
auto newText = comboBox->currentText();
if (oldText != newText) {
model->setData(index, comboBox->currentText(), Qt::EditRole);
auto comment = model->data(index, CommentRole).value<Comment>();
comment.setTitle(newText);
model->setData(index, QVariant::fromValue(comment), CommentRole);
// Set default value to data item
auto colIdx = model->index(index.row(), ColumnId::Value);
if (defaultAnnotations()->hasDefault(comment))
model->setData(colIdx, defaultAnnotations()->defaultValue(comment), Qt::DisplayRole);
else
// Reset to rich text when there is no default item
model->setData(colIdx,
QVariant::fromValue<RichTextProxy>({comment.text()}),
Qt::DisplayRole);
}
}
CommentValueDelegate::CommentValueDelegate(QObject *parent)
: CommentDelegate(parent)
{}
CommentValueDelegate::~CommentValueDelegate() {}
void CommentValueDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
auto data = index.model()->data(index, Qt::DisplayRole);
if (data.userType() == qMetaTypeId<RichTextProxy>())
drawDisplay(painter, option, option.rect, data.value<RichTextProxy>().plainText());
else if (data.userType() == QMetaType::QColor)
painter->fillRect(option.rect, data.value<QColor>());
else
QItemDelegate::paint(painter, option, index);
}
void CommentValueDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto data = index.model()->data(index, Qt::DisplayRole);
if (data.userType() == qMetaTypeId<RichTextProxy>()) {
auto richText = data.value<RichTextProxy>();
auto *e = qobject_cast<RichTextCellEditor *>(editor);
e->setText(richText.plainText());
e->setupSignal(index.row(), comment(index).title());
connect(e,
&RichTextCellEditor::richTextClicked,
this,
&CommentValueDelegate::richTextEditorRequested,
Qt::UniqueConnection);
} else if (data.userType() == QMetaType::QString) {
auto *e = qobject_cast<QLineEdit *>(editor);
e->setText(data.toString());
} else if (data.userType() == QMetaType::QColor) {
auto *e = qobject_cast<Utils::QtColorButton *>(editor);
e->setColor(data.value<QColor>());
} else
QItemDelegate::setEditorData(editor, index);
}
void CommentValueDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
auto data = model->data(index, Qt::EditRole);
if (data.userType() == qMetaTypeId<RichTextProxy>())
return;
else if (data.userType() == QMetaType::QColor)
model->setData(index,
qobject_cast<Utils::QtColorButton *>(editor)->color(),
Qt::DisplayRole);
else if (data.userType() == QMetaType::QString)
model->setData(index, qobject_cast<QLineEdit *>(editor)->text(), Qt::DisplayRole);
else
QItemDelegate::setModelData(editor, model, index);
}
RichTextCellEditor::RichTextCellEditor(QWidget *parent)
: QLabel(parent)
{}
RichTextCellEditor::~RichTextCellEditor() {}
RichTextProxy RichTextCellEditor::richText() const
{
return m_richText;
}
void RichTextCellEditor::setRichText(const RichTextProxy &richText)
{
if (richText.text == m_richText.text)
return;
m_richText = richText;
setText(richText.plainText());
emit richTextChanged();
}
void RichTextCellEditor::setupSignal(int index, const QString &commentTitle)
{
if (m_connection)
disconnect(m_connection);
m_connection = connect(this, &RichTextCellEditor::clicked, this, [=]() {
emit richTextClicked(index, commentTitle);
});
}
void RichTextCellEditor::mouseReleaseEvent(QMouseEvent *)
{
emit clicked();
}
AnnotationTableView::AnnotationTableView(QWidget *parent)
: QTableView(parent)
, m_model(std::make_unique<QStandardItemModel>())
, m_editorFactory(std::make_unique<QItemEditorFactory>())
{
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ContiguousSelection);
setModel(m_model.get());
connect(m_model.get(), &QStandardItemModel::itemChanged, this, [this](QStandardItem *item) {
if (item->isCheckable())
m_model->setData(item->index(), item->checkState() == Qt::Checked);
if (this->m_modelUpdating)
return;
auto *valueItem = m_model->item(item->row(), ColumnId::Value);
// When comment title was edited, make value item editable
if (item->column() == ColumnId::Title && valueItem) {
valueItem->setEditable(!item->text().isEmpty());
valueItem->setCheckable(valueItem->data(Qt::DisplayRole).userType() == QMetaType::Bool);
}
m_modelUpdating = true;
if (!rowIsEmpty(m_model->rowCount() - 1))
addEmptyRow();
m_modelUpdating = false;
});
horizontalHeader()->setStretchLastSection(true);
horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
m_editorFactory->registerEditor(qMetaTypeId<RichTextProxy>(),
new QItemEditorCreator<RichTextCellEditor>("richText"));
m_editorFactory->registerEditor(QMetaType::QColor,
new QItemEditorCreator<Utils::QtColorButton>("color"));
m_valueDelegate.setItemEditorFactory(m_editorFactory.get());
connect(&m_valueDelegate,
&CommentValueDelegate::richTextEditorRequested,
this,
&AnnotationTableView::richTextEditorRequested);
verticalHeader()->hide();
}
AnnotationTableView::~AnnotationTableView() {}
QVector<Comment> AnnotationTableView::fetchComments() const
{
QVector<Comment> comments;
for (int i = 0; i < m_model->rowCount(); ++i) {
Comment comment = fetchComment(i);
if (!comment.isEmpty())
comments.push_back(comment);
}
return comments;
}
Comment AnnotationTableView::fetchComment(int row) const
{
auto *item = m_model->item(row, ColumnId::Title);
if (item->text().isEmpty())
return {};
Comment comment = item->data().value<Comment>();
comment.setTitle(item->text());
comment.setAuthor(m_model->item(row, ColumnId::Author)->text());
comment.setText(dataToCommentText(m_model->item(row, ColumnId::Value)->data(Qt::DisplayRole)));
return comment;
}
void AnnotationTableView::setupComments(QVector<Comment> const &comments)
{
m_model->clear();
m_modelUpdating = true;
m_model->setColumnCount(3);
m_model->setHeaderData(ColumnId::Title, Qt::Horizontal, tr("Title"));
m_model->setHeaderData(ColumnId::Author, Qt::Horizontal, tr("Author"));
m_model->setHeaderData(ColumnId::Value, Qt::Horizontal, tr("Value"));
setItemDelegateForColumn(ColumnId::Title, &m_titleDelegate);
setItemDelegateForColumn(ColumnId::Value, &m_valueDelegate);
for (auto &comment : comments) {
if (comment.isEmpty())
continue;
addEmptyRow();
changeRow(m_model->rowCount() - 1, comment);
}
addEmptyRow();
m_modelUpdating = false;
}
DefaultAnnotationsModel *AnnotationTableView::defaultAnnotations() const
{
return m_defaults;
}
void AnnotationTableView::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
m_titleDelegate.setDefaultAnnotations(defaults);
m_valueDelegate.setDefaultAnnotations(defaults);
}
void AnnotationTableView::changeRow(int index, Comment const &comment)
{
auto *titleItem = m_model->item(index, ColumnId::Title);
auto *authorItem = m_model->item(index, ColumnId::Author);
auto *textItem = m_model->item(index, ColumnId::Value);
titleItem->setText(comment.title());
titleItem->setData(QVariant::fromValue<Comment>(comment));
authorItem->setText(comment.author());
QVariant data = commentToData(comment,
m_defaults ? m_defaults->defaultType(comment)
: QMetaType::UnknownType);
textItem->setEditable(data.isValid());
textItem->setCheckable(data.userType() == QMetaType::Bool);
textItem->setData(data, Qt::DisplayRole);
}
void AnnotationTableView::removeRow(int index)
{
m_model->removeRow(index);
}
void AnnotationTableView::removeSelectedRows()
{
const auto selRows = selectionModel()->selectedRows();
for (auto it = selRows.rbegin(); it != selRows.rend(); ++it)
removeRow(it->row());
}
void AnnotationTableView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete)
removeSelectedRows();
}
void AnnotationTableView::addEmptyRow()
{
auto *valueItem = new QStandardItem;
valueItem->setEditable(false);
m_model->appendRow({new QStandardItem, new QStandardItem, valueItem});
}
bool AnnotationTableView::rowIsEmpty(int row) const
{
auto itemText = [&](int col) {
return m_model->item(row, col) ? m_model->item(row, col)->text() : QString();
};
return QString(itemText(0) + itemText(1) + itemText(2)).isEmpty();
}
QString AnnotationTableView::dataToCommentText(QVariant const &data)
{
auto type = data.userType();
if (type == qMetaTypeId<RichTextProxy>())
return data.value<RichTextProxy>().text;
switch (type) {
case QMetaType::QColor:
return data.value<QColor>().name();
case QMetaType::Bool:
return data.toBool() ? QStringLiteral("true") : QStringLiteral("false");
case QMetaType::QString:
return data.toString();
}
return {};
}
QVariant AnnotationTableView::commentToData(Comment const& comment, QMetaType::Type type)
{
switch (type) {
case QMetaType::Bool:
return QVariant::fromValue(comment.deescapedText().toLower().trimmed() == "true");
case QMetaType::QColor:
return QVariant::fromValue(QColor(comment.deescapedText().toLower().trimmed()));
break;
case QMetaType::QString:
return QVariant::fromValue(comment.text());
break;
default:
if (type == qMetaTypeId<RichTextProxy>() || type == QMetaType::UnknownType)
return QVariant::fromValue<RichTextProxy>({comment.text()});
}
return {};
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,170 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QItemDelegate>
#include <QLabel>
#include <QPointer>
#include <QTableView>
#include <memory>
#include "annotation.h"
#include "defaultannotations.h"
QT_BEGIN_NAMESPACE
class QStandardItemModel;
class QCompleter;
QT_END_NAMESPACE
namespace QmlDesigner {
class CommentDelegate : public QItemDelegate
{
Q_OBJECT
public:
enum Role { CommentRole = Qt::UserRole + 1 };
CommentDelegate(QObject *parent = nullptr);
~CommentDelegate() override;
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
QCompleter *completer() const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
static Comment comment(QModelIndex const &);
private:
std::unique_ptr<QCompleter> m_completer;
QPointer<DefaultAnnotationsModel> m_defaults;
};
class CommentTitleDelegate : public CommentDelegate
{
Q_OBJECT
public:
CommentTitleDelegate(QObject *parent = nullptr);
~CommentTitleDelegate() override;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const override;
signals:
void commentChanged(int row, Comment const &);
};
class CommentValueDelegate : public CommentDelegate
{
Q_OBJECT
public:
CommentValueDelegate(QObject *parent = nullptr);
~CommentValueDelegate();
void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const override;
signals:
void richTextEditorRequested(int index, QString const &richText);
};
class RichTextCellEditor : public QLabel
{
Q_OBJECT
Q_PROPERTY(QmlDesigner::RichTextProxy richText READ richText WRITE setRichText NOTIFY
richTextChanged USER true)
public:
RichTextCellEditor(QWidget *parent = nullptr);
~RichTextCellEditor() override;
RichTextProxy richText() const;
void setRichText(RichTextProxy const &);
void setupSignal(int row, QString const &commentTitle);
signals:
void clicked();
void richTextChanged();
void richTextClicked(int index, QString const &text);
protected:
void mouseReleaseEvent(QMouseEvent *) override;
private:
RichTextProxy m_richText;
QMetaObject::Connection m_connection;
};
class AnnotationTableView : public QTableView
{
Q_OBJECT
public:
AnnotationTableView(QWidget *parent = nullptr);
~AnnotationTableView();
QVector<Comment> fetchComments() const;
Comment fetchComment(int row) const;
void setupComments(QVector<Comment> const &comments);
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
void changeRow(int index, Comment const &comment);
void removeRow(int index);
void removeSelectedRows();
signals:
void richTextEditorRequested(int index, QString const &commentTitle);
protected:
void keyPressEvent(QKeyEvent *) override;
private:
void addEmptyRow();
bool rowIsEmpty(int row) const;
static QString dataToCommentText(QVariant const &);
static QVariant commentToData(Comment const&, QMetaType::Type type);
CommentTitleDelegate m_titleDelegate;
CommentValueDelegate m_valueDelegate;
bool m_modelUpdating = false;
std::unique_ptr<QStandardItemModel> m_model;
std::unique_ptr<QItemEditorFactory> m_editorFactory;
QPointer<DefaultAnnotationsModel> m_defaults;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,154 @@
/****************************************************************************
**
** 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 "annotationtabwidget.h"
#include "annotationcommenttab.h"
#include <timelineeditor/timelineicons.h>
#include <QAction>
#include <QMessageBox>
#include <QToolBar>
namespace QmlDesigner {
AnnotationTabWidget::AnnotationTabWidget(QWidget *parent)
: QTabWidget(parent)
{
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]() { addCommentTab(); });
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
int currentIndex = this->currentIndex();
QString currentTitle = tabText(currentIndex);
if (QMessageBox::question(this,
tr("Global Annotation"),
tr("Do you want to delete this annotation?"))
== QMessageBox::Yes) {
removeTab(currentIndex);
if (count() == 0) //lets be sure that tabWidget is never empty
addCommentTab();
}
});
commentCornerWidget->addAction(commentAddAction);
commentCornerWidget->addAction(commentRemoveAction);
setCornerWidget(commentCornerWidget, Qt::TopRightCorner);
}
AnnotationTabWidget::~AnnotationTabWidget() {}
QVector<Comment> AnnotationTabWidget::fetchComments() const
{
QVector<Comment> comments;
for (int i = 0; i < count(); i++) {
auto *tab = qobject_cast<AnnotationCommentTab *>(widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
comments.push_back(comment);
}
return comments;
}
void AnnotationTabWidget::setupComments(QVector<Comment> const &comments)
{
setUpdatesEnabled(false);
deleteAllTabs();
if (comments.isEmpty())
addCommentTab();
for (const Comment &comment : comments)
addCommentTab(comment);
setUpdatesEnabled(true);
}
DefaultAnnotationsModel *AnnotationTabWidget::defaultAnnotations() const
{
return m_defaults;
}
void AnnotationTabWidget::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
for (int i = 0; i < count(); i++) {
// The tab widget might be contain regular QTabs initially, hence we need this qobject_cast test
if (auto *tab = qobject_cast<AnnotationCommentTab *>(widget(i)))
tab->setDefaultAnnotations(defaults);
}
}
void AnnotationTabWidget::onCommentTitleChanged(const QString &text, QWidget *tab)
{
int tabIndex = indexOf(tab);
if (tabIndex >= 0)
setTabText(tabIndex, text);
if (text.isEmpty())
setTabText(tabIndex, defaultTabName + " " + QString::number(tabIndex + 1));
}
void AnnotationTabWidget::addCommentTab(const Comment &comment)
{
auto *commentTab = new AnnotationCommentTab();
commentTab->setDefaultAnnotations(m_defaults);
commentTab->setComment(comment);
QString tabTitle(comment.title());
int tabIndex = addTab(commentTab, tabTitle);
setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex + 1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
setTabText(tabIndex, tabTitle);
}
connect(commentTab,
&AnnotationCommentTab::titleChanged,
this,
&AnnotationTabWidget::onCommentTitleChanged);
}
void AnnotationTabWidget::deleteAllTabs()
{
while (count() > 0) {
QWidget *w = widget(0);
removeTab(0);
delete w;
}
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QTabWidget>
#include "annotation.h"
#include "defaultannotations.h"
namespace QmlDesigner {
class AnnotationCommentTab;
class AnnotationTabWidget : public QTabWidget
{
Q_OBJECT
public:
AnnotationTabWidget(QWidget *parent = nullptr);
~AnnotationTabWidget();
QVector<Comment> fetchComments() const;
void setupComments(QVector<Comment> const &comments);
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
public slots:
void addCommentTab(const Comment &comment = {});
void deleteAllTabs();
private slots:
void onCommentTitleChanged(const QString &text, QWidget *tab);
private:
const QString defaultTabName = {tr("Annotation")};
QPointer<DefaultAnnotationsModel> m_defaults;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,181 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "defaultannotations.h"
#include <QColor>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
namespace QmlDesigner {
DefaultAnnotationsModel::DefaultAnnotationsModel(QObject *parent)
: QAbstractListModel(parent)
{
qRegisterMetaType<RichTextProxy>();
}
DefaultAnnotationsModel::~DefaultAnnotationsModel() {}
int DefaultAnnotationsModel::rowCount(const QModelIndex &) const
{
return static_cast<int>(m_defaults.size());
}
QVariant DefaultAnnotationsModel::data(const QModelIndex &index, int role) const
{
const auto row = static_cast<size_t>(index.row());
if (!index.isValid() || m_defaults.size() < row)
return {};
auto &item = m_defaults[row];
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
case Name:
return item.first;
case Type:
return item.second.typeName();
case Default:
return item.second;
}
return {};
}
QVariantMap DefaultAnnotationsModel::fetchData() const
{
return m_defaultMap;
}
bool DefaultAnnotationsModel::hasDefault(const Comment &comment) const
{
return m_defaultMap.count(comment.title().toLower());
}
QMetaType::Type DefaultAnnotationsModel::defaultType(const Comment &comment) const
{
return hasDefault(comment) ? QMetaType::Type(m_defaultMap[comment.title().toLower()].userType())
: QMetaType::UnknownType;
}
QVariant DefaultAnnotationsModel::defaultValue(const Comment &comment) const
{
return hasDefault(comment) ? m_defaultMap.value(comment.title().toLower()) : QVariant();
}
bool DefaultAnnotationsModel::isRichText(const Comment &comment) const
{
const auto type = defaultType(comment);
return type == QMetaType::UnknownType || type == qMetaTypeId<RichTextProxy>();
}
void DefaultAnnotationsModel::loadFromFile(QString const &filename)
{
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
loadFromFile(&file);
}
}
void DefaultAnnotationsModel::loadFromFile(QIODevice *io)
{
QJsonParseError error;
auto doc = QJsonDocument::fromJson(io->readAll(), &error);
if (error.error == QJsonParseError::NoError)
loadFromJson(doc);
else {
} // TODO: Error handling
}
void DefaultAnnotationsModel::loadFromJson(const QJsonDocument &doc)
{
beginResetModel();
m_defaultMap = asVariantMapFromJson(doc);
m_defaults.clear();
m_defaults.reserve(m_defaultMap.size());
for (auto &key : m_defaultMap.keys())
m_defaults.emplace_back(key, m_defaultMap.value(key));
endResetModel();
}
QVariantMap DefaultAnnotationsModel::asVariantMapFromJson(const QJsonDocument &doc)
{
QVariantMap map;
QJsonObject obj = doc.object();
for (auto key : obj.keys()) {
key = key.toLower();
auto val = obj[key];
switch (val.type()) {
case QJsonValue::Double:
map[key] = double{0.0};
break;
case QJsonValue::String:
map[key] = QString{};
break;
case QJsonValue::Bool:
map[key] = false;
break;
case QJsonValue::Object: {
auto o = val.toObject();
auto type = o["type"].toString().toLower();
auto val = o["value"].toVariant();
if (type == QStringLiteral("richtext"))
map[key] = QVariant::fromValue(RichTextProxy{val.toString()});
else if (type == QStringLiteral("string"))
map[key] = QVariant::fromValue(val.toString());
else if (type == QStringLiteral("bool"))
map[key] = QVariant::fromValue(val.toBool());
else if (type == QStringLiteral("double"))
map[key] = QVariant::fromValue(val.toDouble());
else if (type == QStringLiteral("color"))
map[key] = QVariant::fromValue(QColor(val.toString()));
}
}
}
return map;
}
QString DefaultAnnotationsModel::defaultJsonFilePath()
{
return QStringLiteral(":/annotationeditor/defaultannotations.json");
}
QString RichTextProxy::plainText() const
{
QString plainText(text);
plainText.remove(QRegularExpression("<.*?>"));
return plainText.mid(plainText.indexOf("}") + 1);
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QAbstractListModel>
QT_BEGIN_NAMESPACE
class QJsonDocument;
QT_END_NAMESPACE
namespace QmlDesigner {
// We need this proxy type to distinguish between a 'normal' QString
// and a 'richtext' string when they are stored in a QVariant
struct RichTextProxy
{
QString text;
QString plainText() const;
};
class DefaultAnnotationsModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Role { Name = Qt::UserRole + 1, Type, Default };
Q_ENUM(Role)
DefaultAnnotationsModel(QObject *parent = nullptr);
~DefaultAnnotationsModel() override;
int rowCount(const QModelIndex & = {}) const override;
QVariant data(const QModelIndex &, int role) const override;
QVariantMap fetchData() const;
bool hasDefault(const Comment &comment) const;
QMetaType::Type defaultType(const Comment &comment) const;
QVariant defaultValue(const Comment &comment) const;
bool isRichText(const Comment &comment) const;
void loadFromFile(QString const &);
void loadFromFile(QIODevice *);
void loadFromJson(const QJsonDocument &);
static QVariantMap asVariantMapFromJson(const QJsonDocument &);
static QString defaultJsonFilePath();
private:
std::vector<std::pair<QString, QVariant>> m_defaults;
QVariantMap m_defaultMap;
};
} // namespace QmlDesigner
Q_DECLARE_METATYPE(QmlDesigner::RichTextProxy);

View File

@@ -0,0 +1,36 @@
{
"description" : "",
"display condition" : "",
"helper lines" : true,
"position marker" : true,
"highlight" : true,
"project author" : "",
"project confirmed" : true,
"project developer" : "",
"project distributor" : "",
"project modified" : "",
"project type" : "",
"project version" : "",
"screen description" : "",
"section" : "",
"normalcolor" : {
"type": "color",
"value": "#000000"
},
"focuscolor" : {
"type": "color",
"value": "#000000"
},
"selectedcolor" : {
"type": "color",
"value": "#000000"
},
"pressedcolor" : {
"type": "color",
"value": "#000000"
},
"overview" : {
"type": "richtext",
"value": ""
}
}

View File

@@ -50,8 +50,8 @@ GlobalAnnotationEditor::~GlobalAnnotationEditor()
void GlobalAnnotationEditor::showWidget()
{
m_dialog = new GlobalAnnotationEditorDialog(Core::ICore::dialogParent(),
modelNode().globalAnnotation(),
modelNode().globalStatus());
m_dialog->setAnnotation(modelNode().globalAnnotation());
QObject::connect(m_dialog, &GlobalAnnotationEditorDialog::acceptedDialog,
this, &GlobalAnnotationEditor::acceptedClicked);
@@ -142,7 +142,6 @@ void GlobalAnnotationEditor::acceptedClicked()
hideWidget();
emit accepted();
emit annotationChanged();
}

View File

@@ -24,87 +24,55 @@
****************************************************************************/
#include "globalannotationeditordialog.h"
#include "ui_globalannotationeditordialog.h"
#include "annotation.h"
#include "annotationcommenttab.h"
#include "ui_annotationcommenttab.h"
#include "ui_globalannotationeditordialog.h"
#include <timelineicons.h>
#include <utils/qtcassert.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
#include <QObject>
#include <QToolBar>
namespace QmlDesigner {
GlobalAnnotationEditorDialog::GlobalAnnotationEditorDialog(QWidget *parent, const Annotation &annotation, GlobalAnnotationStatus status)
: QDialog(parent)
GlobalAnnotationEditorDialog::GlobalAnnotationEditorDialog(QWidget *parent,
GlobalAnnotationStatus status)
: BasicAnnotationEditorDialog(parent)
, ui(new Ui::GlobalAnnotationEditorDialog)
, m_annotation(annotation)
, m_globalStatus(status)
, m_statusIsActive(false)
{
ui->setupUi(this);
ui->tabWidget->setDefaultAnnotations(defaultAnnotations());
ui->tableView->setDefaultAnnotations(defaultAnnotations());
setWindowFlag(Qt::Tool, true);
setModal(true);
connect(this, &QDialog::accepted, this, &GlobalAnnotationEditorDialog::acceptedClicked);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &GlobalAnnotationEditorDialog::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(ui->tableView,
&AnnotationTableView::richTextEditorRequested,
this,
[&](int index, QString const &) {
switchToTabView();
ui->tabWidget->setCurrentIndex(index);
});
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
if (ui->tabWidget->count() == 0) { //it is not even supposed to happen but lets be sure
QTC_ASSERT(false, return);
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);
connect(ui->statusAddButton, &QPushButton::clicked, [&](bool){
connect(ui->statusAddButton, &QPushButton::clicked, this, [&](bool) {
setStatusVisibility(true);
});
setStatus(m_globalStatus);
connect(ui->rbTableView,
&QRadioButton::clicked,
this,
&GlobalAnnotationEditorDialog::switchToTableView);
connect(ui->rbTabView,
&QRadioButton::clicked,
this,
&GlobalAnnotationEditorDialog::switchToTabView);
fillFields();
setStatus(m_globalStatus);
setWindowTitle(globalEditorTitle);
switchToTabView();
}
GlobalAnnotationEditorDialog::~GlobalAnnotationEditorDialog()
@@ -112,26 +80,18 @@ GlobalAnnotationEditorDialog::~GlobalAnnotationEditorDialog()
delete ui;
}
void GlobalAnnotationEditorDialog::setAnnotation(const Annotation &annotation)
GlobalAnnotationEditorDialog::ViewMode GlobalAnnotationEditorDialog::viewMode() const
{
m_annotation = annotation;
fillFields();
}
Annotation GlobalAnnotationEditorDialog::annotation() const
{
return m_annotation;
return ui->rbTableView->isChecked() ? TableView : TabsView;
}
void GlobalAnnotationEditorDialog::setStatus(GlobalAnnotationStatus status)
{
m_globalStatus = status;
bool hasStatus = status.status() != GlobalAnnotationStatus::NoStatus;
bool hasStatus = (status.status() != GlobalAnnotationStatus::NoStatus);
if (hasStatus) {
if (hasStatus)
ui->statusComboBox->setCurrentIndex(int(status.status()));
}
setStatusVisibility(hasStatus);
}
@@ -141,117 +101,73 @@ GlobalAnnotationStatus GlobalAnnotationEditorDialog::globalStatus() const
return m_globalStatus;
}
void GlobalAnnotationEditorDialog::acceptedClicked()
void GlobalAnnotationEditorDialog::showStatusContainer(bool show)
{
Annotation annotation;
annotation.removeComments();
for (int i = 0; i < ui->tabWidget->count(); i++) {
AnnotationCommentTab* tab = reinterpret_cast<AnnotationCommentTab*>(ui->tabWidget->widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
annotation.addComment(comment);
}
m_annotation = annotation;
if (m_statusIsActive) {
m_globalStatus.setStatus(ui->statusComboBox->currentIndex());
}
emit GlobalAnnotationEditorDialog::acceptedDialog();
ui->statusContainer->setVisible(show);
}
void GlobalAnnotationEditorDialog::commentTitleChanged(const QString &text, QWidget *tab)
void GlobalAnnotationEditorDialog::switchToTabView()
{
int tabIndex = ui->tabWidget->indexOf(tab);
if (tabIndex >= 0)
ui->tabWidget->setTabText(tabIndex, text);
m_annotation.setComments(ui->tableView->fetchComments());
ui->rbTabView->setChecked(true);
ui->tableView->hide();
ui->tabWidget->show();
fillFields();
}
if (text.isEmpty())
ui->tabWidget->setTabText(tabIndex,
(defaultTabName + " " + QString::number(tabIndex+1)));
void GlobalAnnotationEditorDialog::switchToTableView()
{
m_annotation.setComments(ui->tabWidget->fetchComments());
ui->rbTableView->setChecked(true);
ui->tabWidget->hide();
ui->tableView->show();
fillFields();
}
void GlobalAnnotationEditorDialog::acceptedClicked()
{
updateAnnotation();
emit GlobalAnnotationEditorDialog::acceptedDialog();
}
void GlobalAnnotationEditorDialog::fillFields()
{
setupComments();
ui->tabWidget->setupComments(m_annotation.comments());
ui->tableView->setupComments(m_annotation.comments());
}
void GlobalAnnotationEditorDialog::setupComments()
void GlobalAnnotationEditorDialog::updateAnnotation()
{
ui->tabWidget->setUpdatesEnabled(false);
deleteAllTabs();
const QVector<Comment> comments = m_annotation.comments();
if (comments.isEmpty())
addComment(Comment());
for (const Comment &comment : comments) {
addCommentTab(comment);
Annotation annotation;
switch (viewMode()) {
case TabsView:
annotation.setComments(ui->tabWidget->fetchComments());
break;
case TableView:
annotation.setComments(ui->tableView->fetchComments());
break;
}
ui->tabWidget->setUpdatesEnabled(true);
m_annotation = annotation;
if (m_statusIsActive)
m_globalStatus.setStatus(ui->statusComboBox->currentIndex());
}
void GlobalAnnotationEditorDialog::addComment(const Comment &comment)
{
m_annotation.addComment(comment);
addCommentTab(comment);
ui->tabWidget->addCommentTab(comment);
}
void GlobalAnnotationEditorDialog::removeComment(int index)
{
if ((m_annotation.commentsSize() > index) && (index >= 0)) {
m_annotation.removeComment(index);
removeCommentTab(index);
}
}
void GlobalAnnotationEditorDialog::addCommentTab(const Comment &comment)
{
auto commentTab = new AnnotationCommentTab();
commentTab->setComment(comment);
QString tabTitle(comment.title());
int tabIndex = ui->tabWidget->addTab(commentTab, tabTitle);
ui->tabWidget->setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex+1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
ui->tabWidget->setTabText(tabIndex, tabTitle);
}
connect(commentTab, &AnnotationCommentTab::titleChanged,
this, &GlobalAnnotationEditorDialog::commentTitleChanged);
}
void GlobalAnnotationEditorDialog::removeCommentTab(int index)
{
if ((ui->tabWidget->count() > index) && (index >= 0)) {
ui->tabWidget->removeTab(index);
}
}
void GlobalAnnotationEditorDialog::deleteAllTabs()
{
while (ui->tabWidget->count() > 0) {
QWidget *w = ui->tabWidget->widget(0);
ui->tabWidget->removeTab(0);
delete w;
}
}
void GlobalAnnotationEditorDialog::setStatusVisibility(bool hasStatus)
{
ui->statusAddButton->setVisible(!hasStatus);
@@ -260,9 +176,4 @@ void GlobalAnnotationEditorDialog::setStatusVisibility(bool hasStatus)
m_statusIsActive = hasStatus;
}
void GlobalAnnotationEditorDialog::tabChanged(int index)
{
(void) index;
}
} //namespace QmlDesigner

View File

@@ -25,9 +25,7 @@
#pragma once
#include <QDialog>
#include "annotation.h"
#include "annotationeditordialog.h"
namespace QmlDesigner {
@@ -35,46 +33,46 @@ namespace Ui {
class GlobalAnnotationEditorDialog;
}
class GlobalAnnotationEditorDialog : public QDialog
class GlobalAnnotationEditorDialog : public BasicAnnotationEditorDialog
{
Q_OBJECT
public:
explicit GlobalAnnotationEditorDialog(QWidget *parent, const Annotation &annotation, GlobalAnnotationStatus status);
enum ViewMode {
TableView,
TabsView
};
explicit GlobalAnnotationEditorDialog(
QWidget *parent = nullptr, GlobalAnnotationStatus status = GlobalAnnotationStatus::NoStatus);
~GlobalAnnotationEditorDialog();
void setAnnotation(const Annotation &annotation);
Annotation annotation() const;
ViewMode viewMode() const;
void setStatus(GlobalAnnotationStatus status);
GlobalAnnotationStatus globalStatus() const;
signals:
void acceptedDialog(); //use instead of QDialog::accepted
public slots:
void showStatusContainer(bool show);
void switchToTabView();
void switchToTableView();
private slots:
void acceptedClicked();
void tabChanged(int index);
void commentTitleChanged(const QString &text, QWidget *tab);
void acceptedClicked() override;
private:
void fillFields();
void setupComments();
void fillFields() override;
void updateAnnotation();
void addComment(const Comment &comment);
void removeComment(int index);
void addCommentTab(const Comment &comment);
void removeCommentTab(int index);
void deleteAllTabs();
void setStatusVisibility(bool hasStatus);
private:
const QString globalEditorTitle = {tr("Global Annotation Editor")};
const QString defaultTabName = {tr("Annotation")};
Ui::GlobalAnnotationEditorDialog *ui;
Annotation m_annotation;
GlobalAnnotationStatus m_globalStatus;
bool m_statusIsActive;
};

View File

@@ -73,6 +73,23 @@
</item>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbTabView">
<property name="text">
<string>Tab View</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbTableView">
<property name="text">
<string>Table View</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@@ -90,7 +107,7 @@
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="AnnotationTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
@@ -109,6 +126,9 @@
</widget>
</widget>
</item>
<item>
<widget class="AnnotationTableView" name="tableView"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="focusPolicy">
@@ -124,6 +144,19 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AnnotationTabWidget</class>
<extends>QTabWidget</extends>
<header>annotationtabwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AnnotationTableView</class>
<extends>QTableView</extends>
<header>annotationtableview.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
</tabstops>

View File

@@ -426,8 +426,8 @@ void FormEditorAnnotationIcon::createAnnotationEditor()
m_annotationEditor = new AnnotationEditorDialog(Core::ICore::dialogParent(),
m_modelNode.displayName(),
m_modelNode.customId(),
m_modelNode.annotation());
m_modelNode.customId());
m_annotationEditor->setAnnotation(m_modelNode.annotation());
connect(m_annotationEditor, &AnnotationEditorDialog::acceptedDialog,
this, &FormEditorAnnotationIcon::annotationDialogAccepted);

View File

@@ -730,6 +730,7 @@ Project {
"annotationeditor/annotationcommenttab.ui",
"annotationeditor/annotationeditor.cpp",
"annotationeditor/annotationeditor.h",
"annotationeditor/annotationeditor.qrc",
"annotationeditor/globalannotationeditor.cpp",
"annotationeditor/globalannotationeditor.h",
"annotationeditor/annotationeditordialog.cpp",
@@ -738,6 +739,12 @@ Project {
"annotationeditor/globalannotationeditordialog.cpp",
"annotationeditor/globalannotationeditordialog.h",
"annotationeditor/globalannotationeditordialog.ui",
"annotationeditor/defaultannotations.cpp",
"annotationeditor/defaultannotations.h",
"annotationeditor/annotationtableview.cpp",
"annotationeditor/annotationtableview.h",
"annotationeditor/annotationtabwidget.cpp",
"annotationeditor/annotationtabwidget.h",
"bindingeditor/bindingeditor.cpp",
"bindingeditor/bindingeditor.h",
"bindingeditor/actioneditor.cpp",