cutter/src/MainWindow.cpp
Nics c4e8a1c178 Small refactor for the widgets of Cutter (#405)
* Small refactor for the widgets of Cutter

This refactor include the following :

* Creation of a new class
Creation of a new class, named CutterWidget, that inherits from QDockWidget and
is used to represent all of the widgets of the main window.
The goal of this class is to regroup all the behaviour shared by the widgets of
Cutter.

For example : in the constructor, instructions corresponding of those
present in the macro **ADD_DOCK** (in MainWindow.cpp) are executed.
This was made because I think that the macro **ADD_DOCK** which is used
to construct the widgets does not take advantage of the object structure.

* Ensure that every widget has a parent
Some widgets were created using the constructor QDockWidget, but using
**nullptr** (default) as argument, thus they haven't got any parent.

The constructor of a CutterWidget takes as argument the MainWindow and an
action (optional) and calls the constructor of QDockWidget with the main
window as argument. This is valid under the assumption that it is mandatory
for every widget to have the main window as a parent.

* Constructors removal
The constructors of some widgets are not used anywhere and does not seem not
fullfill any current usecase. They were removed.

* Renaming CutterWidget to CutterDockWidget
2018-03-16 22:46:57 +01:00

773 lines
21 KiB
C++

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "dialogs/CommentsDialog.h"
#include "dialogs/AboutDialog.h"
#include "dialogs/RenameDialog.h"
#include "dialogs/preferences/PreferencesDialog.h"
#include "utils/Helpers.h"
#include <QApplication>
#include <QComboBox>
#include <QCompleter>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFile>
#include <QFileDialog>
#include <QFont>
#include <QFontDialog>
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QMessageBox>
#include <QProcess>
#include <QPropertyAnimation>
#include <QScrollBar>
#include <QSettings>
#include <QShortcut>
#include <QStringListModel>
#include <QStyledItemDelegate>
#include <QStyleFactory>
#include <QTextCursor>
#include <QtGlobal>
#include <QToolButton>
#include <QToolTip>
#include <QTreeWidgetItem>
#include <QSvgRenderer>
#include "utils/Highlighter.h"
#include "utils/HexAsciiHighlighter.h"
#include "utils/Helpers.h"
#include "utils/SvgIconEngine.h"
#include "dialogs/NewFileDialog.h"
#include "widgets/DisassemblerGraphView.h"
#include "widgets/GraphWidget.h"
#include "widgets/FunctionsWidget.h"
#include "widgets/SectionsWidget.h"
#include "widgets/CommentsWidget.h"
#include "widgets/ImportsWidget.h"
#include "widgets/ExportsWidget.h"
#include "widgets/TypesWidget.h"
#include "widgets/SearchWidget.h"
#include "widgets/SymbolsWidget.h"
#include "widgets/StringsWidget.h"
#include "widgets/SectionsDock.h"
#include "widgets/RelocsWidget.h"
#include "widgets/FlagsWidget.h"
#include "widgets/VisualNavbar.h"
#include "widgets/Dashboard.h"
#include "widgets/Sidebar.h"
#include "widgets/SdbDock.h"
#include "widgets/Omnibar.h"
#include "widgets/ConsoleWidget.h"
#include "dialogs/OptionsDialog.h"
#include "widgets/EntrypointWidget.h"
#include "dialogs/SaveProjectDialog.h"
#include "widgets/ClassesWidget.h"
#include "widgets/ResourcesWidget.h"
#include "widgets/VTablesWidget.h"
#include "widgets/JupyterWidget.h"
// graphics
#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <cassert>
static void registerCustomFonts()
{
int ret = QFontDatabase::addApplicationFont(":/fonts/Anonymous Pro.ttf");
assert(-1 != ret && "unable to register Anonymous Pro.ttf");
ret = QFontDatabase::addApplicationFont(":/fonts/Inconsolata-Regular.ttf");
assert(-1 != ret && "unable to register Inconsolata-Regular.ttf");
// Do not issue a warning in release
Q_UNUSED(ret)
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
core(Core()),
ui(new Ui::MainWindow)
{
panelLock = false;
tabsOnTop = false;
configuration = Config();
}
MainWindow::~MainWindow()
{
}
void MainWindow::initUI()
{
ui->setupUi(this);
registerCustomFonts();
/*
* Toolbar
*/
// Hide central tab widget tabs
QTabBar *centralbar = ui->centralTabWidget->tabBar();
centralbar->setVisible(false);
// Sepparator between undo/redo and goto lineEdit
QWidget *spacer3 = new QWidget();
spacer3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
spacer3->setMinimumSize(20, 20);
spacer3->setMaximumWidth(300);
ui->mainToolBar->addWidget(spacer3);
// Omnibar LineEdit
this->omnibar = new Omnibar(this);
ui->mainToolBar->addWidget(this->omnibar);
// Add special separators to the toolbar that expand to separate groups of elements
QWidget *spacer2 = new QWidget();
spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
spacer2->setMinimumSize(10, 10);
spacer2->setMaximumWidth(300);
ui->mainToolBar->addWidget(spacer2);
// Separator between back/forward and undo/redo buttons
QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
spacer->setMinimumSize(20, 20);
ui->mainToolBar->addWidget(spacer);
// Visual navigation tool bar
this->visualNavbar = new VisualNavbar(this);
this->visualNavbar->setMovable(false);
addToolBarBreak(Qt::TopToolBarArea);
addToolBar(visualNavbar);
/*
* Dock Widgets
*/
dockWidgets.reserve(20);
disassemblyDock = new DisassemblyWidget(this, ui->actionDisassembly);
sidebarDock = new SidebarWidget(this, ui->actionSidebar);
hexdumpDock = new HexdumpWidget(this, ui->actionHexdump);
pseudocodeDock = new PseudocodeWidget(this, ui->actionPseudocode);
consoleDock = new ConsoleWidget(this, ui->actionConsole);
// Add graph view as dockable
graphDock = new GraphWidget(this, ui->actionGraph);
// Hide centralWidget as we do not need it
ui->centralWidget->hide();
sectionsDock = new SectionsDock(this, ui->actionSections);
disassemblyDock = new DisassemblyWidget(this, ui->actionDisassembly);
entrypointDock = new EntrypointWidget(this, ui->actionEntrypoints);
functionsDock = new FunctionsWidget(this, ui->actionFunctions);
importsDock = new ImportsWidget(this, ui->actionImports);
exportsDock = new ExportsWidget(this, ui->actionExports);
typesDock = new TypesWidget(this, ui->actionTypes);
searchDock = new SearchWidget(this, ui->actionSearch);
symbolsDock = new SymbolsWidget(this, ui->actionSymbols);
relocsDock = new RelocsWidget(this, ui->actionRelocs);
commentsDock = new CommentsWidget(this, ui->actionComments);
stringsDock = new StringsWidget(this, ui->actionStrings);
flagsDock = new FlagsWidget(this, ui->actionFlags);
#ifdef CUTTER_ENABLE_JUPYTER
jupyterDock = new JupyterWidget(this, ui->actionJupyter);
#else
ui->actionJupyter->setEnabled(false);
ui->actionJupyter->setVisible(false);
#endif
dashboardDock = new Dashboard(this, ui->actionDashboard);
disassemblyDock = new DisassemblyWidget(this, ui->actionDisassembly);
sdbDock = new SdbDock(this, ui->actionSDBBrowser);
classesDock = new ClassesWidget(this, ui->actionClasses);
resourcesDock = new ResourcesWidget(this, ui->actionResources);
vTablesDock = new VTablesWidget(this, ui->actionVTables);
// Set up dock widgets default layout
resetToDefaultLayout();
// Restore saved settings
this->readSettings();
// TODO: Allow the user to select this option visually in the GUI settings
// Adjust the DockWidget areas
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
//setCorner( Qt::TopRightCorner, Qt::RightDockWidgetArea );
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
//setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea );
/*
* Some global shortcuts
*/
// Period goes to command entry
QShortcut *cmd_shortcut = new QShortcut(QKeySequence(Qt::Key_Period), this);
connect(cmd_shortcut, SIGNAL(activated()), consoleDock, SLOT(focusInputLineEdit()));
// G and S goes to goto entry
QShortcut *goto_shortcut = new QShortcut(QKeySequence(Qt::Key_G), this);
connect(goto_shortcut, SIGNAL(activated()), this->omnibar, SLOT(setFocus()));
QShortcut *seek_shortcut = new QShortcut(QKeySequence(Qt::Key_S), this);
connect(seek_shortcut, SIGNAL(activated()), this->omnibar, SLOT(setFocus()));
QShortcut *refresh_shortcut = new QShortcut(QKeySequence(QKeySequence::Refresh), this);
connect(refresh_shortcut, SIGNAL(activated()), this, SLOT(refreshAll()));
connect(core, SIGNAL(projectSaved(const QString &)), this, SLOT(projectSaved(const QString &)));
}
void MainWindow::openNewFile(const QString &fn, int analLevel, QList<QString> advancedOptions)
{
setFilename(fn);
/* Reset config */
core->resetDefaultAsmOptions();
/* Prompt to load filename.r2 script */
QString script = QString("%1.r2").arg(this->filename);
if (r_file_exists(script.toStdString().data())) {
QMessageBox mb;
mb.setWindowTitle(tr("Script loading"));
mb.setText(tr("Do you want to load the '%1' script?").arg(script));
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
if (mb.exec() == QMessageBox::Yes) {
core->loadScript(script);
}
}
/* Show analysis options dialog */
displayAnalysisOptionsDialog(analLevel, advancedOptions);
}
void MainWindow::displayNewFileDialog()
{
NewFileDialog *n = new NewFileDialog();
newFileDialog = n;
n->setAttribute(Qt::WA_DeleteOnClose);
n->show();
}
void MainWindow::closeNewFileDialog()
{
if (newFileDialog) {
newFileDialog->close();
}
newFileDialog = nullptr;
}
void MainWindow::displayAnalysisOptionsDialog(int analLevel, QList<QString> advancedOptions)
{
OptionsDialog *o = new OptionsDialog(this);
o->setAttribute(Qt::WA_DeleteOnClose);
o->show();
if (analLevel >= 0)
{
o->setupAndStartAnalysis(analLevel, advancedOptions);
}
}
void MainWindow::openProject(const QString &project_name)
{
QString filename = core->cmd("Pi " + project_name);
setFilename(filename.trimmed());
core->openProject(project_name);
initUI();
finalizeOpen();
}
void MainWindow::finalizeOpen()
{
core->getOpcodes();
// Set settings to override any incorrect saved in the project
core->setSettings();
addOutput(tr(" > Populating UI"));
// FIXME: initialization order frakup. the next line is needed so that the
// comments widget displays the function names.
core->cmd("fs sections");
refreshAll();
addOutput(tr(" > Finished, happy reversing :)"));
// Add fortune message
addOutput("\n" + core->cmd("fo"));
//previewDock->setWindowTitle("entry0");
showMaximized();
}
bool MainWindow::saveProject(bool quit)
{
QString projectName = core->getConfig("prj.name");
if (projectName.isEmpty())
{
return saveProjectAs(quit);
}
else
{
core->saveProject(projectName);
return true;
}
}
bool MainWindow::saveProjectAs(bool quit)
{
SaveProjectDialog dialog(quit, this);
int result = dialog.exec();
return !quit || result != SaveProjectDialog::Rejected;
}
void MainWindow::refreshOmniBar(const QStringList &flags)
{
omnibar->refresh(flags);
}
void MainWindow::setFilename(const QString &fn)
{
// Add file name to window title
this->filename = fn;
this->setWindowTitle(APPNAME" - " + fn);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
QMessageBox::StandardButton ret = QMessageBox::question(this, APPNAME,
tr("Do you really want to exit?\nSave your project before closing!"),
(QMessageBox::StandardButtons)(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel));
if (ret == QMessageBox::Save)
{
if (saveProject(true))
{
saveSettings();
}
QMainWindow::closeEvent(event);
}
else if (ret == QMessageBox::Discard)
{
saveSettings();
QMainWindow::closeEvent(event);
}
else
{
event->ignore();
}
}
void MainWindow::readSettings()
{
QSettings settings;
QByteArray geo = settings.value("geometry", QByteArray()).toByteArray();
restoreGeometry(geo);
QByteArray state = settings.value("state", QByteArray()).toByteArray();
restoreState(state);
this->responsive = settings.value("responsive").toBool();
panelLock = settings.value("panelLock").toBool();
setPanelLock();
tabsOnTop = settings.value("tabsOnTop").toBool();
setTabLocation();
updateDockActionsChecked();
}
void MainWindow::saveSettings()
{
QSettings settings;
settings.setValue("geometry", saveGeometry());
settings.setValue("size", size());
settings.setValue("pos", pos());
settings.setValue("state", saveState());
settings.setValue("panelLock", panelLock);
settings.setValue("tabsOnTop", tabsOnTop);
}
void MainWindow::setPanelLock()
{
if (panelLock)
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
ui->actionLock->setChecked(false);
}
else
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::AllDockWidgetFeatures);
}
ui->actionLock->setChecked(true);
}
}
void MainWindow::setTabLocation()
{
if (tabsOnTop)
{
ui->centralTabWidget->setTabPosition(QTabWidget::North);
this->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
ui->actionTabs_on_Top->setChecked(true);
}
else
{
ui->centralTabWidget->setTabPosition(QTabWidget::South);
this->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::South);
ui->actionTabs_on_Top->setChecked(false);
}
}
void MainWindow::refreshAll()
{
Core()->triggerRefreshAll();
}
void MainWindow::lockUnlock_Docks(bool what)
{
if (what)
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
}
else
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::AllDockWidgetFeatures);
}
}
}
void MainWindow::restoreDocks()
{
// In the upper half the functions are the first widget
addDockWidget(Qt::TopDockWidgetArea, functionsDock);
// Function | Dashboard | Sidebar
splitDockWidget(functionsDock, dashboardDock, Qt::Horizontal);
splitDockWidget(dashboardDock, sidebarDock, Qt::Horizontal);
// In the lower half the console is the first widget
addDockWidget(Qt::BottomDockWidgetArea, consoleDock);
// Console | Sections
splitDockWidget(consoleDock, sectionsDock, Qt::Horizontal);
// Tabs for center (must be applied after splitDockWidget())
tabifyDockWidget(sectionsDock, commentsDock);
tabifyDockWidget(dashboardDock, disassemblyDock);
tabifyDockWidget(dashboardDock, graphDock);
tabifyDockWidget(dashboardDock, hexdumpDock);
tabifyDockWidget(dashboardDock, pseudocodeDock);
tabifyDockWidget(dashboardDock, entrypointDock);
tabifyDockWidget(dashboardDock, flagsDock);
tabifyDockWidget(dashboardDock, stringsDock);
tabifyDockWidget(dashboardDock, relocsDock);
tabifyDockWidget(dashboardDock, importsDock);
tabifyDockWidget(dashboardDock, exportsDock);
tabifyDockWidget(dashboardDock, typesDock);
tabifyDockWidget(dashboardDock, searchDock);
tabifyDockWidget(dashboardDock, symbolsDock);
tabifyDockWidget(dashboardDock, classesDock);
tabifyDockWidget(dashboardDock, resourcesDock);
tabifyDockWidget(dashboardDock, vTablesDock);
#ifdef CUTTER_ENABLE_JUPYTER
tabifyDockWidget(dashboardDock, jupyterDock);
#endif
updateDockActionsChecked();
}
void MainWindow::hideAllDocks()
{
for (auto w : dockWidgets)
{
removeDockWidget(w);
}
updateDockActionsChecked();
}
void MainWindow::updateDockActionsChecked()
{
for(auto i=dockWidgetActions.constBegin(); i!=dockWidgetActions.constEnd(); i++)
{
i.key()->setChecked(!i.value()->isHidden());
}
}
void MainWindow::showDefaultDocks()
{
const QList<QDockWidget *> defaultDocks = { sectionsDock,
entrypointDock,
functionsDock,
commentsDock,
stringsDock,
consoleDock,
importsDock,
symbolsDock,
graphDock,
disassemblyDock,
sidebarDock,
hexdumpDock,
pseudocodeDock,
dashboardDock,
#ifdef CUTTER_ENABLE_JUPYTER
jupyterDock
#endif
};
for (auto w : dockWidgets)
{
if (defaultDocks.contains(w))
{
w->show();
}
}
updateDockActionsChecked();
}
void MainWindow::resetToDefaultLayout()
{
hideAllDocks();
restoreDocks();
showDefaultDocks();
dashboardDock->raise();
// ugly workaround to set the default widths of functions and sidebar docks
// if anyone finds a way to do this cleaner that also works, feel free to change it!
auto restoreFunctionDock = qhelpers::forceWidth(functionsDock->widget(), 300);
auto restoreSidebarDock = qhelpers::forceWidth(sidebarDock->widget(), 300);
qApp->processEvents();
restoreFunctionDock.restoreWidth(functionsDock->widget());
restoreSidebarDock.restoreWidth(sidebarDock->widget());
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
}
void MainWindow::addOutput(const QString &msg)
{
consoleDock->addOutput(msg);
}
void MainWindow::addDebugOutput(const QString &msg)
{
printf("debug output: %s\n", msg.toLocal8Bit().constData());
consoleDock->addDebugOutput(msg);
}
void MainWindow::on_actionLock_triggered()
{
panelLock = !panelLock;
setPanelLock();
}
void MainWindow::on_actionLockUnlock_triggered()
{
if (ui->actionLockUnlock->isChecked())
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
ui->actionLockUnlock->setIcon(QIcon(":/lock"));
}
else
{
foreach (QDockWidget *dockWidget, findChildren<QDockWidget *>())
{
dockWidget->setFeatures(QDockWidget::AllDockWidgetFeatures);
}
ui->actionLockUnlock->setIcon(QIcon(":/unlock"));
}
}
void MainWindow::on_actionFunctionsRename_triggered()
{
RenameDialog *r = new RenameDialog(this);
// Get function based on click position
//r->setFunctionName(fcn_name);
r->open();
}
void MainWindow::on_actionDefault_triggered()
{
resetToDefaultLayout();
}
void MainWindow::on_actionNew_triggered()
{
on_actionOpen_triggered();
}
void MainWindow::on_actionSave_triggered()
{
saveProject();
}
void MainWindow::on_actionSaveAs_triggered()
{
saveProjectAs();
}
void MainWindow::on_actionRun_Script_triggered()
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setViewMode(QFileDialog::Detail);
dialog.setDirectory(QDir::home());
QString fileName;
fileName = dialog.getOpenFileName(this, tr("Select radare2 script"));
if (!fileName.length()) //cancel was pressed
return;
this->core->cmd(". " + fileName);
}
void MainWindow::on_actionOpen_triggered()
{
QProcess process(this);
process.setEnvironment(QProcess::systemEnvironment());
process.startDetached(qApp->applicationFilePath());
}
void MainWindow::toggleResponsive(bool maybe)
{
this->responsive = maybe;
// Save options in settings
QSettings settings;
settings.setValue("responsive", this->responsive);
}
void MainWindow::on_actionTabs_on_Top_triggered()
{
this->on_actionTabs_triggered();
}
void MainWindow::on_actionReset_settings_triggered()
{
QMessageBox::StandardButton ret =
(QMessageBox::StandardButton)QMessageBox::question(this, APPNAME,
tr("Do you really want to clear all settings?"),
QMessageBox::Ok | QMessageBox::Cancel);
if (ret == QMessageBox::Ok)
{
Config()->resetAll();
}
}
void MainWindow::on_actionQuit_triggered()
{
close();
}
void MainWindow::on_actionBackward_triggered()
{
Core()->seekPrev();
}
void MainWindow::on_actionForward_triggered()
{
Core()->seekNext();
}
void MainWindow::on_actionUndoSeek_triggered()
{
Core()->seekPrev();
}
void MainWindow::on_actionRedoSeek_triggered()
{
Core()->seekNext();
}
void MainWindow::on_actionDisasAdd_comment_triggered()
{
CommentsDialog *c = new CommentsDialog(this);
c->exec();
delete c;
}
void MainWindow::on_actionRefresh_contents_triggered()
{
refreshAll();
}
void MainWindow::on_actionPreferences_triggered()
{
auto dialog = new PreferencesDialog(this);
dialog->show();
}
void MainWindow::on_actionTabs_triggered()
{
tabsOnTop = !tabsOnTop;
setTabLocation();
}
void MainWindow::on_actionAbout_triggered()
{
AboutDialog *a = new AboutDialog(this);
a->open();
}
void MainWindow::on_actionRefresh_Panels_triggered()
{
this->refreshAll();
}
void MainWindow::on_actionAnalyze_triggered()
{
displayAnalysisOptionsDialog(-1, QList<QString>());
}
void MainWindow::on_actionImportPDB_triggered()
{
QFileDialog dialog(this);
dialog.setWindowTitle(tr("Select PDB file"));
dialog.setNameFilters({ tr("PDB file (*.pdb)"), tr("All files (*)") });
if (!dialog.exec())
{
return;
}
QString pdbFile = dialog.selectedFiles().first();
if (!pdbFile.isEmpty())
{
Core()->loadPDB(pdbFile);
addOutput(tr("%1 loaded.").arg(pdbFile));
}
}
void MainWindow::projectSaved(const QString &name)
{
addOutput(tr("Project saved: ") + name);
}
void MainWindow::addToDockWidgetList(QDockWidget *dockWidget) {
this->dockWidgets.push_back(dockWidget);
}
void MainWindow::addDockWidgetAction(QDockWidget *dockWidget, QAction *action) {
this->dockWidgetActions[action] = dockWidget;
}