/* * Copyright (c) 2021, RPTU Kaiserslautern-Landau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: * Derek Christ */ #include "commentmodel.h" #include #include #include CommentModel::CommentModel(QObject *parent) : QAbstractTableModel(parent), gotoAction(new QAction("Goto comment", this)), editAction(new QAction("Edit comment", this)), deleteAction(new QAction("Delete comment", this)), selectAllAction(new QAction("Select all comments", this)), deselectAllAction(new QAction("Deselect all comments", this)), internalSelectionModel(new QItemSelectionModel(this, this)) { setUpActions(); } void CommentModel::setUpActions() { QObject::connect(gotoAction, &QAction::triggered, this, [=](){ const QModelIndexList indexes = internalSelectionModel->selectedRows(); for (const QModelIndex ¤tIndex : indexes) { emit gotoCommentTriggered(currentIndex); } }); QObject::connect(editAction, &QAction::triggered, this, [=](){ const QModelIndexList indexes = internalSelectionModel->selectedRows(); for (const QModelIndex ¤tIndex : indexes) emit editTriggered(index(currentIndex.row(), static_cast(Column::Comment))); }); QObject::connect(deleteAction, &QAction::triggered, this, [=](){ const QModelIndexList indexes = internalSelectionModel->selectedRows(); for (const QModelIndex ¤tIndex : indexes) removeComment(currentIndex); }); QObject::connect(selectAllAction, &QAction::triggered, this, [=](){ QModelIndex topLeft = index(0, 0); QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1); internalSelectionModel->select(QItemSelection(topLeft, bottomRight), QItemSelectionModel::Select | QItemSelectionModel::Rows); }); QObject::connect(deselectAllAction, &QAction::triggered, internalSelectionModel, &QItemSelectionModel::clearSelection); } int CommentModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return comments.size(); } int CommentModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return static_cast(Column::COLUMNCOUNT); } QVariant CommentModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); // Qt::UserRole is used to get the raw time without pretty formatting. if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::UserRole) return QVariant(); const Comment &comment = comments.at(index.row()); if (role == Qt::UserRole && static_cast(index.column()) == Column::Time) return QVariant(comment.time); switch (static_cast(index.column())) { case Column::Time: return QVariant(prettyFormatTime(comment.time)); case Column::Comment: return QVariant(comment.text); default: break; } return QVariant(); } bool CommentModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; if (role != Qt::EditRole) return false; if (static_cast(index.column()) != Column::Comment) return false; QString newText = value.toString(); Comment &comment = comments.at(index.row()); comment.text = newText; emit dataChanged(index, index); return true; } QVariant CommentModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (static_cast(section)) { case Column::Time: return "Time"; case Column::Comment: return "Comment"; default: break; } } return QVariant(); } Qt::ItemFlags CommentModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.isValid() && index.column() == static_cast(Column::Comment)) flags |= Qt::ItemIsEditable; return flags; } void CommentModel::openContextMenu() { if (!internalSelectionModel->hasSelection()) return; QMenu *menu = new QMenu(); menu->addActions({gotoAction, editAction, deleteAction}); menu->addSeparator(); menu->addActions({selectAllAction, deselectAllAction}); QObject::connect(menu, &QMenu::aboutToHide, [=]() { menu->deleteLater(); }); menu->popup(QCursor::pos()); } QItemSelectionModel *CommentModel::selectionModel() const { return internalSelectionModel; } void CommentModel::addComment(traceTime time) { auto commentIt = std::find_if(comments.rbegin(), comments.rend(), [time](const Comment &comment){ return comment.time <= time; }); int insertIndex = std::distance(comments.begin(), commentIt.base()); beginInsertRows(QModelIndex(), insertIndex, insertIndex); comments.insert(comments.begin() + insertIndex, {time, "Enter comment text..."}); endInsertRows(); internalSelectionModel->setCurrentIndex(index(insertIndex, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); emit editTriggered(index(insertIndex, 1)); } void CommentModel::addComment(traceTime time, QString text) { auto commentIt = std::find_if(comments.rbegin(), comments.rend(), [time](const Comment &comment){ return comment.time <= time; }); int insertIndex = std::distance(comments.begin(), commentIt.base()); beginInsertRows(QModelIndex(), insertIndex, insertIndex); comments.insert(comments.begin() + insertIndex, {time, text}); endInsertRows(); } void CommentModel::removeComment(traceTime time) { auto commentIt = std::find_if(comments.begin(), comments.end(), [time](const Comment &comment){ return comment.time == time; }); if (commentIt == comments.end()) return; int removeIndex = std::distance(comments.begin(), commentIt); beginRemoveRows(QModelIndex(), removeIndex, removeIndex); comments.erase(commentIt); endRemoveRows(); } void CommentModel::removeComment(const QModelIndex &index) { beginRemoveRows(QModelIndex(), index.row(), index.row()); comments.erase(comments.begin() + index.row()); endRemoveRows(); } const std::vector &CommentModel::getComments() const { return comments; } traceTime CommentModel::getTimeFromIndex(const QModelIndex &index) const { Q_ASSERT(comments.size() > index.row()); return comments[index.row()].time; } QModelIndex CommentModel::hoveredComment(Timespan timespan) const { auto commentIt = std::find_if(comments.begin(), comments.end(), [timespan](const Comment &comment){ return timespan.Begin() < comment.time && comment.time < timespan.End(); }); if (commentIt == comments.end()) return QModelIndex(); int commentIndex = std::distance(comments.begin(), commentIt); return index(commentIndex, 0); } bool CommentModel::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete) { const QModelIndexList indexes = internalSelectionModel->selectedRows(); for (const QModelIndex ¤tIndex : indexes) { removeComment(currentIndex); } } } return false; } void CommentModel::rowDoubleClicked(const QModelIndex &index) { if (!index.isValid()) return; if (static_cast(index.column()) == Column::Time) Q_EMIT gotoCommentTriggered(index); }