From f6b987f7777349537f8984d41b7fd6dc446ab518 Mon Sep 17 00:00:00 2001 From: Derek Christ Date: Mon, 30 Aug 2021 14:32:05 +0200 Subject: [PATCH] Add functionality to select comments and directly delete/edit them It's now possible to select comments directly in the traceplot instead of selecting them in the CommentTreeWidget. Selections from the TreeWidget are synchronized to the plot but not vice versa. It would generally be the better to introduce a Model/View based approach instead of trying to synchronize selections and QActions. --- .../traceAnalyzer/businessObjects/comment.h | 10 +++ .../presentation/commenttreewidget.cpp | 48 +++++------ .../presentation/commenttreewidget.h | 20 +++-- .../presentation/tracenavigator.cpp | 62 +++++++++++++- .../presentation/tracenavigator.h | 17 +++- .../traceAnalyzer/presentation/traceplot.cpp | 85 ++++++++++++++++--- .../traceAnalyzer/presentation/traceplot.h | 4 +- .../presentation/tracescroller.cpp | 10 ++- DRAMSys/traceAnalyzer/tracefiletab.cpp | 3 + 9 files changed, 207 insertions(+), 52 deletions(-) diff --git a/DRAMSys/traceAnalyzer/businessObjects/comment.h b/DRAMSys/traceAnalyzer/businessObjects/comment.h index c52753f8..91d8860b 100644 --- a/DRAMSys/traceAnalyzer/businessObjects/comment.h +++ b/DRAMSys/traceAnalyzer/businessObjects/comment.h @@ -59,6 +59,16 @@ public: { text = newText; } + bool isSelected(traceTime mouseTime, traceTime clk) + { + // Increase selectable area of comments + traceTime offset = static_cast(0.35 * clk); + + if (time >= (mouseTime - offset) && time <= (mouseTime + offset)) + return true; + + return false; + } }; #endif // COMMENT_H diff --git a/DRAMSys/traceAnalyzer/presentation/commenttreewidget.cpp b/DRAMSys/traceAnalyzer/presentation/commenttreewidget.cpp index ebdf84db..399354a2 100644 --- a/DRAMSys/traceAnalyzer/presentation/commenttreewidget.cpp +++ b/DRAMSys/traceAnalyzer/presentation/commenttreewidget.cpp @@ -41,12 +41,10 @@ #include #include #include -#include -#include CommentTreeWidget::CommentTreeWidget(QWidget *parent) : QTreeWidget(parent), deleteComment(new QAction("Delete comment", this)), - changeCommentText(new QAction("Change comment text", this)) + editCommentText(new QAction("Edit comment text", this)) { connect(deleteComment, &QAction::triggered, this, [this](){ for (QTreeWidgetItem *item : selectedItems()) @@ -56,25 +54,25 @@ CommentTreeWidget::CommentTreeWidget(QWidget *parent) : QTreeWidget(parent), } }); - connect(changeCommentText, &QAction::triggered, this, [this](){ + connect(editCommentText, &QAction::triggered, this, [this](){ for (QTreeWidgetItem *item : selectedItems()) { CommentTreeItem *commentItem = static_cast(item); - - bool ok; - QString newText = QInputDialog::getText(this, QString("Change comment text"), - QString("Change comment text"), QLineEdit::Normal, - commentItem->Text(), &ok); - - if (ok) - { - deleteComment->trigger(); - navigator->insertComment({commentItem->Time(), newText}); - } - - commentsChanged(); + navigator->editCommentAtTime(commentItem->Time()); } }); + + connect(this, &QTreeWidget::itemSelectionChanged, this, [this](){ + std::vector> selectedComments; + + for (auto &item : selectedItems()) + { + CommentTreeItem *commentItem = static_cast(item); + selectedComments.push_back(commentItem->getComment()); + } + + Q_EMIT selectedCommentsChanged(selectedComments); + }); } void CommentTreeWidget::init(TraceNavigator *navigator) @@ -103,7 +101,7 @@ void CommentTreeWidget::commentsChanged() void CommentTreeWidget::printComments() { for (const auto &pair : navigator->getComments()) { - const Comment &comment = pair.second; + const std::shared_ptr comment = pair.second; QTreeWidgetItem *item = new CommentTreeItem(this, comment); addTopLevelItem(item); } @@ -118,17 +116,19 @@ void CommentTreeWidget::itemDoubleClicked(QTreeWidgetItem *item, int /*column*/) void CommentTreeWidget::ContextMenuRequested(QPoint point) { + if (selectedItems().isEmpty()) + return; + QMenu contextMenu(this); - contextMenu.addActions({deleteComment, changeCommentText}); + contextMenu.addActions({editCommentText, deleteComment}); contextMenu.exec(mapToGlobal(point)); } - CommentTreeWidget::CommentTreeItem::CommentTreeItem(QTreeWidget *parent, - const Comment &comment): QTreeWidgetItem(parent), comment(comment) + const std::shared_ptr comment): QTreeWidgetItem(parent), comment(comment) { - this->setText(0, prettyFormatTime(comment.Time())); - this->setText(1, comment.Text()); + this->setText(0, prettyFormatTime(comment->Time())); + this->setText(1, comment->Text()); } void CommentTreeWidget::keyPressEvent(QKeyEvent *event) @@ -136,7 +136,7 @@ void CommentTreeWidget::keyPressEvent(QKeyEvent *event) if (event->key() == Qt::Key_Delete) deleteComment->trigger(); else if (event->key() == Qt::Key_F2) - changeCommentText->trigger(); + editCommentText->trigger(); QTreeWidget::keyPressEvent(event); } diff --git a/DRAMSys/traceAnalyzer/presentation/commenttreewidget.h b/DRAMSys/traceAnalyzer/presentation/commenttreewidget.h index 7429f75e..c5c63bad 100644 --- a/DRAMSys/traceAnalyzer/presentation/commenttreewidget.h +++ b/DRAMSys/traceAnalyzer/presentation/commenttreewidget.h @@ -43,6 +43,7 @@ #include #include "businessObjects/comment.h" #include "tracenavigator.h" +#include "businessObjects/tracetime.h" class CommentTreeWidget : public QTreeWidget { @@ -55,6 +56,9 @@ public: protected: void keyPressEvent(QKeyEvent *event) override; +Q_SIGNALS: + void selectedCommentsChanged(std::vector> comments); + private Q_SLOTS: void commentsChanged(); void ContextMenuRequested(QPoint point); @@ -65,27 +69,29 @@ private: TraceNavigator *navigator; void printComments(); QAction *deleteComment; - QAction *changeCommentText; + QAction *editCommentText; class CommentTreeItem : public QTreeWidgetItem { private: - Comment comment; + const std::shared_ptr comment; public: - CommentTreeItem(QTreeWidget *parent, const Comment &comment); + CommentTreeItem(QTreeWidget *parent, const std::shared_ptr comment); QString Text() { - return comment.Text(); + return comment->Text(); } traceTime Time() { - return comment.Time(); + return comment->Time(); } - void changeText(QString newText) + const std::shared_ptr getComment() { - comment.changeText(newText); + return comment; } }; + + CommentTreeItem *getTreeItemFromTime(traceTime time); }; #endif // COMMENTTREEWIDGET_H diff --git a/DRAMSys/traceAnalyzer/presentation/tracenavigator.cpp b/DRAMSys/traceAnalyzer/presentation/tracenavigator.cpp index 13b0526c..0758cd01 100644 --- a/DRAMSys/traceAnalyzer/presentation/tracenavigator.cpp +++ b/DRAMSys/traceAnalyzer/presentation/tracenavigator.cpp @@ -39,6 +39,8 @@ #include "tracenavigator.h" #include "util/traceplotlinecache.h" #include "vector" +#include +#include using namespace std; @@ -85,7 +87,7 @@ void TraceNavigator::navigateToTransaction(ID id) */ void TraceNavigator::insertComment(const Comment &comment) { - comments.emplace(comment.Time(), comment); + comments.emplace(comment.Time(), std::make_shared(comment)); changesToCommitExist = true; Q_EMIT commentsChanged(); } @@ -93,6 +95,7 @@ void TraceNavigator::insertComment(const Comment &comment) void TraceNavigator::removeCommentAtTime(traceTime time) { auto found = comments.find(time); + if (found != comments.end()) { comments.erase(found); changesToCommitExist = true; @@ -100,7 +103,60 @@ void TraceNavigator::removeCommentAtTime(traceTime time) } } +void TraceNavigator::editCommentAtTime(traceTime time) +{ + auto found = comments.find(time); + if (found != comments.end()) { + comments.erase(found); + auto comment = found->second; + + bool ok; + QString newText = QInputDialog::getText(nullptr, QString("Edit comment text"), + QString("Edit comment text"), QLineEdit::Normal, + comment->Text(), &ok); + + if (ok) + { + removeCommentAtTime(time); + insertComment({comment->Time(), newText}); + changesToCommitExist = true; + Q_EMIT commentsChanged(); + } + } +} + +void TraceNavigator::addSelectedComments(const std::vector> + &comments) +{ + for (auto &comment : comments) + selectedComments.push_back(comment); + + Q_EMIT selectedCommentsChanged(); +} + +void TraceNavigator::clearSelectedComments() +{ + selectedComments.clear(); + Q_EMIT selectedCommentsChanged(); +} + +void TraceNavigator::updateCommentSelection(std::vector> comments) +{ + selectedComments.clear(); + addSelectedComments(comments); +} + +bool TraceNavigator::commentIsSelected(const std::shared_ptr comment) const +{ + for (auto &selectedComment : selectedComments) + { + if (comment == selectedComment) + return true; + } + + return false; +} /* DB * @@ -110,7 +166,7 @@ void TraceNavigator::commitChangesToDB() { vector commentsToInsert; for (const auto &pair : comments) { - commentsToInsert.push_back(pair.second); + commentsToInsert.push_back(*pair.second.get()); } traceFile.updateComments(commentsToInsert); @@ -120,7 +176,7 @@ void TraceNavigator::commitChangesToDB() void TraceNavigator::getCommentsFromDB() { for (const Comment &comment : traceFile.getComments()) { - comments.emplace(comment.Time(), comment); + comments.emplace(comment.Time(), std::make_shared(comment)); } } diff --git a/DRAMSys/traceAnalyzer/presentation/tracenavigator.h b/DRAMSys/traceAnalyzer/presentation/tracenavigator.h index 28bc8af7..00c76294 100644 --- a/DRAMSys/traceAnalyzer/presentation/tracenavigator.h +++ b/DRAMSys/traceAnalyzer/presentation/tracenavigator.h @@ -56,7 +56,7 @@ class TracePlotLineCache; class TraceNavigator : public QObject { Q_OBJECT - using CommentMap = std::map; + using CommentMap = std::map>; public: TraceNavigator(QString path, QObject *parent = 0); @@ -117,6 +117,16 @@ public: return comments; } void removeCommentAtTime(traceTime time); + void editCommentAtTime(traceTime time); + void addSelectedComments(const std::vector> + &comments); + void clearSelectedComments(); + const std::vector> &SelectedComments() + { + return selectedComments; + } + + bool commentIsSelected(const std::shared_ptr comment) const; void commitChangesToDB(); void refreshData(); @@ -127,8 +137,12 @@ public: Q_SIGNALS: void currentTraceTimeChanged(); void selectedTransactionsChanged(); + void selectedCommentsChanged(); void commentsChanged(); +public Q_SLOTS: + void updateCommentSelection(std::vector> comments); + private: TraceDB traceFile; @@ -136,6 +150,7 @@ private: //components drawing the tracefile center around that time traceTime currentTraceTime = 0; std::vector> selectedTransactions; + std::vector> selectedComments; CommentMap comments; void getCommentsFromDB(); bool changesToCommitExist; diff --git a/DRAMSys/traceAnalyzer/presentation/traceplot.cpp b/DRAMSys/traceAnalyzer/presentation/traceplot.cpp index fef381a1..64fbf582 100644 --- a/DRAMSys/traceAnalyzer/presentation/traceplot.cpp +++ b/DRAMSys/traceAnalyzer/presentation/traceplot.cpp @@ -216,6 +216,8 @@ void TracePlot::connectNavigatorQ_SIGNALS() SLOT(currentTraceTimeChanged())); QObject::connect(navigator, SIGNAL(selectedTransactionsChanged()), this, SLOT(selectedTransactionsChanged())); + QObject::connect(navigator, SIGNAL(selectedCommentsChanged()), this, + SLOT(commentsChanged())); QObject::connect(navigator, SIGNAL(commentsChanged()), this, SLOT(commentsChanged())); } @@ -344,15 +346,17 @@ Timespan TracePlot::GetCurrentTimespan() void TracePlot::getAndDrawComments() { for (const auto &pair : navigator->getComments()) { - const Comment &comment = pair.second; - QwtPlotMarker *maker = new QwtPlotMarker(); - maker->setLabel(comment.Text()); - maker->setLabelOrientation(Qt::Vertical); - maker->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom); - maker->setXValue(static_cast(comment.Time())); - maker->setLineStyle(QwtPlotMarker::LineStyle::VLine); - maker->setLinePen(QColor(Qt::blue), 2); - maker->attach(this); + const std::shared_ptr comment = pair.second; + bool highlight = navigator->commentIsSelected(comment); + + QwtPlotMarker *marker = new QwtPlotMarker(); + marker->setLabel(comment->Text()); + marker->setLabelOrientation(Qt::Vertical); + marker->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom); + marker->setXValue(static_cast(comment->Time())); + marker->setLineStyle(QwtPlotMarker::LineStyle::VLine); + marker->setLinePen(QColor(highlight ? Qt::red : Qt::blue), 2); + marker->attach(this); } } @@ -646,9 +650,13 @@ bool TracePlot::eventFilter(QObject *object, QEvent *event) mouseDownData.mouseIsDownForDragging = true; canvas()->setCursor(Qt::ClosedHandCursor); SelectTransaction(mouseEvent->x(), mouseEvent->y()); + SelectComment(mouseEvent->x()); } return true; } else if (mouseEvent->button() == Qt::RightButton) { + // Also select comments to make it more obvious. + SelectComment(mouseEvent->x()); + openContextMenu(this->canvas()->mapToGlobal(mouseEvent->pos()), mouseEvent->pos()); return true; @@ -704,7 +712,7 @@ bool TracePlot::eventFilter(QObject *object, QEvent *event) return false; } -void TracePlot::SelectTransaction(int x, int y) +void TracePlot::SelectTransaction(int x, int y) const { double yVal = invTransform(yLeft, y); traceTime time = invTransform(xBottom, x); @@ -717,10 +725,61 @@ void TracePlot::SelectTransaction(int x, int y) } } +void TracePlot::SelectComment(int x) const +{ + traceTime time = invTransform(xBottom, x); + + auto selectedComments = hoveredComments(time); + + if (selectedComments.size() > 0) { + if (!keyPressData.ctrlPressed) + navigator->clearSelectedComments(); + + navigator->addSelectedComments(selectedComments); + } else { + navigator->clearSelectedComments(); + } +} + +const std::vector> TracePlot::hoveredComments(traceTime time) const +{ + std::vector> hoveredComments; + + for (const auto &commentPair : navigator->getComments()) + { + auto comment = commentPair.second; + if (comment->isSelected(time, navigator->GeneralTraceInfo().clkPeriod)) + hoveredComments.push_back(comment); + } + + return hoveredComments; +} + void TracePlot::openContextMenu(const QPoint &pos, const QPoint &mouseDown) { contextMenuMouseDown = mouseDown; - contextMenu->exec(pos); + + traceTime time = invTransform(xBottom, mouseDown.x()); + auto comments = hoveredComments(time); + + if (comments.size() == 0) + contextMenu->exec(pos); + else + { + QMenu commentContextMenu(this); + QAction editCommentText("Edit comment text", this); + QAction deleteComment("Delete comment", this); + + auto commentTime = comments[0]->Time(); + connect(&editCommentText, &QAction::triggered, this, [=](){ + navigator->editCommentAtTime(commentTime); + }); + + connect(&deleteComment, &QAction::triggered, this, [=](){ + navigator->removeCommentAtTime(commentTime); + }); + + commentContextMenu.addActions({&editCommentText, &deleteComment}); + commentContextMenu.exec(pos); + } } - - diff --git a/DRAMSys/traceAnalyzer/presentation/traceplot.h b/DRAMSys/traceAnalyzer/presentation/traceplot.h index 2676988d..10dd534d 100644 --- a/DRAMSys/traceAnalyzer/presentation/traceplot.h +++ b/DRAMSys/traceAnalyzer/presentation/traceplot.h @@ -186,7 +186,9 @@ private: void openContextMenu(const QPoint &pos, const QPoint &mouseDown); QPoint contextMenuMouseDown; - void SelectTransaction(int x, int y); + void SelectTransaction(int x, int y) const; + void SelectComment(int x) const; + const std::vector> hoveredComments(traceTime time) const; void keyPressEvent(QKeyEvent *keyPressedEvent); void keyReleaseEvent(QKeyEvent *keyReleasedEvent); diff --git a/DRAMSys/traceAnalyzer/presentation/tracescroller.cpp b/DRAMSys/traceAnalyzer/presentation/tracescroller.cpp index ae9906a1..91b8b3a2 100644 --- a/DRAMSys/traceAnalyzer/presentation/tracescroller.cpp +++ b/DRAMSys/traceAnalyzer/presentation/tracescroller.cpp @@ -121,6 +121,8 @@ void TraceScroller::connectNavigatorQ_SIGNALS() SLOT(currentTraceTimeChanged())); QObject::connect(navigator, SIGNAL(commentsChanged()), this, SLOT(commentsChanged())); + QObject::connect(navigator, SIGNAL(selectedCommentsChanged()), this, + SLOT(commentsChanged())); QObject::connect(navigator, SIGNAL(selectedTransactionsChanged()), this, SLOT(selectedTransactionsChanged())); } @@ -148,11 +150,13 @@ Timespan TraceScroller::GetCurrentTimespan() void TraceScroller::getAndDrawComments() { for (const auto &pair : navigator->getComments()) { - const Comment &comment = pair.second; + const std::shared_ptr comment = pair.second; + bool highlight = navigator->commentIsSelected(comment); + QwtPlotMarker *maker = new QwtPlotMarker(); - maker->setXValue(static_cast(comment.Time())); + maker->setXValue(static_cast(comment->Time())); maker->setLineStyle(QwtPlotMarker::LineStyle::VLine); - maker->setLinePen(QColor(Qt::blue), 2); + maker->setLinePen(QColor(highlight ? Qt::red : Qt::blue), 2); maker->attach(this); } } diff --git a/DRAMSys/traceAnalyzer/tracefiletab.cpp b/DRAMSys/traceAnalyzer/tracefiletab.cpp index 36c95509..1fe452d0 100644 --- a/DRAMSys/traceAnalyzer/tracefiletab.cpp +++ b/DRAMSys/traceAnalyzer/tracefiletab.cpp @@ -89,6 +89,9 @@ TraceFileTab::TraceFileTab(QWidget *parent, const QString &path) : ui->memSpecView->setModel(memSpecModel); ui->memSpecView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + connect(ui->commentTree, &CommentTreeWidget::selectedCommentsChanged, + navigator, &TraceNavigator::updateCommentSelection); + tracefileChanged(); }