mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-21 04:16:12 +00:00
Add Global Vars widget, dialog, context menus (#3203)
* Add global variables support - Add Globals widget - Add global variable add/modify dialog - Add "Add at" context submenu in Disassembly widget Co-authored-by: Giovanni <561184+wargio@users.noreply.github.com> --------- Co-authored-by: Giovanni <561184+wargio@users.noreply.github.com>
This commit is contained in:
parent
041118dbd7
commit
3d30892a30
src
@ -18,6 +18,7 @@ set(SOURCES
|
||||
dialogs/CommentsDialog.cpp
|
||||
dialogs/EditInstructionDialog.cpp
|
||||
dialogs/FlagDialog.cpp
|
||||
dialogs/GlobalVariableDialog.cpp
|
||||
dialogs/RemoteDebugDialog.cpp
|
||||
dialogs/NativeDebugDialog.cpp
|
||||
dialogs/XrefsDialog.cpp
|
||||
@ -36,6 +37,7 @@ set(SOURCES
|
||||
widgets/ExportsWidget.cpp
|
||||
widgets/FlagsWidget.cpp
|
||||
widgets/FunctionsWidget.cpp
|
||||
widgets/GlobalsWidget.cpp
|
||||
widgets/ImportsWidget.cpp
|
||||
widgets/Omnibar.cpp
|
||||
widgets/RelocsWidget.cpp
|
||||
@ -172,6 +174,7 @@ set(HEADER_FILES
|
||||
dialogs/CommentsDialog.h
|
||||
dialogs/EditInstructionDialog.h
|
||||
dialogs/FlagDialog.h
|
||||
dialogs/GlobalVariableDialog.h
|
||||
dialogs/RemoteDebugDialog.h
|
||||
dialogs/NativeDebugDialog.h
|
||||
dialogs/XrefsDialog.h
|
||||
@ -327,6 +330,7 @@ set(UI_FILES
|
||||
dialogs/CommentsDialog.ui
|
||||
dialogs/EditInstructionDialog.ui
|
||||
dialogs/FlagDialog.ui
|
||||
dialogs/GlobalVariableDialog.ui
|
||||
dialogs/RemoteDebugDialog.ui
|
||||
dialogs/NativeDebugDialog.ui
|
||||
dialogs/XrefsDialog.ui
|
||||
@ -338,6 +342,7 @@ set(UI_FILES
|
||||
widgets/Dashboard.ui
|
||||
widgets/EntrypointWidget.ui
|
||||
widgets/FlagsWidget.ui
|
||||
widgets/GlobalsWidget.ui
|
||||
widgets/StringsWidget.ui
|
||||
widgets/HexdumpWidget.ui
|
||||
dialogs/preferences/PreferencesDialog.ui
|
||||
|
@ -1815,6 +1815,32 @@ QList<VariableDescription> CutterCore::getVariables(RVA at)
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<GlobalDescription> CutterCore::getAllGlobals()
|
||||
{
|
||||
CORE_LOCK();
|
||||
RzListIter *it;
|
||||
|
||||
QList<GlobalDescription> ret;
|
||||
|
||||
RzAnalysisVarGlobal *glob;
|
||||
if (core && core->analysis && core->analysis->typedb) {
|
||||
const RzList *globals = rz_analysis_var_global_get_all(core->analysis);
|
||||
CutterRzListForeach (globals, it, RzAnalysisVarGlobal, glob) {
|
||||
const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type);
|
||||
if (!gtype) {
|
||||
continue;
|
||||
}
|
||||
GlobalDescription global;
|
||||
global.addr = glob->addr;
|
||||
global.name = QString(glob->name);
|
||||
global.type = QString(gtype);
|
||||
ret << global;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<RegisterRefValueDescription> CutterCore::getRegisterRefValues()
|
||||
{
|
||||
QVector<RegisterRefValueDescription> result;
|
||||
@ -4022,6 +4048,99 @@ QList<XrefDescription> CutterCore::getXRefs(RVA addr, bool to, bool whole_functi
|
||||
return xrefList;
|
||||
}
|
||||
|
||||
void CutterCore::addGlobalVariable(RVA offset, QString name, QString typ)
|
||||
{
|
||||
name = sanitizeStringForCommand(name);
|
||||
CORE_LOCK();
|
||||
char *errmsg = NULL;
|
||||
RzType *globType = rz_type_parse_string_single(core->analysis->typedb->parser,
|
||||
typ.toStdString().c_str(), &errmsg);
|
||||
if (errmsg) {
|
||||
qWarning() << tr("Error parsing type: \"%1\" message: ").arg(typ) << errmsg;
|
||||
free(errmsg);
|
||||
return;
|
||||
}
|
||||
if (!rz_analysis_var_global_create(core->analysis, name.toStdString().c_str(), globType,
|
||||
offset)) {
|
||||
qWarning() << tr("Error creating global variable: \"%1\"").arg(name);
|
||||
return;
|
||||
}
|
||||
|
||||
emit globalVarsChanged();
|
||||
}
|
||||
|
||||
void CutterCore::modifyGlobalVariable(RVA offset, QString name, QString typ)
|
||||
{
|
||||
name = sanitizeStringForCommand(name);
|
||||
CORE_LOCK();
|
||||
RzAnalysisVarGlobal *glob = rz_analysis_var_global_get_byaddr_at(core->analysis, offset);
|
||||
if (!glob) {
|
||||
return;
|
||||
}
|
||||
// Compare if the name is not the same - also rename it
|
||||
if (name.compare(glob->name)) {
|
||||
rz_analysis_var_global_rename(core->analysis, glob->name, name.toStdString().c_str());
|
||||
}
|
||||
char *errmsg = NULL;
|
||||
RzType *globType = rz_type_parse_string_single(core->analysis->typedb->parser,
|
||||
typ.toStdString().c_str(), &errmsg);
|
||||
if (errmsg) {
|
||||
qWarning() << tr("Error parsing type: \"%1\" message: ").arg(typ) << errmsg;
|
||||
free(errmsg);
|
||||
return;
|
||||
}
|
||||
rz_analysis_var_global_set_type(glob, globType);
|
||||
|
||||
emit globalVarsChanged();
|
||||
}
|
||||
|
||||
void CutterCore::delGlobalVariable(QString name)
|
||||
{
|
||||
name = sanitizeStringForCommand(name);
|
||||
CORE_LOCK();
|
||||
rz_analysis_var_global_delete_byname(core->analysis, name.toStdString().c_str());
|
||||
|
||||
emit globalVarsChanged();
|
||||
}
|
||||
|
||||
void CutterCore::delGlobalVariable(RVA offset)
|
||||
{
|
||||
CORE_LOCK();
|
||||
rz_analysis_var_global_delete_byaddr_at(core->analysis, offset);
|
||||
|
||||
emit globalVarsChanged();
|
||||
}
|
||||
|
||||
QString CutterCore::getGlobalVariableType(QString name)
|
||||
{
|
||||
name = sanitizeStringForCommand(name);
|
||||
CORE_LOCK();
|
||||
RzAnalysisVarGlobal *glob =
|
||||
rz_analysis_var_global_get_byname(core->analysis, name.toStdString().c_str());
|
||||
if (!glob) {
|
||||
return QString("");
|
||||
}
|
||||
const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type);
|
||||
if (!gtype) {
|
||||
return QString("");
|
||||
}
|
||||
return QString(gtype);
|
||||
}
|
||||
|
||||
QString CutterCore::getGlobalVariableType(RVA offset)
|
||||
{
|
||||
CORE_LOCK();
|
||||
RzAnalysisVarGlobal *glob = rz_analysis_var_global_get_byaddr_at(core->analysis, offset);
|
||||
if (!glob) {
|
||||
return QString("");
|
||||
}
|
||||
const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type);
|
||||
if (!gtype) {
|
||||
return QString("");
|
||||
}
|
||||
return QString(gtype);
|
||||
}
|
||||
|
||||
void CutterCore::addFlag(RVA offset, QString name, RVA size)
|
||||
{
|
||||
name = sanitizeStringForCommand(name);
|
||||
@ -4536,9 +4655,9 @@ char *CutterCore::getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat form
|
||||
RzGraph *graph = rz_core_graph(core, type, address);
|
||||
if (!graph) {
|
||||
if (address == RVA_INVALID) {
|
||||
qWarning() << "Cannot get global graph";
|
||||
qWarning() << tr("Cannot get global graph");
|
||||
} else {
|
||||
qWarning() << "Cannot get graph at " << RzAddressString(address);
|
||||
qWarning() << tr("Cannot get graph at ") << RzAddressString(address);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -239,6 +239,14 @@ public:
|
||||
QString nearestFlag(RVA offset, RVA *flagOffsetOut);
|
||||
void triggerFlagsChanged();
|
||||
|
||||
/* Global Variables */
|
||||
void addGlobalVariable(RVA offset, QString name, QString typ);
|
||||
void delGlobalVariable(QString name);
|
||||
void delGlobalVariable(RVA offset);
|
||||
void modifyGlobalVariable(RVA offset, QString name, QString typ);
|
||||
QString getGlobalVariableType(QString name);
|
||||
QString getGlobalVariableType(RVA offset);
|
||||
|
||||
/* Edition functions */
|
||||
PRzAnalysisBytes getRzAnalysisBytesSingle(RVA addr);
|
||||
QString getInstructionBytes(RVA addr);
|
||||
@ -584,6 +592,7 @@ public:
|
||||
QList<ExportDescription> getAllExports();
|
||||
QList<SymbolDescription> getAllSymbols();
|
||||
QList<HeaderDescription> getAllHeaders();
|
||||
QList<GlobalDescription> getAllGlobals();
|
||||
QList<FlirtDescription> getSignaturesDB();
|
||||
QList<CommentDescription> getAllComments(const QString &filterType);
|
||||
QList<RelocDescription> getAllRelocs();
|
||||
@ -750,6 +759,7 @@ signals:
|
||||
|
||||
void functionRenamed(const RVA offset, const QString &new_name);
|
||||
void varsChanged();
|
||||
void globalVarsChanged();
|
||||
void functionsChanged();
|
||||
void flagsChanged();
|
||||
void commentsChanged(RVA addr);
|
||||
|
@ -360,6 +360,13 @@ struct VariableDescription
|
||||
QString value;
|
||||
};
|
||||
|
||||
struct GlobalDescription
|
||||
{
|
||||
RVA addr;
|
||||
QString type;
|
||||
QString name;
|
||||
};
|
||||
|
||||
struct RegisterRefValueDescription
|
||||
{
|
||||
QString name;
|
||||
@ -407,6 +414,7 @@ Q_DECLARE_METATYPE(RelocDescription)
|
||||
Q_DECLARE_METATYPE(StringDescription)
|
||||
Q_DECLARE_METATYPE(FlagspaceDescription)
|
||||
Q_DECLARE_METATYPE(FlagDescription)
|
||||
Q_DECLARE_METATYPE(GlobalDescription)
|
||||
Q_DECLARE_METATYPE(XrefDescription)
|
||||
Q_DECLARE_METATYPE(EntrypointDescription)
|
||||
Q_DECLARE_METATYPE(RzBinPluginDescription)
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "widgets/DisassemblerGraphView.h"
|
||||
#include "widgets/GraphView.h"
|
||||
#include "widgets/GraphWidget.h"
|
||||
#include "widgets/GlobalsWidget.h"
|
||||
#include "widgets/OverviewWidget.h"
|
||||
#include "widgets/OverviewView.h"
|
||||
#include "widgets/FunctionsWidget.h"
|
||||
@ -401,6 +402,7 @@ void MainWindow::initDocks()
|
||||
sectionsDock = new SectionsWidget(this),
|
||||
segmentsDock = new SegmentsWidget(this),
|
||||
symbolsDock = new SymbolsWidget(this),
|
||||
globalsDock = new GlobalsWidget(this),
|
||||
vTablesDock = new VTablesWidget(this),
|
||||
flirtDock = new FlirtWidget(this),
|
||||
rzGraphDock = new RizinGraphWidget(this),
|
||||
@ -905,6 +907,7 @@ void MainWindow::restoreDocks()
|
||||
tabifyDockWidget(dashboardDock, headersDock);
|
||||
tabifyDockWidget(dashboardDock, flirtDock);
|
||||
tabifyDockWidget(dashboardDock, symbolsDock);
|
||||
tabifyDockWidget(dashboardDock, globalsDock);
|
||||
tabifyDockWidget(dashboardDock, classesDock);
|
||||
tabifyDockWidget(dashboardDock, resourcesDock);
|
||||
tabifyDockWidget(dashboardDock, vTablesDock);
|
||||
|
@ -26,6 +26,7 @@ class FunctionsWidget;
|
||||
class ImportsWidget;
|
||||
class ExportsWidget;
|
||||
class SymbolsWidget;
|
||||
class GlobalsWidget;
|
||||
class RelocsWidget;
|
||||
class CommentsWidget;
|
||||
class StringsWidget;
|
||||
@ -240,6 +241,7 @@ private:
|
||||
TypesWidget *typesDock = nullptr;
|
||||
SearchWidget *searchDock = nullptr;
|
||||
SymbolsWidget *symbolsDock = nullptr;
|
||||
GlobalsWidget *globalsDock = nullptr;
|
||||
RelocsWidget *relocsDock = nullptr;
|
||||
CommentsWidget *commentsDock = nullptr;
|
||||
StringsWidget *stringsDock = nullptr;
|
||||
|
71
src/dialogs/GlobalVariableDialog.cpp
Normal file
71
src/dialogs/GlobalVariableDialog.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "GlobalVariableDialog.h"
|
||||
#include "ui_GlobalVariableDialog.h"
|
||||
|
||||
#include <QIntValidator>
|
||||
#include "core/Cutter.h"
|
||||
|
||||
GlobalVariableDialog::GlobalVariableDialog(RVA offset, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
ui(new Ui::GlobalVariableDialog),
|
||||
offset(offset),
|
||||
globalVariableName(""),
|
||||
globalVariableOffset(RVA_INVALID)
|
||||
{
|
||||
// Setup UI
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
|
||||
RzAnalysisVarGlobal *globalVariable =
|
||||
rz_analysis_var_global_get_byaddr_at(Core()->core()->analysis, offset);
|
||||
if (globalVariable) {
|
||||
globalVariableName = QString(globalVariable->name);
|
||||
globalVariableOffset = globalVariable->addr;
|
||||
}
|
||||
|
||||
if (globalVariable) {
|
||||
ui->nameEdit->setText(globalVariable->name);
|
||||
QString globalVarType = Core()->getGlobalVariableType(globalVariable->name);
|
||||
ui->typeEdit->setText(globalVarType);
|
||||
ui->labelAction->setText(tr("Edit global variable at %1").arg(RzAddressString(offset)));
|
||||
} else {
|
||||
ui->labelAction->setText(tr("Add global variable at %1").arg(RzAddressString(offset)));
|
||||
}
|
||||
|
||||
// Connect slots
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||
&GlobalVariableDialog::buttonBoxAccepted);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this,
|
||||
&GlobalVariableDialog::buttonBoxRejected);
|
||||
}
|
||||
|
||||
GlobalVariableDialog::~GlobalVariableDialog() {}
|
||||
|
||||
void GlobalVariableDialog::buttonBoxAccepted()
|
||||
{
|
||||
QString name = ui->nameEdit->text();
|
||||
QString typ = ui->typeEdit->text();
|
||||
|
||||
if (name.isEmpty()) {
|
||||
if (globalVariableOffset != RVA_INVALID) {
|
||||
// Empty name and global variable exists -> delete the global variable
|
||||
Core()->delGlobalVariable(globalVariableOffset);
|
||||
} else {
|
||||
// GlobalVariable was not existing and we gave an empty name, do nothing
|
||||
}
|
||||
} else {
|
||||
if (globalVariableOffset != RVA_INVALID) {
|
||||
// Name provided and global variable exists -> rename the global variable
|
||||
Core()->modifyGlobalVariable(globalVariableOffset, name, typ);
|
||||
} else {
|
||||
// Name provided and global variable does not exist -> create the global variable
|
||||
Core()->addGlobalVariable(offset, name, typ);
|
||||
}
|
||||
}
|
||||
close();
|
||||
this->setResult(QDialog::Accepted);
|
||||
}
|
||||
|
||||
void GlobalVariableDialog::buttonBoxRejected()
|
||||
{
|
||||
close();
|
||||
this->setResult(QDialog::Rejected);
|
||||
}
|
32
src/dialogs/GlobalVariableDialog.h
Normal file
32
src/dialogs/GlobalVariableDialog.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef GLOBALVARIABLEDIALOG_H
|
||||
#define GLOBALVARIABLEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <memory>
|
||||
#include "core/CutterCommon.h"
|
||||
|
||||
namespace Ui {
|
||||
class GlobalVariableDialog;
|
||||
}
|
||||
|
||||
class GlobalVariableDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlobalVariableDialog(RVA offset, QWidget *parent = nullptr);
|
||||
~GlobalVariableDialog();
|
||||
|
||||
private slots:
|
||||
void buttonBoxAccepted();
|
||||
void buttonBoxRejected();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::GlobalVariableDialog> ui;
|
||||
RVA offset;
|
||||
QString globalVariableName;
|
||||
RVA globalVariableOffset;
|
||||
QString typ;
|
||||
};
|
||||
|
||||
#endif // GLOBALVARIABLEDIALOG_H
|
100
src/dialogs/GlobalVariableDialog.ui
Normal file
100
src/dialogs/GlobalVariableDialog.ui
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GlobalVariableDialog</class>
|
||||
<widget class="QDialog" name="GlobalVariableDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>452</width>
|
||||
<height>121</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Add Global Variable</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelAction">
|
||||
<property name="text">
|
||||
<string>Add global variable at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelName">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nameEdit">
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="typeEdit">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>int</string>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelType">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -3,6 +3,7 @@
|
||||
#include "dialogs/EditInstructionDialog.h"
|
||||
#include "dialogs/CommentsDialog.h"
|
||||
#include "dialogs/FlagDialog.h"
|
||||
#include "dialogs/GlobalVariableDialog.h"
|
||||
#include "dialogs/XrefsDialog.h"
|
||||
#include "dialogs/EditVariablesDialog.h"
|
||||
#include "dialogs/SetToDataDialog.h"
|
||||
@ -34,6 +35,7 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
|
||||
actionAnalyzeFunction(this),
|
||||
actionEditFunction(this),
|
||||
actionRename(this),
|
||||
actionGlobalVar(this),
|
||||
actionSetFunctionVarTypes(this),
|
||||
actionXRefs(this),
|
||||
actionXRefsForVariables(this),
|
||||
@ -83,10 +85,6 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
|
||||
getCommentSequence());
|
||||
addAction(&actionAddComment);
|
||||
|
||||
initAction(&actionRename, tr("Rename or add flag"), SLOT(on_actionRename_triggered()),
|
||||
getRenameSequence());
|
||||
addAction(&actionRename);
|
||||
|
||||
initAction(&actionSetFunctionVarTypes, tr("Re-type Local Variables"),
|
||||
SLOT(on_actionSetFunctionVarTypes_triggered()), getRetypeSequence());
|
||||
addAction(&actionSetFunctionVarTypes);
|
||||
@ -112,6 +110,8 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
|
||||
|
||||
addSeparator();
|
||||
|
||||
addAddAtMenu();
|
||||
|
||||
addSetBaseMenu();
|
||||
|
||||
addSetBitsMenu();
|
||||
@ -166,6 +166,19 @@ QWidget *DisassemblyContextMenu::parentForDialog()
|
||||
return parentWidget();
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::addAddAtMenu()
|
||||
{
|
||||
setAsMenu = addMenu(tr("Add at..."));
|
||||
|
||||
initAction(&actionRename, tr("Rename or add flag"), SLOT(on_actionRename_triggered()),
|
||||
getRenameSequence());
|
||||
setAsMenu->addAction(&actionRename);
|
||||
|
||||
initAction(&actionGlobalVar, tr("Modify or add global variable"),
|
||||
SLOT(on_actionGlobalVar_triggered()), getGlobalVarSequence());
|
||||
setAsMenu->addAction(&actionGlobalVar);
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::addSetBaseMenu()
|
||||
{
|
||||
setBaseMenu = addMenu(tr("Set base of immediate value to.."));
|
||||
@ -479,7 +492,12 @@ void DisassemblyContextMenu::setupRenaming()
|
||||
|
||||
// Now, build the renaming menu and show it
|
||||
buildRenameMenu(tuh);
|
||||
|
||||
auto name = RzAddressString(tuh->offset);
|
||||
actionGlobalVar.setText(tr("Add or change global variable at %1 (used here)").arg(name));
|
||||
|
||||
actionRename.setVisible(true);
|
||||
actionGlobalVar.setVisible(true);
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::aboutToShowSlot()
|
||||
@ -655,6 +673,11 @@ QKeySequence DisassemblyContextMenu::getRenameSequence() const
|
||||
return { Qt::Key_N };
|
||||
}
|
||||
|
||||
QKeySequence DisassemblyContextMenu::getGlobalVarSequence() const
|
||||
{
|
||||
return { Qt::Key_G };
|
||||
}
|
||||
|
||||
QKeySequence DisassemblyContextMenu::getRetypeSequence() const
|
||||
{
|
||||
return { Qt::Key_Y };
|
||||
@ -868,6 +891,18 @@ void DisassemblyContextMenu::on_actionRename_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::on_actionGlobalVar_triggered()
|
||||
{
|
||||
bool ok = false;
|
||||
GlobalVariableDialog dialog(doRenameInfo.addr, parentForDialog());
|
||||
ok = dialog.exec();
|
||||
|
||||
if (ok) {
|
||||
// Rebuild menu in case the user presses the rename shortcut directly before clicking
|
||||
setupRenaming();
|
||||
}
|
||||
}
|
||||
|
||||
void DisassemblyContextMenu::on_actionSetFunctionVarTypes_triggered()
|
||||
{
|
||||
RzAnalysisFunction *fcn = Core()->functionIn(offset);
|
||||
|
@ -45,6 +45,7 @@ private slots:
|
||||
void on_actionAddComment_triggered();
|
||||
void on_actionAnalyzeFunction_triggered();
|
||||
void on_actionRename_triggered();
|
||||
void on_actionGlobalVar_triggered();
|
||||
void on_actionSetFunctionVarTypes_triggered();
|
||||
void on_actionXRefs_triggered();
|
||||
void on_actionXRefsForVariables_triggered();
|
||||
@ -78,6 +79,7 @@ private:
|
||||
QKeySequence getCopySequence() const;
|
||||
QKeySequence getCommentSequence() const;
|
||||
QKeySequence getCopyAddressSequence() const;
|
||||
QKeySequence getGlobalVarSequence() const;
|
||||
QKeySequence getSetToCodeSequence() const;
|
||||
QKeySequence getSetAsStringSequence() const;
|
||||
QKeySequence getSetAsStringAdvanced() const;
|
||||
@ -118,6 +120,7 @@ private:
|
||||
QAction actionXRefs;
|
||||
QAction actionXRefsForVariables;
|
||||
QAction actionDisplayOptions;
|
||||
QAction actionGlobalVar;
|
||||
|
||||
QAction actionDeleteComment;
|
||||
QAction actionDeleteFlag;
|
||||
@ -190,6 +193,7 @@ private:
|
||||
void addSetAsMenu();
|
||||
void addSetToDataMenu();
|
||||
void addEditMenu();
|
||||
void addAddAtMenu();
|
||||
void addBreakpointMenu();
|
||||
void addDebugMenu();
|
||||
|
||||
|
@ -90,6 +90,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main)
|
||||
connect(Core(), &CutterCore::varsChanged, this, &DecompilerWidget::doRefresh);
|
||||
connect(Core(), &CutterCore::functionsChanged, this, &DecompilerWidget::doRefresh);
|
||||
connect(Core(), &CutterCore::flagsChanged, this, &DecompilerWidget::doRefresh);
|
||||
connect(Core(), &CutterCore::globalVarsChanged, this, &DecompilerWidget::doRefresh);
|
||||
connect(Core(), &CutterCore::commentsChanged, this, &DecompilerWidget::refreshIfChanged);
|
||||
connect(Core(), &CutterCore::instructionChanged, this, &DecompilerWidget::refreshIfChanged);
|
||||
connect(Core(), &CutterCore::refreshCodeViews, this, &DecompilerWidget::doRefresh);
|
||||
|
@ -52,6 +52,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
|
||||
connect(Core(), &CutterCore::commentsChanged, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::functionRenamed, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::flagsChanged, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::globalVarsChanged, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::varsChanged, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::instructionChanged, this, &DisassemblerGraphView::refreshView);
|
||||
connect(Core(), &CutterCore::breakpointsChanged, this, &DisassemblerGraphView::refreshView);
|
||||
|
@ -128,6 +128,7 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main)
|
||||
|
||||
connect(Core(), &CutterCore::commentsChanged, this, [this]() { refreshDisasm(); });
|
||||
connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshDisasm()));
|
||||
connect(Core(), SIGNAL(globalVarsChanged()), this, SLOT(refreshDisasm()));
|
||||
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshDisasm()));
|
||||
connect(Core(), &CutterCore::functionRenamed, this, [this]() { refreshDisasm(); });
|
||||
connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshDisasm()));
|
||||
|
228
src/widgets/GlobalsWidget.cpp
Normal file
228
src/widgets/GlobalsWidget.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
#include "GlobalsWidget.h"
|
||||
#include "ui_GlobalsWidget.h"
|
||||
#include "core/MainWindow.h"
|
||||
#include "common/Helpers.h"
|
||||
#include "dialogs/GlobalVariableDialog.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QShortcut>
|
||||
|
||||
GlobalsModel::GlobalsModel(QList<GlobalDescription> *globals, QObject *parent)
|
||||
: AddressableItemModel<QAbstractListModel>(parent), globals(globals)
|
||||
{
|
||||
}
|
||||
|
||||
int GlobalsModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return globals->count();
|
||||
}
|
||||
|
||||
int GlobalsModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return GlobalsModel::ColumnCount;
|
||||
}
|
||||
|
||||
QVariant GlobalsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (index.row() >= globals->count()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const GlobalDescription &global = globals->at(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column()) {
|
||||
case GlobalsModel::AddressColumn:
|
||||
return RzAddressString(global.addr);
|
||||
case GlobalsModel::TypeColumn:
|
||||
return QString(global.type).trimmed();
|
||||
case GlobalsModel::NameColumn:
|
||||
return global.name;
|
||||
case GlobalsModel::CommentColumn:
|
||||
return Core()->getCommentAt(global.addr);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case GlobalsModel::GlobalDescriptionRole:
|
||||
return QVariant::fromValue(global);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant GlobalsModel::headerData(int section, Qt::Orientation, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case GlobalsModel::AddressColumn:
|
||||
return tr("Address");
|
||||
case GlobalsModel::TypeColumn:
|
||||
return tr("Type");
|
||||
case GlobalsModel::NameColumn:
|
||||
return tr("Name");
|
||||
case GlobalsModel::CommentColumn:
|
||||
return tr("Comment");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
RVA GlobalsModel::address(const QModelIndex &index) const
|
||||
{
|
||||
const GlobalDescription &global = globals->at(index.row());
|
||||
return global.addr;
|
||||
}
|
||||
|
||||
QString GlobalsModel::name(const QModelIndex &index) const
|
||||
{
|
||||
const GlobalDescription &global = globals->at(index.row());
|
||||
return global.name;
|
||||
}
|
||||
|
||||
GlobalsProxyModel::GlobalsProxyModel(GlobalsModel *sourceModel, QObject *parent)
|
||||
: AddressableFilterProxyModel(sourceModel, parent)
|
||||
{
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool GlobalsProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(row, 0, parent);
|
||||
auto global = index.data(GlobalsModel::GlobalDescriptionRole).value<GlobalDescription>();
|
||||
|
||||
return qhelpers::filterStringContains(global.name, this);
|
||||
}
|
||||
|
||||
bool GlobalsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
auto leftGlobal = left.data(GlobalsModel::GlobalDescriptionRole).value<GlobalDescription>();
|
||||
auto rightGlobal = right.data(GlobalsModel::GlobalDescriptionRole).value<GlobalDescription>();
|
||||
|
||||
switch (left.column()) {
|
||||
case GlobalsModel::AddressColumn:
|
||||
return leftGlobal.addr < rightGlobal.addr;
|
||||
case GlobalsModel::TypeColumn:
|
||||
return leftGlobal.type < rightGlobal.type;
|
||||
case GlobalsModel::NameColumn:
|
||||
return leftGlobal.name < rightGlobal.name;
|
||||
case GlobalsModel::CommentColumn:
|
||||
return Core()->getCommentAt(leftGlobal.addr) < Core()->getCommentAt(rightGlobal.addr);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GlobalsWidget::editGlobal()
|
||||
{
|
||||
QModelIndex index = ui->treeView->currentIndex();
|
||||
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RVA globalVariableAddress = globalsModel->address(index);
|
||||
|
||||
GlobalVariableDialog dialog(globalVariableAddress, parentWidget());
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void GlobalsWidget::deleteGlobal()
|
||||
{
|
||||
QModelIndex index = ui->treeView->currentIndex();
|
||||
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RVA globalVariableAddress = globalsModel->address(index);
|
||||
Core()->delGlobalVariable(globalVariableAddress);
|
||||
}
|
||||
|
||||
void GlobalsWidget::showGlobalsContextMenu(const QPoint &pt)
|
||||
{
|
||||
QModelIndex index = ui->treeView->indexAt(pt);
|
||||
|
||||
QMenu menu(ui->treeView);
|
||||
|
||||
if (index.isValid()) {
|
||||
menu.addAction(actionEditGlobal);
|
||||
menu.addAction(actionDeleteGlobal);
|
||||
}
|
||||
|
||||
menu.exec(ui->treeView->mapToGlobal(pt));
|
||||
}
|
||||
|
||||
GlobalsWidget::GlobalsWidget(MainWindow *main)
|
||||
: CutterDockWidget(main), ui(new Ui::GlobalsWidget), tree(new CutterTreeWidget(this))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->quickFilterView->setLabelText(tr("Category"));
|
||||
|
||||
setWindowTitle(tr("Globals"));
|
||||
setObjectName("GlobalsWidget");
|
||||
|
||||
// Add status bar which displays the count
|
||||
tree->addStatusBar(ui->verticalLayout);
|
||||
|
||||
// Set single select mode
|
||||
ui->treeView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
// Setup up the model and the proxy model
|
||||
globalsModel = new GlobalsModel(&globals, this);
|
||||
globalsProxyModel = new GlobalsProxyModel(globalsModel, this);
|
||||
ui->treeView->setModel(globalsProxyModel);
|
||||
ui->treeView->sortByColumn(GlobalsModel::AddressColumn, Qt::AscendingOrder);
|
||||
|
||||
// Setup custom context menu
|
||||
connect(ui->treeView, &QWidget::customContextMenuRequested, this,
|
||||
&GlobalsWidget::showGlobalsContextMenu);
|
||||
|
||||
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, globalsProxyModel,
|
||||
&QSortFilterProxyModel::setFilterWildcard);
|
||||
|
||||
connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this,
|
||||
[this] { tree->showItemsNumber(globalsProxyModel->rowCount()); });
|
||||
|
||||
QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this);
|
||||
connect(searchShortcut, &QShortcut::activated, ui->quickFilterView,
|
||||
&ComboQuickFilterView::showFilter);
|
||||
searchShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
|
||||
QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||
connect(clearShortcut, &QShortcut::activated, ui->quickFilterView,
|
||||
&ComboQuickFilterView::clearFilter);
|
||||
clearShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
|
||||
actionEditGlobal = new QAction(tr("Edit Global Variable"), this);
|
||||
actionDeleteGlobal = new QAction(tr("Delete Global Variable"), this);
|
||||
|
||||
connect(actionEditGlobal, &QAction::triggered, [this]() { editGlobal(); });
|
||||
connect(actionDeleteGlobal, &QAction::triggered, [this]() { deleteGlobal(); });
|
||||
|
||||
connect(Core(), &CutterCore::globalVarsChanged, this, &GlobalsWidget::refreshGlobals);
|
||||
connect(Core(), &CutterCore::codeRebased, this, &GlobalsWidget::refreshGlobals);
|
||||
connect(Core(), &CutterCore::refreshAll, this, &GlobalsWidget::refreshGlobals);
|
||||
connect(Core(), &CutterCore::commentsChanged, this,
|
||||
[this]() { qhelpers::emitColumnChanged(globalsModel, GlobalsModel::CommentColumn); });
|
||||
}
|
||||
|
||||
GlobalsWidget::~GlobalsWidget() {}
|
||||
|
||||
void GlobalsWidget::refreshGlobals()
|
||||
{
|
||||
globalsModel->beginResetModel();
|
||||
globals = Core()->getAllGlobals();
|
||||
globalsModel->endResetModel();
|
||||
|
||||
qhelpers::adjustColumns(ui->treeView, GlobalsModel::ColumnCount, 0);
|
||||
}
|
89
src/widgets/GlobalsWidget.h
Normal file
89
src/widgets/GlobalsWidget.h
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef GLOBALSWIDGET_H
|
||||
#define GLOBALSWIDGET_H
|
||||
|
||||
#include <memory>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "core/Cutter.h"
|
||||
#include "CutterDockWidget.h"
|
||||
#include "widgets/ListDockWidget.h"
|
||||
|
||||
class MainWindow;
|
||||
class QTreeWidget;
|
||||
class GlobalsWidget;
|
||||
|
||||
namespace Ui {
|
||||
class GlobalsWidget;
|
||||
}
|
||||
|
||||
class MainWindow;
|
||||
class QTreeWidgetItem;
|
||||
|
||||
class GlobalsModel : public AddressableItemModel<QAbstractListModel>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend GlobalsWidget;
|
||||
|
||||
private:
|
||||
QList<GlobalDescription> *globals;
|
||||
|
||||
public:
|
||||
enum Column { AddressColumn = 0, TypeColumn, NameColumn, CommentColumn, ColumnCount };
|
||||
enum Role { GlobalDescriptionRole = Qt::UserRole };
|
||||
|
||||
GlobalsModel(QList<GlobalDescription> *exports, QObject *parent = nullptr);
|
||||
|
||||
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;
|
||||
|
||||
RVA address(const QModelIndex &index) const override;
|
||||
QString name(const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
class GlobalsProxyModel : public AddressableFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GlobalsProxyModel(GlobalsModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int row, const QModelIndex &parent) const override;
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
};
|
||||
|
||||
class GlobalsWidget : public CutterDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GlobalsWidget(MainWindow *main);
|
||||
~GlobalsWidget();
|
||||
|
||||
private slots:
|
||||
void refreshGlobals();
|
||||
|
||||
void showGlobalsContextMenu(const QPoint &pt);
|
||||
|
||||
void editGlobal();
|
||||
void deleteGlobal();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::GlobalsWidget> ui;
|
||||
|
||||
QList<GlobalDescription> globals;
|
||||
GlobalsModel *globalsModel;
|
||||
GlobalsProxyModel *globalsProxyModel;
|
||||
CutterTreeWidget *tree;
|
||||
|
||||
QAction *actionEditGlobal;
|
||||
QAction *actionDeleteGlobal;
|
||||
};
|
||||
|
||||
#endif // GLOBALSWIDGET_H
|
107
src/widgets/GlobalsWidget.ui
Normal file
107
src/widgets/GlobalsWidget.ui
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GlobalsWidget</class>
|
||||
<widget class="QDockWidget" name="GlobalsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Global Variables</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="CutterTreeView" name="treeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">CutterTreeView::item
|
||||
{
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ComboQuickFilterView" name="quickFilterView" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<action name="actionEditGlobal">
|
||||
<property name="text">
|
||||
<string>Edit Global Variable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Edit Global Variable</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDeleteGlobal">
|
||||
<property name="text">
|
||||
<string>Delete Global Variable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete Global Variable</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>CutterTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>widgets/CutterTreeView.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ComboQuickFilterView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/ComboQuickFilterView.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -50,6 +50,7 @@ VisualNavbar::VisualNavbar(MainWindow *main, QWidget *parent)
|
||||
connect(Core(), &CutterCore::refreshAll, this, &VisualNavbar::fetchAndPaintData);
|
||||
connect(Core(), &CutterCore::functionsChanged, this, &VisualNavbar::fetchAndPaintData);
|
||||
connect(Core(), &CutterCore::flagsChanged, this, &VisualNavbar::fetchAndPaintData);
|
||||
connect(Core(), &CutterCore::globalVarsChanged, this, &VisualNavbar::fetchAndPaintData);
|
||||
|
||||
graphicsScene = new QGraphicsScene(this);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user