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.
This commit is contained in:
baldurk
2017-06-08 18:23:10 +01:00
parent 085026aa91
commit 199c26adbf
7 changed files with 184 additions and 65 deletions
+28 -1
View File
@@ -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<void()> 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;
+11 -10
View File
@@ -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,
-6
View File
@@ -323,9 +323,6 @@
<iconset resource="../Resources/resources.qrc">
<normaloff>:/cross.png</normaloff>:/cross.png</iconset>
</property>
<property name="shortcut">
<string>Esc</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
@@ -436,9 +433,6 @@
<iconset resource="../Resources/resources.qrc">
<normaloff>:/cross.png</normaloff>:/cross.png</iconset>
</property>
<property name="shortcut">
<string>Esc</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
+135 -42
View File
@@ -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<QAction *> 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<QWidget *, ShortcutCallback> callbacks = m_WidgetShortcutCallbacks[pressed];
QList<QWidget *> 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();
+8
View File
@@ -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<LiveCapture *> m_LiveCaptures;
QMap<QKeySequence, ShortcutCallback> m_GlobalShortcutCallbacks;
QMap<QKeySequence, QMap<QWidget *, ShortcutCallback>> m_WidgetShortcutCallbacks;
RDLabel *statusIcon;
RDLabel *statusText;
QProgressBar *statusProgress;
-3
View File
@@ -199,9 +199,6 @@
<property name="text">
<string>E&amp;xit</string>
</property>
<property name="shortcut">
<string>Alt+F4</string>
</property>
</action>
<action name="action_Python_Shell">
<property name="text">
+2 -3
View File
@@ -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);