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 \ menus/AddressableItemContextMenu.cpp \
common/AddressableItemModel.cpp \ common/AddressableItemModel.cpp \
widgets/ListDockWidget.cpp \ widgets/ListDockWidget.cpp \
dialogs/MultitypeFileSaveDialog.cpp dialogs/MultitypeFileSaveDialog.cpp \
widgets/BoolToggleDelegate.cpp
GRAPHVIZ_SOURCES = \ GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp widgets/GraphvizLayout.cpp
@ -528,7 +529,8 @@ HEADERS += \
common/AddressableItemModel.h \ common/AddressableItemModel.h \
widgets/ListDockWidget.h \ widgets/ListDockWidget.h \
widgets/AddressableItemList.h \ widgets/AddressableItemList.h \
dialogs/MultitypeFileSaveDialog.h dialogs/MultitypeFileSaveDialog.h \
widgets/BoolToggleDelegate.cpp
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h

View File

@ -1776,6 +1776,15 @@ void CutterCore::disableBreakpoint(RVA addr)
emit breakpointsChanged(); 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> CutterCore::getBreakpoints()
{ {
QList<BreakpointDescription> ret; QList<BreakpointDescription> ret;

View File

@ -326,6 +326,13 @@ public:
void delAllBreakpoints(); void delAllBreakpoints();
void enableBreakpoint(RVA addr); void enableBreakpoint(RVA addr);
void disableBreakpoint(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); bool isBreakpoint(const QList<RVA> &breakpoints, RVA addr);
QList<RVA> getBreakpointsAddresses(); 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 "dialogs/BreakpointsDialog.h"
#include "core/MainWindow.h" #include "core/MainWindow.h"
#include "common/Helpers.h" #include "common/Helpers.h"
#include "widgets/BoolToggleDelegate.h"
#include <QMenu> #include <QMenu>
#include <QStyledItemDelegate>
#include <QCheckBox>
BreakpointModel::BreakpointModel(QList<BreakpointDescription> *breakpoints, QObject *parent) BreakpointModel::BreakpointModel(QObject *parent)
: QAbstractListModel(parent), : AddressableItemModel<QAbstractListModel>(parent)
breakpoints(breakpoints)
{ {
} }
void BreakpointModel::refresh()
{
beginResetModel();
breakpoints = Core()->getBreakpoints();
endResetModel();
}
int BreakpointModel::rowCount(const QModelIndex &) const int BreakpointModel::rowCount(const QModelIndex &) const
{ {
return breakpoints->count(); return breakpoints.count();
} }
int BreakpointModel::columnCount(const QModelIndex &) const 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 QVariant BreakpointModel::data(const QModelIndex &index, int role) const
{ {
if (index.row() >= breakpoints->count()) if (index.row() >= breakpoints.count())
return QVariant(); return QVariant();
const BreakpointDescription &breakpoint = breakpoints->at(index.row()); const BreakpointDescription &breakpoint = breakpoints.at(index.row());
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
@ -44,6 +53,15 @@ QVariant BreakpointModel::data(const QModelIndex &index, int role) const
default: default:
return QVariant(); 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: case BreakpointDescriptionRole:
return QVariant::fromValue(breakpoint); return QVariant::fromValue(breakpoint);
default: default:
@ -74,10 +92,62 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation, int role) con
} }
} }
BreakpointProxyModel::BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent) bool BreakpointModel::setData(const QModelIndex &index, const QVariant &value, int role)
: QSortFilterProxyModel(parent) {
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 bool BreakpointProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
@ -117,17 +187,19 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) :
{ {
ui->setupUi(this); ui->setupUi(this);
breakpointModel = new BreakpointModel(&breakpoints, this); ui->breakpointTreeView->setMainWindow(mainWindow);
breakpointModel = new BreakpointModel(this);
breakpointProxyModel = new BreakpointProxyModel(breakpointModel, this); breakpointProxyModel = new BreakpointProxyModel(breakpointModel, this);
ui->breakpointTreeView->setModel(breakpointProxyModel); ui->breakpointTreeView->setModel(breakpointProxyModel);
ui->breakpointTreeView->sortByColumn(BreakpointModel::AddrColumn, Qt::AscendingOrder); ui->breakpointTreeView->sortByColumn(BreakpointModel::AddrColumn, Qt::AscendingOrder);
ui->breakpointTreeView->setItemDelegate(new BoolTogggleDelegate(this));
refreshDeferrer = createRefreshDeferrer([this]() { refreshDeferrer = createRefreshDeferrer([this]() {
refreshBreakpoint(); refreshBreakpoint();
}); });
setScrollMode(); setScrollMode();
actionDelBreakpoint = new QAction(tr("Delete breakpoint"), this); actionDelBreakpoint = new QAction(tr("Delete breakpoint"), this);
actionDelBreakpoint->setShortcut(Qt::Key_Delete); actionDelBreakpoint->setShortcut(Qt::Key_Delete);
actionDelBreakpoint->setShortcutContext(Qt::WidgetShortcut); actionDelBreakpoint->setShortcutContext(Qt::WidgetShortcut);
@ -140,6 +212,10 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) :
connect(actionToggleBreakpoint, &QAction::triggered, this, &BreakpointWidget::toggleBreakpoint); connect(actionToggleBreakpoint, &QAction::triggered, this, &BreakpointWidget::toggleBreakpoint);
ui->breakpointTreeView->addAction(actionToggleBreakpoint); 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::refreshAll, this, &BreakpointWidget::refreshBreakpoint);
connect(Core(), &CutterCore::breakpointsChanged, this, &BreakpointWidget::refreshBreakpoint); connect(Core(), &CutterCore::breakpointsChanged, this, &BreakpointWidget::refreshBreakpoint);
connect(Core(), &CutterCore::codeRebased, 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->addBreakpoint, &QAbstractButton::clicked, this, &BreakpointWidget::addBreakpointDialog);
connect(ui->delBreakpoint, &QAbstractButton::clicked, this, &BreakpointWidget::delBreakpoint); connect(ui->delBreakpoint, &QAbstractButton::clicked, this, &BreakpointWidget::delBreakpoint);
connect(ui->delAllBreakpoints, &QAbstractButton::clicked, Core(), &CutterCore::delAllBreakpoints); 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; BreakpointWidget::~BreakpointWidget() = default;
void BreakpointWidget::refreshBreakpoint() void BreakpointWidget::refreshBreakpoint()
{ {
if (!refreshDeferrer->attemptRefresh(nullptr)) { if (editing || !refreshDeferrer->attemptRefresh(nullptr)) {
return; return;
} }
breakpointModel->beginResetModel(); breakpointModel->refresh();
breakpoints = Core()->getBreakpoints();
breakpointModel->endResetModel();
ui->breakpointTreeView->resizeColumnToContents(0); ui->breakpointTreeView->resizeColumnToContents(0);
ui->breakpointTreeView->resizeColumnToContents(1); ui->breakpointTreeView->resizeColumnToContents(1);
@ -174,26 +245,6 @@ void BreakpointWidget::setScrollMode()
qhelpers::setVerticalScrollMode(ui->breakpointTreeView); 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() void BreakpointWidget::addBreakpointDialog()
{ {
BreakpointsDialog dialog(this); 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() void BreakpointWidget::delBreakpoint()
{ {
BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data( auto breakpointsToRemove = getSelectedAddresses();
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>(); for (auto address : breakpointsToRemove) {
Core()->delBreakpoint(bp.addr); Core()->delBreakpoint(address);
}
} }
void BreakpointWidget::toggleBreakpoint() void BreakpointWidget::toggleBreakpoint()
{ {
BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data( auto selection = ui->breakpointTreeView->selectionModel()->selectedRows();
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>(); editing = true;
if (bp.enabled) { for (auto row : selection) {
Core()->disableBreakpoint(bp.addr); auto cell = breakpointProxyModel->index(row.row(), BreakpointModel::EnabledColumn);
} else { breakpointProxyModel->setData(cell, !cell.data(Qt::EditRole).toBool());
Core()->enableBreakpoint(bp.addr);
} }
editing = false;
} }

View File

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

View File

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

View File

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