Add UI for managing layouts (#2211)

* Add dialog for deleting and renaming layouts.

* Add documentation.

Co-authored-by: Itay Cohen
This commit is contained in:
karliss 2020-05-24 01:12:32 +03:00 committed by GitHub
parent 6f75fa1a71
commit 0ea5d6fa4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 255 additions and 29 deletions

View File

@ -41,6 +41,7 @@ release = '1.10.3'
extensions = [ extensions = [
'breathe', 'breathe',
'recommonmark', 'recommonmark',
'sphinx.ext.autosectionlabel'
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -73,6 +74,9 @@ exclude_patterns = ['_build']
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'rainbow_dash' pygments_style = 'rainbow_dash'
autosectionlabel_prefix_document = True
# -- Options for Breathe ----------------------------------------------------- # -- Options for Breathe -----------------------------------------------------
breathe_projects = { 'cutter': '../doxygen-out/xml' } breathe_projects = { 'cutter': '../doxygen-out/xml' }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -13,7 +13,7 @@ Refresh contents
Reset to default layout Reset to default layout
---------------------------------------- ----------------------------------------
**Description:** Reset the current layout to the default layout provided by Cutter. **Description:** Reset the current :doc:`layout</user-docs/preferences/layout>` to the default layout provided by Cutter.
**Steps:** View -> Reset to default layout **Steps:** View -> Reset to default layout
@ -67,14 +67,20 @@ Reset Zoom
**Shortcut:** :kbd:`Ctrl` + :kbd:`=` **Shortcut:** :kbd:`Ctrl` + :kbd:`=`
Manage layouts
----------------------------------------
**Description:** Rename and delete saved :doc:`layouts</user-docs/preferences/layout>`.
**Steps:** View -> Manage layouts , select layout, choose command
Save layout Save layout
---------------------------------------- ----------------------------------------
**Description:** Save the current layout with a given name. A layout includes the set of currently opened widgets, their position, and some properties. **Description:** Save the current :doc:`layout</user-docs/preferences/layout>` with a given name. A layout includes the set of currently opened widgets, their position, and some properties.
**Steps:** View -> Save Layout , enter a layout name in the dialog. **Steps:** View -> Save Layout , enter a layout name in the dialog.
Layouts Layouts
---------------------------------------- ----------------------------------------
**Description:** Load the settings from the selected layout into the current layout. Loading a layout will not cause it to automatically be modified. To do that you must use the `Save layout`_ command. **Description:** Load the settings from the selected :doc:`layout</user-docs/preferences/layout>` into the current layout. Loading a layout will not cause it to automatically be modified. To do that you must use the `Save layout`_ command.
**Steps:** View -> Layouts -> layout name **Steps:** View -> Layouts -> layout name

View File

@ -1,11 +1,12 @@
Preferences Configuration
===================== =====================
This part of the documentation will provide the reader with information about different preferences available. This part of the documentation will provide the reader with information about different configuration options available.
Preferences can be opened by clicking ``Edit -> Preferences``. Most configuration is done using the Preferences dialog. It can be opened by clicking :ref:`Edit -> Preferences<user-docs/menus/menu-bar/edit-menu:Preferences>`.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
:glob: :glob:
preferences/initialization-script preferences/initialization-script
preferences/* preferences/layout
preferences/*

View File

@ -0,0 +1,21 @@
Layout
======
The set of currently opened widgets, their placement, and some properties is grouped into layouts.
Cutter will automatically restore the last layout state when reopening Cutter. Last debug and normal layouts are stored separately.
You can :ref:`save<user-docs/menus/menu-bar/view-menu:Save Layout>` multiple named layouts for different use cases.
Use :doc:`../menus/menu-bar/view-menu` to :ref:`save<user-docs/menus/menu-bar/view-menu:Save Layout>`,
:ref:`load<user-docs/menus/menu-bar/view-menu:Layouts>` or :ref:`user-docs/menus/menu-bar/view-menu:Reset to default layout`.
A named layout is never automatically modified. To modify a previously saved layout, instead of entering a new name, select
an existing layout from list in Save Layout dialog.
Layout manager
-----------------------------------
.. image:: ../../images/layout_manager.png
:alt: Layout manager dialog
**Description:** Layout manager allows renaming and deleting saved layouts.
**Steps to open:** :doc:`View<../menus/menu-bar/view-menu>` -> :ref:`user-docs/menus/menu-bar/view-menu:Manage Layouts`

View File

@ -422,7 +422,9 @@ SOURCES += \
dialogs/MultitypeFileSaveDialog.cpp \ dialogs/MultitypeFileSaveDialog.cpp \
widgets/BoolToggleDelegate.cpp \ widgets/BoolToggleDelegate.cpp \
common/IOModesController.cpp \ common/IOModesController.cpp \
common/SettingsUpgrade.cpp common/SettingsUpgrade.cpp \
dialogs/LayoutManager.cpp \
common/CutterLayout.cpp
GRAPHVIZ_SOURCES = \ GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp widgets/GraphvizLayout.cpp
@ -570,7 +572,9 @@ HEADERS += \
dialogs/MultitypeFileSaveDialog.h \ dialogs/MultitypeFileSaveDialog.h \
widgets/BoolToggleDelegate.h \ widgets/BoolToggleDelegate.h \
common/IOModesController.h \ common/IOModesController.h \
common/SettingsUpgrade.h common/SettingsUpgrade.h \
dialogs/LayoutManager.h \
common/CutterLayout.h
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h
@ -637,7 +641,8 @@ FORMS += \
dialogs/LinkTypeDialog.ui \ dialogs/LinkTypeDialog.ui \
widgets/ColorPicker.ui \ widgets/ColorPicker.ui \
dialogs/preferences/ColorThemeEditDialog.ui \ dialogs/preferences/ColorThemeEditDialog.ui \
widgets/ListDockWidget.ui widgets/ListDockWidget.ui \
dialogs/LayoutManager.ui
RESOURCES += \ RESOURCES += \
resources.qrc \ resources.qrc \

View File

@ -0,0 +1,8 @@
#include "CutterLayout.h"
using namespace Cutter;
bool Cutter::isBuiltinLayoutName(const QString &name)
{
return name == LAYOUT_DEFAULT || name == LAYOUT_DEBUG;
}

25
src/common/CutterLayout.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef CUTTER_LAYOUT_H
#define CUTTER_LAYOUT_H
#include <QByteArray>
#include <QMap>
#include <QString>
#include <QVariantMap>
namespace Cutter
{
struct CutterLayout
{
QByteArray geometry;
QByteArray state;
QMap<QString, QVariantMap> viewProperties;
};
const QString LAYOUT_DEFAULT = "Default";
const QString LAYOUT_DEBUG = "Debug";
bool isBuiltinLayoutName(const QString &name);
}
#endif

View File

@ -26,6 +26,7 @@
#include "dialogs/preferences/PreferencesDialog.h" #include "dialogs/preferences/PreferencesDialog.h"
#include "dialogs/MapFileDialog.h" #include "dialogs/MapFileDialog.h"
#include "dialogs/AsyncTaskDialog.h" #include "dialogs/AsyncTaskDialog.h"
#include "dialogs/LayoutManager.h"
// Widgets Headers // Widgets Headers
#include "widgets/DisassemblerGraphView.h" #include "widgets/DisassemblerGraphView.h"
@ -114,12 +115,7 @@
template<class T> template<class T>
T *getNewInstance(MainWindow *m) { return new T(m); } T *getNewInstance(MainWindow *m) { return new T(m); }
static const QString LAYOUT_DEFAULT = "Default"; using namespace Cutter;
static const QString LAYOUT_DEBUG = "Debug";
static bool isBuiltinLayoutName(const QString &name) {
return name == LAYOUT_DEFAULT || name == LAYOUT_DEBUG;
}
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
@ -235,6 +231,7 @@ void MainWindow::initUI()
}); });
connect(ui->actionSaveLayout, &QAction::triggered, this, &MainWindow::saveNamedLayout); connect(ui->actionSaveLayout, &QAction::triggered, this, &MainWindow::saveNamedLayout);
connect(ui->actionManageLayouts, &QAction::triggered, this, &MainWindow::manageLayouts);
/* Setup plugins interfaces */ /* Setup plugins interfaces */
for (auto &plugin : Plugins()->getPlugins()) { for (auto &plugin : Plugins()->getPlugins()) {
@ -1110,11 +1107,14 @@ void MainWindow::saveNamedLayout()
{ {
bool ok = false; bool ok = false;
QString name; QString name;
QStringList names = layouts.keys();
names.removeAll(LAYOUT_DEBUG);
names.removeAll(LAYOUT_DEFAULT);
while (name.isEmpty() || isBuiltinLayoutName(name)) { while (name.isEmpty() || isBuiltinLayoutName(name)) {
if (ok) { if (ok) {
QMessageBox::warning(this, tr("Save layout error"), tr("'%1' is not a valid name.").arg(name)); QMessageBox::warning(this, tr("Save layout error"), tr("'%1' is not a valid name.").arg(name));
} }
name = QInputDialog::getText(this, tr("Save layout"), tr("Enter name"), QLineEdit::Normal, {}, &ok); name = QInputDialog::getItem(this, tr("Save layout"), tr("Enter name"), names, -1, true, &ok);
if (!ok) { if (!ok) {
return; return;
} }
@ -1124,6 +1124,13 @@ void MainWindow::saveNamedLayout()
saveSettings(); saveSettings();
} }
void MainWindow::manageLayouts()
{
LayoutManager layoutManger(layouts, this);
layoutManger.exec();
updateLayoutsMenu();
}
void MainWindow::addWidget(CutterDockWidget *widget) void MainWindow::addWidget(CutterDockWidget *widget)
{ {
dockWidgets.push_back(widget); dockWidgets.push_back(widget);

View File

@ -7,6 +7,7 @@
#include "common/Configuration.h" #include "common/Configuration.h"
#include "common/InitialOptions.h" #include "common/InitialOptions.h"
#include "common/IOModesController.h" #include "common/IOModesController.h"
#include "common/CutterLayout.h"
#include "MemoryDockWidget.h" #include "MemoryDockWidget.h"
#include <memory> #include <memory>
@ -56,13 +57,6 @@ namespace Ui {
class MainWindow; class MainWindow;
} }
struct CutterLayout
{
QByteArray geometry;
QByteArray state;
QMap<QString, QVariantMap> viewProperties;
};
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
@ -279,7 +273,7 @@ private:
QMenu *disassemblyContextMenuExtensions = nullptr; QMenu *disassemblyContextMenuExtensions = nullptr;
QMenu *addressableContextMenuExtensions = nullptr; QMenu *addressableContextMenuExtensions = nullptr;
QMap<QString, CutterLayout> layouts; QMap<QString, Cutter::CutterLayout> layouts;
void initUI(); void initUI();
void initToolBar(); void initToolBar();
@ -287,10 +281,10 @@ private:
void initBackForwardMenu(); void initBackForwardMenu();
void displayInitialOptionsDialog(const InitialOptions &options = InitialOptions(), bool skipOptionsDialog = false); void displayInitialOptionsDialog(const InitialOptions &options = InitialOptions(), bool skipOptionsDialog = false);
CutterLayout getViewLayout(); Cutter::CutterLayout getViewLayout();
CutterLayout getViewLayout(const QString &name); Cutter::CutterLayout getViewLayout(const QString &name);
void setViewLayout(const CutterLayout &layout); void setViewLayout(const Cutter::CutterLayout &layout);
void loadLayouts(QSettings &settings); void loadLayouts(QSettings &settings);
void saveLayouts(QSettings &settings); void saveLayouts(QSettings &settings);
@ -313,6 +307,7 @@ private:
void updateHistoryMenu(QMenu *menu, bool redo = false); void updateHistoryMenu(QMenu *menu, bool redo = false);
void updateLayoutsMenu(); void updateLayoutsMenu();
void saveNamedLayout(); void saveNamedLayout();
void manageLayouts();
void setOverviewData(); void setOverviewData();
bool isOverviewActive(); bool isOverviewActive();

View File

@ -114,6 +114,8 @@
<addaction name="actionGrouped_dock_dragging"/> <addaction name="actionGrouped_dock_dragging"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menuZoom"/> <addaction name="menuZoom"/>
<addaction name="separator"/>
<addaction name="actionManageLayouts"/>
<addaction name="actionSaveLayout"/> <addaction name="actionSaveLayout"/>
<widget class="QMenu" name="menuLayouts"> <widget class="QMenu" name="menuLayouts">
<property name="title"> <property name="title">
@ -849,6 +851,11 @@
<string>Save layout</string> <string>Save layout</string>
</property> </property>
</action> </action>
<action name="actionManageLayouts">
<property name="text">
<string>Manage layouts</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources> <resources>

View File

@ -0,0 +1,75 @@
#include "LayoutManager.h"
#include "ui_LayoutManager.h"
#include <QIntValidator>
#include <QInputDialog>
using namespace Cutter;
LayoutManager::LayoutManager(QMap<QString, Cutter::CutterLayout> &layouts, QWidget *parent) :
QDialog(parent),
ui(new Ui::LayoutManager),
layouts(layouts)
{
ui->setupUi(this);
connect(ui->renameButton, &QPushButton::clicked, this, &LayoutManager::renameCurrentLayout);
connect(ui->deleteButton, &QPushButton::clicked, this, &LayoutManager::deleteLayout);
connect(ui->layoutSelector, &QComboBox::currentTextChanged, this, &LayoutManager::updateButtons);
refreshNameList();
}
LayoutManager::~LayoutManager()
{
}
void LayoutManager::refreshNameList(QString selection)
{
ui->layoutSelector->clear();
for (auto it = layouts.begin(), end = layouts.end(); it != end; ++it) {
if (!Cutter::isBuiltinLayoutName(it.key())) {
ui->layoutSelector->addItem(it.key());
}
}
if (!selection.isEmpty()) {
ui->layoutSelector->setCurrentText(selection);
}
updateButtons();
}
void LayoutManager::renameCurrentLayout()
{
QString current = ui->layoutSelector->currentText();
if (layouts.contains(current)) {
QString newName;
while (newName.isEmpty() || isBuiltinLayoutName(newName) || layouts.contains(newName)) {
if (!newName.isEmpty()) {
QMessageBox::warning(this, tr("Rename layout error"), tr("'%1' is already used.").arg(newName));
}
newName = QInputDialog::getText(this, tr("Save layout"), tr("Enter name"), QLineEdit::Normal,
current);
if (newName.isEmpty()) {
return;
}
}
auto layout = layouts.take(current);
layouts.insert(newName, layout);
refreshNameList(newName);
}
}
void LayoutManager::deleteLayout()
{
auto selected = ui->layoutSelector->currentText();
auto answer = QMessageBox::question(this, tr("Delete"),
tr("Do you want to delete '%1'").arg(selected));
if (answer == QMessageBox::Yes) {
layouts.remove(selected);
refreshNameList();
}
}
void LayoutManager::updateButtons()
{
bool hasSelection = !ui->layoutSelector->currentText().isEmpty();
ui->renameButton->setEnabled(hasSelection);
ui->deleteButton->setEnabled(hasSelection);
}

View File

@ -0,0 +1,30 @@
#ifndef LAYOUT_MANAGER_H
#define LAYOUT_MANAGER_H
#include <QDialog>
#include <memory>
#include "core/Cutter.h"
#include "common/CutterLayout.h"
namespace Ui {
class LayoutManager;
}
class LayoutManager : public QDialog
{
Q_OBJECT
public:
LayoutManager(QMap<QString, Cutter::CutterLayout> &layouts, QWidget *parent);
~LayoutManager();
private:
void refreshNameList(QString selection = "");
void renameCurrentLayout();
void deleteLayout();
void updateButtons();
std::unique_ptr<Ui::LayoutManager> ui;
QMap<QString, Cutter::CutterLayout> &layouts;
};
#endif // LAYOUT_MANAGER_H

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LayoutManager</class>
<widget class="QDialog" name="LayoutManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>254</width>
<height>88</height>
</rect>
</property>
<property name="windowTitle">
<string>Layout</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="layoutSelector"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="renameButton">
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>