306 lines
9.2 KiB
C++
306 lines
9.2 KiB
C++
/*
|
|
* 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 <QMenu>
|
|
#include <QItemSelectionModel>
|
|
#include <QKeyEvent>
|
|
|
|
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<int>(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<int>(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<Column>(index.column()) == Column::Time)
|
|
return QVariant(comment.time);
|
|
|
|
switch (static_cast<Column>(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<Column>(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<Column>(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<int>(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::Comment> &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<QKeyEvent *>(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<Column>(index.column()) == Column::Time)
|
|
Q_EMIT gotoCommentTriggered(index);
|
|
}
|