diff options
| author | Glenn Smith <glenn@vector35.com> | 2021-12-28 15:59:31 -0500 |
|---|---|---|
| committer | Glenn Smith <glenn@vector35.com> | 2022-01-17 20:42:22 -0500 |
| commit | e88ed98d60b046cbeb5f4c96fc374c7576d7b57a (patch) | |
| tree | e937ffccba9868cb259e4ec817157b255dc74652 | |
| parent | dc2a99e1159c3951b91935490ec8d2da5fc8e1e7 (diff) | |
OptionsDialog/SettingsView + ProgressTask fixes
| -rw-r--r-- | binaryninjaapi.h | 1 | ||||
| -rw-r--r-- | ui/filecontext.h | 5 | ||||
| -rw-r--r-- | ui/options.h | 48 | ||||
| -rw-r--r-- | ui/progresstask.h | 197 | ||||
| -rw-r--r-- | ui/settingsview.h | 390 | ||||
| -rw-r--r-- | ui/uicontext.h | 6 |
6 files changed, 506 insertions, 141 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index b33f49f8..9cd6475a 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -975,6 +975,7 @@ __attribute__ ((format (printf, 1, 2))) UndoAction action; std::string hash; + MergeResult(): status(NOT_APPLICABLE) {} MergeResult(const BNMergeResult& result); }; diff --git a/ui/filecontext.h b/ui/filecontext.h index 54861591..e31037ed 100644 --- a/ui/filecontext.h +++ b/ui/filecontext.h @@ -31,7 +31,7 @@ class BINARYNINJAUIAPI FileContext: public FileContextBase, public BinaryNinja:: std::map<QString, BinaryViewRef> m_dataViews; ViewFrame* m_currentViewFrame; - std::set<QObject*> m_refs; + std::map<QObject*, QMetaObject::Connection> m_refs; std::vector<SyncGroup*> m_syncGroups; std::map<ViewFrame*, std::pair<View*, ViewLocation>> m_syncLastLocation; @@ -45,7 +45,8 @@ public: FileContext(FileMetadataRef file, BinaryViewRef rawData, const QString& filename = QString(), bool isValidSaveName = false, bool createViews = true); virtual ~FileContext(); - void registerReference(QWidget* widget); + void registerReference(QObject* widget); + void unregisterReference(QObject* widget); void close(); static void closeAllOpenFiles(); diff --git a/ui/options.h b/ui/options.h new file mode 100644 index 00000000..d6288657 --- /dev/null +++ b/ui/options.h @@ -0,0 +1,48 @@ +#pragma once + +#include <QtWidgets/QComboBox> +#include <QtWidgets/QDialog> +#include <QtWidgets/QLabel> +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtWidgets/QTabWidget> +#include <QtWidgets/QWidget> +#include "binaryninjaapi.h" +#include "viewtype.h" +#include "filecontext.h" + +#include <string> +#include <tuple> +#include <vector> + +class BINARYNINJAUIAPI OptionsDialog: public QDialog +{ + Q_OBJECT + + QString m_fileName; + QLabel* m_fileLabel; + QLabel* m_objectLabel; + QComboBox* m_objectCombo; + QTabWidget* m_tab; + QLabel* m_notification; + + bool m_isDatabase; + FileContext* m_file = nullptr; + FileMetadataRef m_fileMetadata = nullptr; + BinaryViewRef m_rawData = nullptr; + std::vector<std::tuple<std::string, size_t, std::string, uint64_t, uint64_t, std::string>> m_objects; + +public: + OptionsDialog(QWidget* parent, const QString& name); + virtual ~OptionsDialog(); + +Q_SIGNALS: + void openFile(FileContext* file); + +private Q_SLOTS: + void cancel(); + void open(); + void addSettingsViewForType(const std::string& bvtName); + void queryViewTypes(); + void viewTabCloseRequested(int index); +}; diff --git a/ui/progresstask.h b/ui/progresstask.h index e8ba1ba2..a291c439 100644 --- a/ui/progresstask.h +++ b/ui/progresstask.h @@ -4,8 +4,13 @@ #include <QtCore/QThread> #include <QtCore/QVariant> #include <QtCore/QCoreApplication> +#include <QtGui/QKeyEvent> #include <QtConcurrent/QtConcurrent> -#include <QtWidgets/QProgressDialog> +#include <QtWidgets/QBoxLayout> +#include <QtWidgets/QDialog> +#include <QtWidgets/QLabel> +#include <QtWidgets/QProgressBar> +#include <QtWidgets/QPushButton> #include <atomic> #include <functional> #include <chrono> @@ -16,91 +21,40 @@ /*! Dialog displaying a progress bar and cancel button */ -class BINARYNINJAUIAPI ProgressDialog: public QObject +class BINARYNINJAUIAPI ProgressDialog: public QDialog { Q_OBJECT - QProgressDialog* m_progress; + QProgressBar* m_progress; + QLabel* m_text; + QPushButton* m_cancel; + bool m_cancellable; bool m_maxSet; + std::atomic<bool> m_processing; + std::atomic<bool> m_wasCancelled; std::chrono::steady_clock::time_point m_lastUpdate; public: - ProgressDialog(QWidget* parent, const QString& title, const QString& text, const QString& cancel=QString()) - { - m_progress = new QProgressDialog(parent); - m_progress->setWindowTitle(title); - m_progress->setLabelText(text); - m_progress->setMinimumDuration(200); - m_progress->setWindowModality(Qt::WindowModal); - m_progress->setCancelButtonText(cancel); - m_progress->setValue(m_progress->minimum()); - connect(m_progress, &QProgressDialog::canceled, this, &ProgressDialog::cancelButton); - m_maxSet = false; - m_lastUpdate = std::chrono::steady_clock::now(); - } - - bool wasCancelled() const - { - return m_progress->wasCanceled(); - } + ProgressDialog(QWidget* parent, const QString& title, const QString& text, const QString& cancel=QString()); - void hideForModal(std::function<void()> modal) - { - m_progress->blockSignals(true); - int value = m_progress->value(); - m_progress->reset(); + bool wasCancelled() const; - modal(); + void hideForModal(std::function<void()> modal); - m_progress->setValue(value); - m_progress->blockSignals(false); - } + QString text() const; - QString text() const - { - return m_progress->labelText(); - } + void setText(const QString& text); - void setText(const QString& text) - { - m_progress->setLabelText(text); - } +protected: + virtual void keyPressEvent(QKeyEvent* event) override; private Q_SLOTS: - void cancelButton() - { - Q_EMIT canceled(); - } + void cancelButton(); public Q_SLOTS: - void update(int cur, int total) - { - if (m_progress->wasCanceled()) - return; + void update(int cur, int total); - bool maxUpdated = false; - if (!m_maxSet || (total != m_progress->maximum())) - { - m_progress->setMaximum((int)total); - m_maxSet = true; - maxUpdated = true; - } - - std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); - if (cur == total || std::chrono::duration_cast<std::chrono::milliseconds>(curTime - m_lastUpdate).count() >= 100 || maxUpdated) - { - m_progress->blockSignals(true); - m_progress->setValue((int)cur); - m_progress->blockSignals(false); - m_lastUpdate = std::chrono::steady_clock::now(); - } - } - - void cancel() - { - m_progress->cancel(); - Q_EMIT canceled(); - } + void cancel(); Q_SIGNALS: void canceled(); }; @@ -121,6 +75,10 @@ Q_SIGNALS: ProgressTask* task = new ProgressTask("Long Operation", "Long Operation", "Cancel", [](std::function<bool(size_t, size_t)> progress) { doLongOperationWithProgress(progress); + + // Report progress by calling the progress function + if (!progress(current, maximum)) + return; // If the progress function returns false, then the user has cancelled the operation }); // Throws if doLongOperationWithProgress threw task->wait(); @@ -138,106 +96,67 @@ class BINARYNINJAUIAPI ProgressTask: public QObject std::exception_ptr m_exception; public: + /*! + Construct a new progress task, which automatically starts running a given function + \param parent Parent QWidget to display progress dialog on top of + \param name Title for progress dialog + \param text Text for progress dialog + \param cancel Cancel button title. If empty, the cancel button will not be shown + \param func Function to run in the background, which takes a progress reporting function for its argument. + The function should call the progress function periodically to signal updates and check for cancellation. + */ ProgressTask(QWidget* parent, const QString& name, const QString& text, const QString& cancel, - std::function<void(std::function<bool(size_t, size_t)>)> func): - QObject(), m_canceled(false), m_func(func), m_exception() - { - m_thread = new QThread(parent); - m_dialog = new ProgressDialog(parent, name, text, cancel); - connect(this, &ProgressTask::progress, m_dialog, &ProgressDialog::update); - connect(m_thread, &QThread::finished, this, &ProgressTask::finish); - connect(m_thread, &QThread::started, this, &ProgressTask::start); - connect(m_dialog, &ProgressDialog::canceled, [this]() { - m_canceled = true; - }); - - m_thread->start(); - - moveToThread(m_thread); - } - virtual ~ProgressTask() {} + std::function<void(std::function<bool(size_t, size_t)>)> func); + virtual ~ProgressTask(); /*! - Wait for the task to finish - \throws exception Any exception that the provided func throws + Wait for the task to finish + \throws exception Any exception that the provided func throws + \returns False if canceled, true otherwise */ - void wait() - { - while (!m_thread->isFinished()) - { - m_thread->wait(50); - QCoreApplication::processEvents(); - } - m_dialog->cancel(); - m_dialog->deleteLater(); - deleteLater(); - if (m_exception) - { - std::rethrow_exception(m_exception); - } - } + bool wait(); /*! Hide the task to present a modal (in a function) since the progress dialog will block other parts of the ui from responding while it is present. \param modal Function to present a modal ui on top */ - void hideForModal(std::function<void()> modal) - { - m_dialog->hideForModal(modal); - } + void hideForModal(std::function<void()> modal); /*! Get the text label of the progress dialog \return Text label contents */ - QString text() const - { - return m_dialog->text(); - } + QString text() const; /*! Set the text label on the progress dialog \param text New text label contents */ - void setText(const QString& text) - { - m_dialog->setText(text); - } + void setText(const QString& text); private Q_SLOTS: - void start() - { - try - { - m_func([this](size_t cur, size_t total) { - Q_EMIT progress(cur, total); - return !m_canceled.load(); - }); - } - catch (...) - { - m_exception = std::current_exception(); - } - m_thread->quit(); - } + void start(); - void finish() - { - Q_EMIT finished(); - } + void finish(); public Q_SLOTS: /*! Cancel the progress dialog */ - void cancel() - { - m_canceled = true; - } + void cancel(); Q_SIGNALS: - void progress(int, int); + /*! + Signal reported every time there is a progress update (probably often) + \param cur Current progress value + \param max Maximum progress value + */ + void progress(int cur, int max); + + /*! + Signal reported when the task has finished + */ void finished(); }; diff --git a/ui/settingsview.h b/ui/settingsview.h new file mode 100644 index 00000000..6a11ee17 --- /dev/null +++ b/ui/settingsview.h @@ -0,0 +1,390 @@ +#pragma once + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QItemSelection> +#include <QtCore/QModelIndex> +#include <QtCore/QRegularExpression> +#include <QtCore/QSize> +#include <QtCore/QSortFilterProxyModel> +#include <QtCore/QTimer> +#include <QtCore/QVariant> +#include <QtGui/QMouseEvent> +#include <QtWidgets/QCheckBox> +#include <QtWidgets/QComboBox> +#include <QtWidgets/QDoubleSpinBox> +#include <QtWidgets/QLabel> +#include <QtWidgets/QLineEdit> +#include <QtWidgets/QSpinBox> +#include <QtWidgets/QStyledItemDelegate> +#include <QtWidgets/QTreeView> + +#include <map> +#include <optional> +#include <set> +#include <utility> +#include <vector> +#include "binaryninjaapi.h" +#include "json/json.h" +#include "render.h" +#include "menus.h" +#include "clickablelabel.h" + + +struct SettingsEntry +{ + SettingsEntry(int p, const QString& h, const QString& g, std::vector<void*>&& j) : parent(p), heading(h), group(g), jsonDefs(j) { } + int parent; + QString heading; + QString group; + std::vector<void*> jsonDefs; + std::vector<QString> subgroups; +}; + + +class BINARYNINJAUIAPI SettingsTreeModel: public QAbstractItemModel +{ + Q_OBJECT + +private: + Json::Value m_schema; + std::vector<SettingsEntry> m_store; + std::map<std::string, QString> m_filterText; + +public: + SettingsTreeModel(std::string schema, QObject* parent = 0); + ~SettingsTreeModel(); + + QVariant data(const QModelIndex& index, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + + void updateModel(); +}; + + +class BINARYNINJAUIAPI SettingsFilterProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + + int m_scopeFilter = SettingsAutoScope; + std::map<std::string, int> m_itemScope; + mutable QRegularExpression m_regExp; + mutable std::map<QString, std::set<QString>> m_subgroupFilterCache; + +public: + SettingsFilterProxyModel(QObject* parent = 0); + + int scopeFilter() { return m_scopeFilter; } + void setScopeFilter(int scope) { m_scopeFilter = scope; invalidateFilter(); m_subgroupFilterCache.clear(); } + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + +public Q_SLOTS: + void updateScope(const std::string& key, int scope) { m_itemScope[key] = scope; } +}; + + +class BINARYNINJAUIAPI SettingsOutlineProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + +public: + SettingsOutlineProxyModel(QObject* parent = 0); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; +}; + + +class BINARYNINJAUIAPI SettingsEditor: public QWidget +{ + Q_OBJECT + +private: + SettingsRef m_settings; + Json::Value m_setting; + std::string m_settingKey; + BinaryViewRef m_view = nullptr; + BNSettingsScope m_scope = SettingsAutoScope; + + QLabel* m_title = nullptr; + ClickableLabel* m_description = nullptr; + QLabel* m_settingKeyText = nullptr; + QLabel* m_settingSep = nullptr; + QLabel* m_message = nullptr; + + BNSettingsScope m_currSettingScope = SettingsDefaultScope; + QLabel* m_scopeText = nullptr; + QCheckBox* m_checkBox = nullptr; + QLineEdit* m_settingText = nullptr; + QDoubleSpinBox* m_doubleSpinBox = nullptr; + QSpinBox* m_spinBox = nullptr; + QComboBox* m_comboBox = nullptr; + QLineEdit* m_arrayText = nullptr; + std::set<QString> m_validComboSelections; + Json::StreamWriterBuilder m_builder; + + bool m_optional = false; + bool m_readOnly = false; + bool m_requiresRestart = false; + bool m_settingModified = false; + +public: + SettingsEditor(QWidget* parent, SettingsRef settings, BinaryViewRef view, BNSettingsScope scope, const Json::Value* setting); + ~SettingsEditor(); + + QSize sizeHint() const override; + void setSetting(const Json::Value* value, bool updateSchema = false); + +Q_SIGNALS: + void geometryChanged(); + void settingChanged(); + void allSettingsChanged(); + void showIdentifiers(bool enable); + void notifyScope(const std::string& key, int scope); + void notifySettingChanged(QString); + void notifyNeedsRestart(); + +private: + void notifySettingUpdate(); // TODO core notification callbacks + +private Q_SLOTS: + void toggleBoolSetting(); + void updateBoolSetting(bool enabled); + void updateEnumStringSetting(const QString& text); + void updateStringSetting(); + void updateFormatedNumberSetting(); + void updateDoubleNumberSetting(double value); + void updateIntNumberSetting(int value); + void updateArraySetting(); + void addArrayStringSetting(const QString& text); + void resetSetting(); + void resetAllSettings(BNSettingsScope scope); + + // TODO GUI plugins + void selectFont(); + void selectUiFont(); + void selectInterpreter(); + void selectVirtualEnv(); + +public Q_SLOTS: + void updateScope(BinaryViewRef, BNSettingsScope); + void updateSize(); + void updateViewMode(bool enabled); + +private: + void contextMenu(); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void paintEvent(QPaintEvent *event) override; +}; + + +class BINARYNINJAUIAPI SettingsDelegate: public QStyledItemDelegate +{ + Q_OBJECT + +private: + SettingsRef m_settings; + SettingsFilterProxyModel* m_filterModel; + BinaryViewRef m_view = nullptr; + BNSettingsScope m_scope = SettingsAutoScope; + QFont m_groupFont; + QFont m_subgroupFont; + QFont m_monoFont; + int m_monoFontHeight; + QTimer* m_updateModelTimer; + + QTreeView* m_treeView; + std::function<void(const QModelIndex& index)> m_hoverAction = nullptr; + std::function<void()> m_defaultSelectionAction = nullptr; + +public: + SettingsDelegate(QWidget* parent, SettingsRef settings, SettingsFilterProxyModel* filterModel, const std::function<void(const QModelIndex& index)>& hoverAction = nullptr); + ~SettingsDelegate(); + + void setDefaultSelection(const std::function<void()>& selectionAction); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + +protected: + bool eventFilter(QObject* object, QEvent* event) override; + +public: +Q_SIGNALS: + void refreshAllSettings() const; + void scopeChanged(BinaryViewRef, BNSettingsScope); + void sizeChanged(); + void viewModeChanged(bool enabled) const; + void notifyNeedsRestart() const; + void notifySettingChanged(QString settingId) const; + +public Q_SLOTS: + void updateFonts(); + void updateModel(); + void updateScope(BinaryViewRef, BNSettingsScope); + void updateSize(); + void updateViewMode(bool enabled) const; + +private Q_SLOTS: + void commitEditorData(); + void editorGeometryChanged(); +}; + + +class BINARYNINJAUIAPI SettingsTreeView: public QTreeView +{ + Q_OBJECT + +public: + explicit SettingsTreeView(QWidget* parent); + ~SettingsTreeView(); + +protected: + virtual void resizeEvent(QResizeEvent* event) override; + + +public Q_SLOTS: + void modelChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); +}; + + +class BINARYNINJAUIAPI BinaryViewScopeLabel: public MenuHelper +{ + Q_OBJECT + + BNSettingsScope m_scope; + QString m_scopeName; + std::vector<QString> m_actionNames; + std::vector<std::pair<BinaryViewRef, QString>> m_views; + QString m_curName; + BinaryViewRef m_curView = nullptr; + + UIActionHandler m_actionHandler; + +public: + BinaryViewScopeLabel(QWidget* parent, const QString& name = "", BNSettingsScope scope = SettingsAutoScope); + + void refresh(); + const QString& currentSelection() { return m_curName; } + BinaryViewRef currentBinaryView() { return m_curView; } + +Q_SIGNALS: + void itemSelected(BinaryViewRef, BNSettingsScope); + +protected: + virtual void showEvent(QShowEvent* event) override; + virtual void showMenu() override; +}; + + +class BINARYNINJAUIAPI SettingsScopeBar: public QWidget +{ + Q_OBJECT + + QPushButton* m_userLabel; + BinaryViewScopeLabel* m_projectLabel; + BinaryViewScopeLabel* m_resourceLabel; + ClickableLabel* m_openProjectLabel; + QLabel* m_desc; + + void setScopeHighlight(unsigned long highlightIdx); + +public: + SettingsScopeBar(QWidget* parent = nullptr); + + void refresh(); + +Q_SIGNALS: + void scopeChanged(BinaryViewRef, BNSettingsScope); +}; + + +class BINARYNINJAUIAPI SearchFilter: public QLineEdit +{ + Q_OBJECT + + std::map<QString, int> m_filterTags; + QRegularExpression m_regexTagExtract; + QTimer* m_filterDelayTimer = nullptr; + int m_delay; + +public: + SearchFilter(QWidget* parent = nullptr); + + void addTag(const QString& tagName, int tag); + void setDelay(int msec = 100) { m_delay = msec; } + void setFilter(); + + std::pair<QString, std::vector<int>> getSearchParams(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +Q_SIGNALS: + void delayedTextChanged(); +}; + + +class BINARYNINJAUIAPI SettingsView: public QWidget +{ + Q_OBJECT + +private: + QWidget* m_owner = nullptr; + SettingsRef m_settings; + SettingsFilterProxyModel* m_proxyModel = nullptr; + SettingsOutlineProxyModel* m_outlineProxyModel = nullptr; + QTreeView* m_outlineView = nullptr; + SettingsTreeView* m_settingsTreeView = nullptr; + SettingsDelegate* m_delegate = nullptr; + SettingsScopeBar* m_scopeBar = nullptr; + QCheckBox* m_viewMode = nullptr; + SearchFilter* m_search = nullptr; + bool m_outlineNavEnabled = true; + +public: + SettingsView(QWidget* parent); + SettingsView(QWidget* parent, SettingsRef settings); + ~SettingsView(); + + void init(std::string schema, bool uiScopeSelection); + void refreshCurrentScope(); + void setData(BinaryViewRef view, const QString& name = ""); + void setDefaultGroupSelection(const QString& group); + +public Q_SLOTS: + void updateFonts(); + void updateTheme(); + +private Q_SLOTS: + void outlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void updateScopeFilter(); + void updateTextFilter(); + +Q_SIGNALS: + void fontsChanged(); + void notifyNeedsRestart(); + void notifySettingChanged(QString settingId) const; +}; diff --git a/ui/uicontext.h b/ui/uicontext.h index 4c2a5853..68160381 100644 --- a/ui/uicontext.h +++ b/ui/uicontext.h @@ -193,6 +193,12 @@ public: virtual bool navigateForBinaryView(BinaryViewRef view, uint64_t addr); /*! + Get a list of all opened binary views, and their names + \return List of binary views and names + */ + virtual std::vector<std::pair<BinaryViewRef, QString>> getAvailableBinaryViews() = 0; + + /*! Get the currently visible View for the currently visible ViewFrame (if it exists) \return Current View or nullptr if the current ViewFrame is null or does not have a View */ |
