From 199c26adbf6cf75d5afd735f337f80d31939d4a2 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 8 Jun 2017 18:23:10 +0100 Subject: [PATCH] Put the MainWindow in charge of handling 'global' type shortcuts * QShortcut falls down on duplicates. It can have activatedAmbiguously events, but these happen in arbitrary order and the shortcuts on menu items just swallow the ambiguous activate so it's not useful. * Instead we just let MainWindow pick up ShortcutOverride events and consult a mapping of which shortcuts to use. We can use a smarter selection method to choose the more 'local' shortcut if two shortcuts that conflict exist. --- qrenderdoc/Code/Interface/QRDInterface.h | 29 +++- qrenderdoc/Windows/EventBrowser.cpp | 21 +-- qrenderdoc/Windows/EventBrowser.ui | 6 - qrenderdoc/Windows/MainWindow.cpp | 177 +++++++++++++++++------ qrenderdoc/Windows/MainWindow.h | 8 + qrenderdoc/Windows/MainWindow.ui | 3 - qrenderdoc/Windows/ShaderViewer.cpp | 5 +- 7 files changed, 184 insertions(+), 65 deletions(-) diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index b5539a62c..e429a46f2 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -93,13 +93,40 @@ struct CaptureSettings DECLARE_REFLECTION_STRUCT(CaptureSettings); -DOCUMENT("The main parent window of the application."); +DOCUMENT(R"(The main parent window of the application. + +.. function:: ShortcutCallback() + + Not a member function - the signature for any ``ShortcutCallback`` callbacks. +)"); struct IMainWindow { + typedef std::function ShortcutCallback; + DOCUMENT( "Retrieves the QWidget for this :class:`MainWindow` if PySide2 is available, or ``None``."); virtual QWidget *Widget() = 0; + DOCUMENT(R"(Register a callback for a particular key shortcut. + +This creates a managed shortcut. Qt's shortcut system doesn't allow specialisation/duplication, so +you can't use ``Ctrl+S`` for a shortcut in a window to update some changes if there's also a global +``Ctrl+S`` shortcut on the window. In the end, neither shortcut will be called. + +Instead this function allows the main window to manage shortcuts internally, and it will pick the +closest shortcut to a given action. The search goes from the widget with the focus currently up the +chain of parents, with the first match being used. If no matches are found, then a 'global' default +will be invoked, if it exists. + +:param str shortcut: The text string representing the shortcut, e.g. 'Ctrl+S'. +:param QWidget widget: A handle to the widget to use as the context for this shortcut, or ``None`` + for a global shortcut. Note that if an existing global shortcut exists the new one will not be + registered. +:rtype: ``str`` +)"); + virtual void RegisterShortcut(const QString &shortcut, QWidget *widget, + ShortcutCallback callback) = 0; + protected: IMainWindow() = default; ~IMainWindow() = default; diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 4b24f0775..30521ed23 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -134,19 +134,20 @@ EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent) }; for(int i = 0; i < 10; i++) { - QShortcut *sc = new QShortcut(QKeySequence(keys[i] | Qt::ControlModifier), this); - QObject::connect(sc, &QShortcut::activated, [this, i]() { jumpToBookmark(i); }); + ctx.GetMainWindow()->RegisterShortcut(QKeySequence(keys[i] | Qt::ControlModifier).toString(), + NULL, [this, i]() { jumpToBookmark(i); }); } - { - QShortcut *sc = new QShortcut(QKeySequence(Qt::Key_Left | Qt::ControlModifier), this); - QObject::connect(sc, &QShortcut::activated, this, &EventBrowser::on_stepPrev_clicked); - } + ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Left | Qt::ControlModifier).toString(), + NULL, [this]() { on_stepPrev_clicked(); }); - { - QShortcut *sc = new QShortcut(QKeySequence(Qt::Key_Right | Qt::ControlModifier), this); - QObject::connect(sc, &QShortcut::activated, this, &EventBrowser::on_stepNext_clicked); - } + ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Right | Qt::ControlModifier).toString(), + NULL, [this]() { on_stepNext_clicked(); }); + + ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Escape).toString(), ui->findStrip, + [this]() { on_HideFindJump(); }); + ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Escape).toString(), ui->jumpStrip, + [this]() { on_HideFindJump(); }); ui->events->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(ui->events, &RDTreeWidget::customContextMenuRequested, this, diff --git a/qrenderdoc/Windows/EventBrowser.ui b/qrenderdoc/Windows/EventBrowser.ui index 085a57e14..475f3cb82 100644 --- a/qrenderdoc/Windows/EventBrowser.ui +++ b/qrenderdoc/Windows/EventBrowser.ui @@ -323,9 +323,6 @@ :/cross.png:/cross.png - - Esc - true @@ -436,9 +433,6 @@ :/cross.png:/cross.png - - Esc - true diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 01796bcee..d4028ea9a 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -57,6 +57,8 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai { ui->setupUi(this); + installEventFilter(this); + setAcceptDrops(true); QObject::connect(ui->action_Load_Default_Layout, &QAction::triggered, this, @@ -166,8 +168,6 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai ui->action_Resolve_Symbols->setEnabled(false); ui->action_Resolve_Symbols->setText(tr("Resolve Symbols")); - bool loaded = LoadLayout(0); - LambdaThread *th = new LambdaThread([this]() { m_Ctx.Config().AddAndroidHosts(); for(RemoteHost *host : m_Ctx.Config().RemoteHosts) @@ -176,46 +176,6 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai th->selfDelete(true); th->start(); - // create default layout if layout failed to load - if(!loaded) - { - QWidget *eventBrowser = m_Ctx.GetEventBrowser()->Widget(); - - ui->toolWindowManager->addToolWindow(eventBrowser, ToolWindowManager::EmptySpace); - - QWidget *textureViewer = m_Ctx.GetTextureViewer()->Widget(); - - ui->toolWindowManager->addToolWindow( - textureViewer, - ToolWindowManager::AreaReference(ToolWindowManager::RightOf, - ui->toolWindowManager->areaOf(eventBrowser), 0.75f)); - - QWidget *pipe = m_Ctx.GetPipelineViewer()->Widget(); - - ui->toolWindowManager->addToolWindow( - pipe, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, - ui->toolWindowManager->areaOf(textureViewer))); - - QWidget *mesh = m_Ctx.GetMeshPreview()->Widget(); - - ui->toolWindowManager->addToolWindow( - mesh, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, - ui->toolWindowManager->areaOf(textureViewer))); - - QWidget *capDialog = m_Ctx.GetCaptureDialog()->Widget(); - - ui->toolWindowManager->addToolWindow( - capDialog, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, - ui->toolWindowManager->areaOf(textureViewer))); - - QWidget *apiInspector = m_Ctx.GetAPIInspector()->Widget(); - - ui->toolWindowManager->addToolWindow( - apiInspector, - ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, - ui->toolWindowManager->areaOf(eventBrowser), 0.3f)); - } - #if defined(Q_OS_WIN32) if(GetModuleHandleA("rdocself.dll")) { @@ -244,6 +204,27 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai #endif m_Ctx.AddLogViewer(this); + + QList actions = ui->menuBar->actions(); + + // register all the UI-designer created shortcut keys + for(int i = 0; i < actions.count(); i++) + { + QAction *a = actions[i]; + + QKeySequence ks = a->shortcut(); + if(!ks.isEmpty()) + { + m_GlobalShortcutCallbacks[ks] = [a]() { + if(a->isEnabled()) + a->trigger(); + }; + } + + // recurse into submenus by appending to the end of the list. + if(a->menu()) + actions.append(a->menu()->actions()); + } } MainWindow::~MainWindow() @@ -845,6 +826,53 @@ ToolWindowManager::AreaReference MainWindow::leftToolArea() return ToolWindowManager::AreaReference(ToolWindowManager::LastUsedArea); } +void MainWindow::show() +{ + bool loaded = LoadLayout(0); + + // create default layout if layout failed to load + if(!loaded) + { + QWidget *eventBrowser = m_Ctx.GetEventBrowser()->Widget(); + + ui->toolWindowManager->addToolWindow(eventBrowser, ToolWindowManager::EmptySpace); + + QWidget *textureViewer = m_Ctx.GetTextureViewer()->Widget(); + + ui->toolWindowManager->addToolWindow( + textureViewer, + ToolWindowManager::AreaReference(ToolWindowManager::RightOf, + ui->toolWindowManager->areaOf(eventBrowser), 0.75f)); + + QWidget *pipe = m_Ctx.GetPipelineViewer()->Widget(); + + ui->toolWindowManager->addToolWindow( + pipe, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->toolWindowManager->areaOf(textureViewer))); + + QWidget *mesh = m_Ctx.GetMeshPreview()->Widget(); + + ui->toolWindowManager->addToolWindow( + mesh, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->toolWindowManager->areaOf(textureViewer))); + + QWidget *capDialog = m_Ctx.GetCaptureDialog()->Widget(); + + ui->toolWindowManager->addToolWindow( + capDialog, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->toolWindowManager->areaOf(textureViewer))); + + QWidget *apiInspector = m_Ctx.GetAPIInspector()->Widget(); + + ui->toolWindowManager->addToolWindow( + apiInspector, + ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, + ui->toolWindowManager->areaOf(eventBrowser), 0.3f)); + } + + QMainWindow::show(); +} + void MainWindow::recentLog(const QString &filename) { if(QFileInfo::exists(filename)) @@ -1258,6 +1286,71 @@ void MainWindow::OnEventChanged(uint32_t eventID) { } +void MainWindow::RegisterShortcut(const QString &shortcut, QWidget *widget, ShortcutCallback callback) +{ + QKeySequence ks = QKeySequence::fromString(shortcut); + + if(widget) + { + m_WidgetShortcutCallbacks[ks][widget] = callback; + } + else + { + if(m_GlobalShortcutCallbacks[ks]) + { + qCritical() << "Assigning duplicate global shortcut for" << ks; + return; + } + + m_GlobalShortcutCallbacks[ks] = callback; + } +} + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) +{ + if(event->type() == QEvent::ShortcutOverride) + { + QKeyEvent *ke = (QKeyEvent *)event; + + QKeySequence pressed(ke->modifiers() | ke->key()); + + // first see if there's a widget shortcut registered for this key. If so, check the focus + // hierarchy to see if we have any matches + QWidget *focus = QApplication::focusWidget(); + + if(focus && m_WidgetShortcutCallbacks.contains(pressed)) + { + const QMap callbacks = m_WidgetShortcutCallbacks[pressed]; + QList widgets = callbacks.keys(); + + while(focus) + { + // if we find a direct ancestor to the focus widget which is registered for this shortcut, + // then use that callback + if(widgets.contains(focus)) + { + callbacks[focus](); + event->accept(); + return true; + } + + // keep searching up the hierarchy + focus = focus->parentWidget(); + } + } + + // if we didn't find matches or no such shortcut is registered, try global shortcuts + if(m_GlobalShortcutCallbacks.contains(pressed)) + { + m_GlobalShortcutCallbacks[pressed](); + event->accept(); + return true; + } + } + + return QMainWindow::eventFilter(watched, event); +} + void MainWindow::on_action_Close_Log_triggered() { PromptCloseLog(); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index f5f575755..a164aabd5 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -53,6 +53,7 @@ public: // IMainWindow QWidget *Widget() override { return this; } + void RegisterShortcut(const QString &shortcut, QWidget *widget, ShortcutCallback callback); // ILogViewerForm void OnLogfileLoaded() override; void OnLogfileClosed() override; @@ -63,6 +64,8 @@ public: ToolWindowManager::AreaReference mainToolArea(); ToolWindowManager::AreaReference leftToolArea(); + void show(); + void setProgress(float val); void takeLogOwnership() { m_OwnTempLog = true; } void LoadFromFilename(const QString &filename); @@ -133,6 +136,8 @@ private: void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; + QString dragFilename(const QMimeData *mimeData); Ui::MainWindow *ui; @@ -140,6 +145,9 @@ private: QList m_LiveCaptures; + QMap m_GlobalShortcutCallbacks; + QMap> m_WidgetShortcutCallbacks; + RDLabel *statusIcon; RDLabel *statusText; QProgressBar *statusProgress; diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 35fa1416b..7e658d6a9 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -199,9 +199,6 @@ E&xit - - Alt+F4 - diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index ea10788d1..1e4e01d31 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -217,9 +217,8 @@ void ShaderViewer::editShader(bool customShader, const QString &entryPoint, cons m_FindState = FindState(); }); - // TODO - shortcuts - QObject::connect(new QShortcut(QKeySequence(Qt::Key_S | Qt::ControlModifier), scintilla), - &QShortcut::activated, this, &ShaderViewer::on_save_clicked); + m_Ctx.GetMainWindow()->RegisterShortcut(QKeySequence(QKeySequence::Save).toString(), this, + [this]() { on_save_clicked(); }); QWidget *w = (QWidget *)scintilla; w->setProperty("filename", f);