Add more breakpoint editing options (#1967)

* Convert breakpoint widget to AddressableItemWidget
* When selecting multiple breakpoints and pressing delete key delete all of them
* Allow toggling breakpoint trace and active properties by doublicking them in table
This commit is contained in:
karliss 2019-12-26 22:51:55 +02:00 committed by GitHub
parent 2097ce3be2
commit 46ba0dc20a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 210 additions and 62 deletions

View File

@ -385,7 +385,8 @@ SOURCES += \
menus/AddressableItemContextMenu.cpp \
common/AddressableItemModel.cpp \
widgets/ListDockWidget.cpp \
dialogs/MultitypeFileSaveDialog.cpp
dialogs/MultitypeFileSaveDialog.cpp \
widgets/BoolToggleDelegate.cpp
GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp
@ -528,7 +529,8 @@ HEADERS += \
common/AddressableItemModel.h \
widgets/ListDockWidget.h \
widgets/AddressableItemList.h \
dialogs/MultitypeFileSaveDialog.h
dialogs/MultitypeFileSaveDialog.h \
widgets/BoolToggleDelegate.cpp
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h

View File

@ -1776,6 +1776,15 @@ void CutterCore::disableBreakpoint(RVA addr)
emit breakpointsChanged();
}
void CutterCore::setBreakpointTrace(int index, bool enabled)
{
if (enabled) {
cmd(QString("dbite %1").arg(index));
} else {
cmd(QString("dbitd %1").arg(index));
}
}
QList<BreakpointDescription> CutterCore::getBreakpoints()
{
QList<BreakpointDescription> ret;

View File

@ -326,6 +326,13 @@ public:
void delAllBreakpoints();
void enableBreakpoint(RVA addr);
void disableBreakpoint(RVA addr);
/**
* @brief Enable or disable breakpoint tracing.
* @param index - breakpoint index to modify
* @param enabled - true if tracing should be enabled
*/
void setBreakpointTrace(int index, bool enabled);
bool isBreakpoint(const QList<RVA> &breakpoints, RVA addr);
QList<RVA> getBreakpointsAddresses();

View File

@ -0,0 +1,32 @@
#include "BoolToggleDelegate.h"
#include <QEvent>
BoolTogggleDelegate::BoolTogggleDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
QWidget *BoolTogggleDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data(Qt::EditRole).type() == QVariant::Bool) {
return nullptr;
}
return QStyledItemDelegate::createEditor(parent, option, index);
}
bool BoolTogggleDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (model->flags(index).testFlag(Qt::ItemFlag::ItemIsEditable)) {
if (event->type() == QEvent::MouseButtonDblClick) {
auto data = index.data(Qt::EditRole);
if (data.type() == QVariant::Bool) {
model->setData(index, !data.toBool());
return true;
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}

View File

@ -0,0 +1,20 @@
#ifndef BOOLTOGGGLEDELEGATE_H
#define BOOLTOGGGLEDELEGATE_H
#include <QStyledItemDelegate>
class BoolTogggleDelegate : public QStyledItemDelegate
{
public:
BoolTogggleDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index) override;
};
#endif // BOOLTOGGGLEDELEGATE_H

View File

@ -3,17 +3,26 @@
#include "dialogs/BreakpointsDialog.h"
#include "core/MainWindow.h"
#include "common/Helpers.h"
#include "widgets/BoolToggleDelegate.h"
#include <QMenu>
#include <QStyledItemDelegate>
#include <QCheckBox>
BreakpointModel::BreakpointModel(QList<BreakpointDescription> *breakpoints, QObject *parent)
: QAbstractListModel(parent),
breakpoints(breakpoints)
BreakpointModel::BreakpointModel(QObject *parent)
: AddressableItemModel<QAbstractListModel>(parent)
{
}
void BreakpointModel::refresh()
{
beginResetModel();
breakpoints = Core()->getBreakpoints();
endResetModel();
}
int BreakpointModel::rowCount(const QModelIndex &) const
{
return breakpoints->count();
return breakpoints.count();
}
int BreakpointModel::columnCount(const QModelIndex &) const
@ -23,10 +32,10 @@ int BreakpointModel::columnCount(const QModelIndex &) const
QVariant BreakpointModel::data(const QModelIndex &index, int role) const
{
if (index.row() >= breakpoints->count())
if (index.row() >= breakpoints.count())
return QVariant();
const BreakpointDescription &breakpoint = breakpoints->at(index.row());
const BreakpointDescription &breakpoint = breakpoints.at(index.row());
switch (role) {
case Qt::DisplayRole:
@ -44,6 +53,15 @@ QVariant BreakpointModel::data(const QModelIndex &index, int role) const
default:
return QVariant();
}
case Qt::EditRole:
switch (index.column()) {
case TraceColumn:
return breakpoint.trace;
case EnabledColumn:
return breakpoint.enabled;
default:
return data(index, Qt::DisplayRole);
}
case BreakpointDescriptionRole:
return QVariant::fromValue(breakpoint);
default:
@ -74,10 +92,62 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation, int role) con
}
}
BreakpointProxyModel::BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent)
: QSortFilterProxyModel(parent)
bool BreakpointModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= breakpoints.count())
return false;
BreakpointDescription &breakpoint = breakpoints[index.row()];
switch (role) {
case Qt::EditRole:
switch (index.column()) {
case TraceColumn:
breakpoint.trace = value.toBool();
Core()->setBreakpointTrace(index.row(), breakpoint.trace);
emit dataChanged(index, index, {role, Qt::DisplayRole});
return true;
case EnabledColumn:
breakpoint.enabled = value.toBool();
if (breakpoint.enabled) {
Core()->enableBreakpoint(breakpoint.addr);
} else {
Core()->disableBreakpoint(breakpoint.addr);
}
emit dataChanged(index, index, {role, Qt::DisplayRole});
return true;
default:
return false;
}
default:
return false;
}
}
Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const
{
switch (index.column()) {
case TraceColumn:
return AddressableItemModel::flags(index) | Qt::ItemFlag::ItemIsEditable;
case EnabledColumn:
return AddressableItemModel::flags(index) | Qt::ItemFlag::ItemIsEditable;
default:
return AddressableItemModel::flags(index);
}
}
RVA BreakpointModel::address(const QModelIndex &index) const
{
if (index.row() < breakpoints.count()) {
return breakpoints.at(index.row()).addr;
}
return RVA_INVALID;
}
BreakpointProxyModel::BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent)
: AddressableFilterProxyModel(sourceModel, parent)
{
setSourceModel(sourceModel);
}
bool BreakpointProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
@ -117,10 +187,12 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) :
{
ui->setupUi(this);
breakpointModel = new BreakpointModel(&breakpoints, this);
ui->breakpointTreeView->setMainWindow(mainWindow);
breakpointModel = new BreakpointModel(this);
breakpointProxyModel = new BreakpointProxyModel(breakpointModel, this);
ui->breakpointTreeView->setModel(breakpointProxyModel);
ui->breakpointTreeView->sortByColumn(BreakpointModel::AddrColumn, Qt::AscendingOrder);
ui->breakpointTreeView->setItemDelegate(new BoolTogggleDelegate(this));
refreshDeferrer = createRefreshDeferrer([this]() {
refreshBreakpoint();
@ -140,6 +212,10 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) :
connect(actionToggleBreakpoint, &QAction::triggered, this, &BreakpointWidget::toggleBreakpoint);
ui->breakpointTreeView->addAction(actionToggleBreakpoint);
auto contextMenu = ui->breakpointTreeView->getItemContextMenu();
contextMenu->addAction(actionToggleBreakpoint);
contextMenu->addAction(actionDelBreakpoint);
connect(Core(), &CutterCore::refreshAll, this, &BreakpointWidget::refreshBreakpoint);
connect(Core(), &CutterCore::breakpointsChanged, this, &BreakpointWidget::refreshBreakpoint);
connect(Core(), &CutterCore::codeRebased, this, &BreakpointWidget::refreshBreakpoint);
@ -147,22 +223,17 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) :
connect(ui->addBreakpoint, &QAbstractButton::clicked, this, &BreakpointWidget::addBreakpointDialog);
connect(ui->delBreakpoint, &QAbstractButton::clicked, this, &BreakpointWidget::delBreakpoint);
connect(ui->delAllBreakpoints, &QAbstractButton::clicked, Core(), &CutterCore::delAllBreakpoints);
ui->breakpointTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->breakpointTreeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showBreakpointContextMenu(const QPoint &)));
}
BreakpointWidget::~BreakpointWidget() = default;
void BreakpointWidget::refreshBreakpoint()
{
if (!refreshDeferrer->attemptRefresh(nullptr)) {
if (editing || !refreshDeferrer->attemptRefresh(nullptr)) {
return;
}
breakpointModel->beginResetModel();
breakpoints = Core()->getBreakpoints();
breakpointModel->endResetModel();
breakpointModel->refresh();
ui->breakpointTreeView->resizeColumnToContents(0);
ui->breakpointTreeView->resizeColumnToContents(1);
@ -174,26 +245,6 @@ void BreakpointWidget::setScrollMode()
qhelpers::setVerticalScrollMode(ui->breakpointTreeView);
}
void BreakpointWidget::on_breakpointTreeView_doubleClicked(const QModelIndex &index)
{
BreakpointDescription item = index.data(
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>();
Core()->seekAndShow(item.addr);
}
void BreakpointWidget::showBreakpointContextMenu(const QPoint &pt)
{
QMenu *menu = new QMenu(ui->breakpointTreeView);
menu->clear();
menu->addAction(actionDelBreakpoint);
menu->addAction(actionToggleBreakpoint);
menu->exec(ui->breakpointTreeView->viewport()->mapToGlobal(pt));
this->setContextMenuPolicy(Qt::CustomContextMenu);
delete menu;
}
void BreakpointWidget::addBreakpointDialog()
{
BreakpointsDialog dialog(this);
@ -209,20 +260,32 @@ void BreakpointWidget::addBreakpointDialog()
}
}
QVector<RVA> BreakpointWidget::getSelectedAddresses() const
{
auto selection = ui->breakpointTreeView->selectionModel()->selectedRows();
QVector<RVA> breakpointAddressese(selection.count());
int index = 0;
for (auto row : selection) {
breakpointAddressese[index++] = breakpointProxyModel->address(row);
}
return breakpointAddressese;
}
void BreakpointWidget::delBreakpoint()
{
BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data(
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>();
Core()->delBreakpoint(bp.addr);
auto breakpointsToRemove = getSelectedAddresses();
for (auto address : breakpointsToRemove) {
Core()->delBreakpoint(address);
}
}
void BreakpointWidget::toggleBreakpoint()
{
BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data(
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>();
if (bp.enabled) {
Core()->disableBreakpoint(bp.addr);
} else {
Core()->enableBreakpoint(bp.addr);
auto selection = ui->breakpointTreeView->selectionModel()->selectedRows();
editing = true;
for (auto row : selection) {
auto cell = breakpointProxyModel->index(row.row(), BreakpointModel::EnabledColumn);
breakpointProxyModel->setData(cell, !cell.data(Qt::EditRole).toBool());
}
editing = false;
}

View File

@ -4,6 +4,7 @@
#include "core/Cutter.h"
#include "CutterDockWidget.h"
#include "AddressableItemModel.h"
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
@ -20,31 +21,39 @@ class MainWindow;
class QTreeWidgetItem;
class BreakpointWidget;
class BreakpointModel: public QAbstractListModel
class BreakpointModel: public AddressableItemModel<QAbstractListModel>
{
Q_OBJECT
friend BreakpointWidget;
private:
QList<BreakpointDescription> *breakpoints;
QList<BreakpointDescription> breakpoints;
public:
enum Column { AddrColumn = 0, PermColumn, HwColumn, TraceColumn, EnabledColumn, ColumnCount };
enum Role { BreakpointDescriptionRole = Qt::UserRole };
BreakpointModel(QList<BreakpointDescription> *breakpoints, QObject *parent = nullptr);
BreakpointModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
void refresh();
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
RVA address(const QModelIndex &index) const override;
};
class BreakpointProxyModel : public QSortFilterProxyModel
class BreakpointProxyModel : public AddressableFilterProxyModel
{
Q_OBJECT
@ -67,8 +76,6 @@ public:
~BreakpointWidget();
private slots:
void on_breakpointTreeView_doubleClicked(const QModelIndex &index);
void showBreakpointContextMenu(const QPoint &pt);
void delBreakpoint();
void toggleBreakpoint();
void addBreakpointDialog();
@ -84,6 +91,8 @@ private:
QAction *actionToggleBreakpoint = nullptr;
void setScrollMode();
QVector<RVA> getSelectedAddresses() const;
RefreshDeferrer *refreshDeferrer;
bool editing = false;
};

View File

@ -28,7 +28,7 @@
<number>0</number>
</property>
<item>
<widget class="CutterTreeView" name="breakpointTreeView">
<widget class="AddressableItemList&lt;&gt;" name="breakpointTreeView">
<property name="styleSheet">
<string notr="true">CutterTreeView::item
{
@ -80,9 +80,9 @@
</widget>
<customwidgets>
<customwidget>
<class>CutterTreeView</class>
<class>AddressableItemList&lt;&gt;</class>
<extends>QTreeView</extends>
<header>widgets/CutterTreeView.h</header>
<header>widgets/AddressableItemList.h</header>
<container>1</container>
</customwidget>
</customwidgets>

View File

@ -101,6 +101,9 @@
<property name="shortcut">
<string>N</string>
</property>
<property name="shortcutContext">
<enum>Qt::WidgetWithChildrenShortcut</enum>
</property>
</action>
<action name="actionDelete">
<property name="text">
@ -109,6 +112,9 @@
<property name="shortcut">
<string>Del</string>
</property>
<property name="shortcutContext">
<enum>Qt::WidgetWithChildrenShortcut</enum>
</property>
</action>
</widget>
<customwidgets>