From fe430bebbe86e0a0465673313f94b38e95edc934 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 26 Sep 2023 10:43:33 +0200 Subject: [PATCH] CompilerExplorer: Fix undo Fixes context handling for the Editor. This allows Undo/Redo actions to activate correctly. Change-Id: Ieb7fa27215f5746cf5f26e8e7b3b74f44023481c Reviewed-by: David Schulz Reviewed-by: --- .../compilerexplorerconstants.h | 1 - .../compilerexplorereditor.cpp | 140 +++++++++++------- .../compilerexplorer/compilerexplorereditor.h | 41 ++++- .../compilerexplorerplugin.cpp | 2 +- .../compilerexplorersettings.cpp | 6 +- .../texteditor/texteditoractionhandler.cpp | 78 ++++++---- .../texteditor/texteditoractionhandler.h | 3 + 7 files changed, 187 insertions(+), 84 deletions(-) diff --git a/src/plugins/compilerexplorer/compilerexplorerconstants.h b/src/plugins/compilerexplorer/compilerexplorerconstants.h index 33a7d528df3..1e8a244626b 100644 --- a/src/plugins/compilerexplorer/compilerexplorerconstants.h +++ b/src/plugins/compilerexplorer/compilerexplorerconstants.h @@ -5,5 +5,4 @@ namespace CompilerExplorer::Constants { const char CE_EDITOR_ID[] = "CompilerExplorer.Editor"; -const char CE_EDITOR_CONTEXT_ID[] = "CompilerExplorer.Editor.Context"; } diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.cpp b/src/plugins/compilerexplorer/compilerexplorereditor.cpp index 9c3f00feb5a..9fe794002cb 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.cpp +++ b/src/plugins/compilerexplorer/compilerexplorereditor.cpp @@ -249,7 +249,8 @@ QString SourceEditorWidget::sourceCode() } CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSettings, - const std::shared_ptr &compilerSettings) + const std::shared_ptr &compilerSettings, + QUndoStack *undoStack) : m_sourceSettings(sourceSettings) , m_compilerSettings(compilerSettings) { @@ -266,7 +267,7 @@ CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSett m_delayTimer, qOverload<>(&QTimer::start)); - m_asmEditor = new AsmEditorWidget; + m_asmEditor = new AsmEditorWidget(undoStack); m_asmDocument = QSharedPointer(new TextDocument); m_asmDocument->setFilePath("asm.asm"); m_asmEditor->setTextDocument(m_asmDocument); @@ -498,10 +499,7 @@ EditorWidget::EditorWidget(const QSharedPointer &document, actionHandler.updateCurrentEditor(); }); - m_context = new Core::IContext(this); - m_context->setWidget(this); - m_context->setContext(Core::Context(Constants::CE_EDITOR_CONTEXT_ID)); - Core::ICore::addContextObject(m_context); + setupHelpWidget(); } EditorWidget::~EditorWidget() @@ -521,7 +519,7 @@ void EditorWidget::addCompiler(const std::shared_ptr &sourceSett int idx, QDockWidget *parentDockWidget) { - auto compiler = new CompilerWidget(sourceSettings, compilerSettings); + auto compiler = new CompilerWidget(sourceSettings, compilerSettings, m_undoStack); compiler->setWindowTitle("Compiler #" + QString::number(idx)); compiler->setObjectName("compiler_" + QString::number(idx)); QDockWidget *dockWidget = addDockForWidget(compiler, parentDockWidget); @@ -573,7 +571,6 @@ void EditorWidget::addSourceEditor(const std::shared_ptr &source sourceSettings->compilers.clear(); m_document->settings()->m_sources.removeItem(sourceSettings->shared_from_this()); m_undoStack->endMacro(); - setupHelpWidget(); }); @@ -615,12 +612,16 @@ void EditorWidget::addSourceEditor(const std::shared_ptr &source == compilerSettings; }); QTC_ASSERT(it != m_compilerWidgets.end(), return); + if (!m_sourceWidgets.isEmpty()) + m_sourceWidgets.first()->widget()->setFocus(Qt::OtherFocusReason); delete *it; m_compilerWidgets.erase(it); }); m_sourceWidgets.append(dockWidget); + sourceEditor->setFocus(Qt::OtherFocusReason); + setupHelpWidget(); } @@ -636,6 +637,8 @@ void EditorWidget::removeSourceEditor(const std::shared_ptr &sou QTC_ASSERT(it != m_sourceWidgets.end(), return); delete *it; m_sourceWidgets.erase(it); + + setupHelpWidget(); } void EditorWidget::recreateEditors() @@ -677,24 +680,25 @@ void EditorWidget::setupHelpWidget() { if (m_document->settings()->m_sources.size() == 0) { setCentralWidget(createHelpWidget()); + centralWidget()->setFocus(Qt::OtherFocusReason); } else { delete takeCentralWidget(); } } -QWidget *EditorWidget::createHelpWidget() const +HelperWidget::HelperWidget() { using namespace Layouting; + setFocusPolicy(Qt::ClickFocus); + setAttribute(Qt::WA_TransparentForMouseEvents, false); + auto addSourceButton = new QPushButton(Tr::tr("Add source code")); - connect(addSourceButton, &QPushButton::clicked, this, [this] { - auto newSource = std::make_shared( - [settings = m_document->settings()] { return settings->apiConfig(); }); - m_document->settings()->m_sources.addItem(newSource); - }); + + connect(addSourceButton, &QPushButton::clicked, this, &HelperWidget::addSource); // clang-format off - return Column { + Column { st, Row { st, @@ -705,10 +709,30 @@ QWidget *EditorWidget::createHelpWidget() const st, }, st, - }.emerge(); + }.attachTo(this); // clang-format on } +void HelperWidget::mousePressEvent(QMouseEvent *event) +{ + setFocus(Qt::MouseFocusReason); + event->accept(); +} + +void EditorWidget::addNewSource() +{ + auto newSource = std::make_shared( + [settings = m_document->settings()] { return settings->apiConfig(); }); + m_document->settings()->m_sources.addItem(newSource); +} + +QWidget *EditorWidget::createHelpWidget() const +{ + auto w = new HelperWidget; + connect(w, &HelperWidget::addSource, this, &EditorWidget::addNewSource); + return w; +} + TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const { for (const QDockWidget *sourceWidget : m_sourceWidgets) { @@ -728,49 +752,40 @@ TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const return nullptr; } -class Editor : public Core::IEditor +Editor::Editor(TextEditorActionHandler &actionHandler) + : m_document(new JsonSettingsDocument(&m_undoStack)) { -public: - Editor(TextEditorActionHandler &actionHandler) - : m_document(new JsonSettingsDocument(&m_undoStack)) - { - setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler)); + setContext(Core::Context(Constants::CE_EDITOR_ID)); + setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler)); - connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] { - actionHandler.updateActions(); - }); - connect(&m_undoStack, &QUndoStack::canRedoChanged, this, [&actionHandler] { - actionHandler.updateActions(); - }); - } + connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] { + actionHandler.updateActions(); + }); + connect(&m_undoStack, &QUndoStack::canRedoChanged, this, [&actionHandler] { + actionHandler.updateActions(); + }); +} - ~Editor() - { - if (m_document->isModified()) { - auto settings = m_document->settings(); - if (settings->isDirty()) { - settings->apply(); - Utils::Store store; - settings->toMap(store); - QJsonDocument doc = QJsonDocument::fromVariant(Utils::mapFromStore(store)); +Editor::~Editor() +{ + delete widget(); +} - CompilerExplorer::settings().defaultDocument.setValue( - QString::fromUtf8(doc.toJson())); - } - } - delete widget(); - } +static bool childHasFocus(QWidget *parent) +{ + if (parent->hasFocus()) + return true; - Core::IDocument *document() const override { return m_document.data(); } - QWidget *toolBar() override { return nullptr; } + for (QWidget *child : parent->findChildren()) + if (childHasFocus(child)) + return true; - QSharedPointer m_document; - QUndoStack m_undoStack; -}; + return false; +} EditorFactory::EditorFactory() : m_actionHandler(Constants::CE_EDITOR_ID, - Constants::CE_EDITOR_CONTEXT_ID, + Constants::CE_EDITOR_ID, TextEditor::TextEditorActionHandler::None, [](Core::IEditor *editor) -> TextEditorWidget * { return static_cast(editor->widget())->focusedEditorWidget(); @@ -798,7 +813,32 @@ EditorFactory::EditorFactory() return false; }); + m_actionHandler.setUnhandledCallback( + [undoStackFromEditor](Utils::Id cmdId, Core::IEditor *editor) { + if (cmdId != Core::Constants::UNDO && cmdId != Core::Constants::REDO) + return false; + + if (!childHasFocus(editor->widget())) + return false; + + QUndoStack *undoStack = undoStackFromEditor(editor); + + if (!undoStack) + return false; + + if (cmdId == Core::Constants::UNDO) + undoStack->undo(); + else + undoStack->redo(); + + return true; + }); + setEditorCreator([this]() { return new Editor(m_actionHandler); }); } +AsmEditorWidget::AsmEditorWidget(QUndoStack *stack) + : m_undoStack(stack) +{} + } // namespace CompilerExplorer diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.h b/src/plugins/compilerexplorer/compilerexplorereditor.h index 061f735b02c..73b417c3ab1 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.h +++ b/src/plugins/compilerexplorer/compilerexplorereditor.h @@ -61,7 +61,7 @@ class AsmEditorWidget : public TextEditor::TextEditorWidget Q_OBJECT public: - using TextEditor::TextEditorWidget::TextEditorWidget; + AsmEditorWidget(QUndoStack *undoStack); void focusInEvent(QFocusEvent *event) override { @@ -69,8 +69,14 @@ public: emit gotFocus(); } + void undo() override { m_undoStack->undo(); } + void redo() override { m_undoStack->redo(); } + signals: void gotFocus(); + +private: + QUndoStack *m_undoStack; }; class JsonSettingsDocument : public Core::IDocument @@ -138,7 +144,8 @@ class CompilerWidget : public QWidget Q_OBJECT public: CompilerWidget(const std::shared_ptr &sourceSettings, - const std::shared_ptr &compilerSettings); + const std::shared_ptr &compilerSettings, + QUndoStack *undoStack); Core::SearchableTerminal *createTerminal(); @@ -172,6 +179,19 @@ private: QList m_marks; }; +class HelperWidget : public QWidget +{ + Q_OBJECT +public: + HelperWidget(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +signals: + void addSource(); +}; + class EditorWidget : public Utils::FancyMainWindow { Q_OBJECT @@ -194,6 +214,8 @@ protected: void setupHelpWidget(); QWidget *createHelpWidget() const; + void addNewSource(); + void addCompiler(const std::shared_ptr &sourceSettings, const std::shared_ptr &compilerSettings, int idx, @@ -207,8 +229,6 @@ protected: QVariantMap windowStateCallback(); private: - Core::IContext *m_context; - QSharedPointer m_document; QUndoStack *m_undoStack; TextEditor::TextEditorActionHandler &m_actionHandler; @@ -217,6 +237,19 @@ private: QList m_sourceWidgets; }; +class Editor : public Core::IEditor +{ +public: + Editor(TextEditor::TextEditorActionHandler &actionHandler); + ~Editor(); + + Core::IDocument *document() const override { return m_document.data(); } + QWidget *toolBar() override { return nullptr; } + + QSharedPointer m_document; + QUndoStack m_undoStack; +}; + class EditorFactory : public Core::IEditorFactory { public: diff --git a/src/plugins/compilerexplorer/compilerexplorerplugin.cpp b/src/plugins/compilerexplorer/compilerexplorerplugin.cpp index f6670df80ad..0a5dbd764b7 100644 --- a/src/plugins/compilerexplorer/compilerexplorerplugin.cpp +++ b/src/plugins/compilerexplorer/compilerexplorerplugin.cpp @@ -38,7 +38,7 @@ public: auto action = new QAction(Tr::tr("Open Compiler Explorer"), this); connect(action, &QAction::triggered, this, [] { - QString name("Compiler Explorer"); + QString name("Compiler Explorer $"); Core::EditorManager::openEditorWithContents(Constants::CE_EDITOR_ID, &name, settings().defaultDocument().toUtf8()); diff --git a/src/plugins/compilerexplorer/compilerexplorersettings.cpp b/src/plugins/compilerexplorer/compilerexplorersettings.cpp index 078b60f73eb..8accdbcc214 100644 --- a/src/plugins/compilerexplorer/compilerexplorersettings.cpp +++ b/src/plugins/compilerexplorer/compilerexplorersettings.cpp @@ -199,7 +199,6 @@ void CompilerSettings::fillLibraries(const LibrarySelectionAspect::ResultCallbac auto future = Api::libraries(m_apiConfigFunction(), lang); auto watcher = new QFutureWatcher(this); - watcher->setFuture(future); QObject::connect(watcher, &QFutureWatcher::finished, this, @@ -212,6 +211,7 @@ void CompilerSettings::fillLibraries(const LibrarySelectionAspect::ResultCallbac return; } }); + watcher->setFuture(future); } void SourceSettings::fillLanguageIdModel(const Utils::StringSelectionAspect::ResultCallback &cb) @@ -241,7 +241,6 @@ void SourceSettings::fillLanguageIdModel(const Utils::StringSelectionAspect::Res auto future = Api::languages(m_apiConfigFunction()); auto watcher = new QFutureWatcher(this); - watcher->setFuture(future); QObject::connect(watcher, &QFutureWatcher::finished, this, @@ -254,6 +253,7 @@ void SourceSettings::fillLanguageIdModel(const Utils::StringSelectionAspect::Res return; } }); + watcher->setFuture(future); } void CompilerSettings::fillCompilerModel(const Utils::StringSelectionAspect::ResultCallback &cb) @@ -277,7 +277,6 @@ void CompilerSettings::fillCompilerModel(const Utils::StringSelectionAspect::Res auto future = Api::compilers(m_apiConfigFunction(), m_languageId); auto watcher = new QFutureWatcher(this); - watcher->setFuture(future); QObject::connect(watcher, &QFutureWatcher::finished, this, @@ -295,6 +294,7 @@ void CompilerSettings::fillCompilerModel(const Utils::StringSelectionAspect::Res return; } }); + watcher->setFuture(future); } CompilerExplorerSettings::CompilerExplorerSettings() diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 80625cc42e1..49b83b8a756 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -62,8 +62,18 @@ public: Utils::Id menueGroup = Utils::Id(), Core::ActionContainer *container = nullptr) { - return registerActionHelper(id, scriptable, title, keySequence, menueGroup, container, - [this, slot](bool) { if (m_currentEditorWidget) slot(m_currentEditorWidget); }); + return registerActionHelper(id, + scriptable, + title, + keySequence, + menueGroup, + container, + [this, slot, id](bool) { + if (m_currentEditorWidget) + slot(m_currentEditorWidget); + else if (m_unhandledCallback) + m_unhandledCallback(id, m_currentEditor); + }); } QAction *registerBoolAction(Utils::Id id, @@ -135,6 +145,8 @@ public: TextEditorActionHandler::Predicate m_canUndoCallback; TextEditorActionHandler::Predicate m_canRedoCallback; + + TextEditorActionHandler::UnhandledCallback m_unhandledCallback; }; TextEditorActionHandlerPrivate::TextEditorActionHandlerPrivate @@ -478,17 +490,28 @@ void TextEditorActionHandlerPrivate::updateActions() m_textWrappingAction->setChecked(m_currentEditorWidget->displaySettings().m_textWrapping); } - if (m_currentEditorWidget) { - updateRedoAction(m_canRedoCallback ? m_canRedoCallback(m_currentEditor) - : m_currentEditorWidget->document()->isRedoAvailable()); - updateUndoAction(m_canUndoCallback ? m_canUndoCallback(m_currentEditor) - : m_currentEditorWidget->document()->isUndoAvailable()); - updateCopyAction(m_currentEditorWidget->textCursor().hasSelection()); - } else { - updateRedoAction(false); - updateUndoAction(false); - updateCopyAction(false); + bool canRedo = false; + bool canUndo = false; + bool canCopy = false; + + if (m_currentEditor && m_currentEditor->document() + && m_currentEditor->document()->id() == m_editorId) { + canRedo = m_canRedoCallback ? m_canRedoCallback(m_currentEditor) : false; + canUndo = m_canUndoCallback ? m_canUndoCallback(m_currentEditor) : false; + + if (m_currentEditorWidget) { + canRedo = m_canRedoCallback ? canRedo + : m_currentEditorWidget->document()->isRedoAvailable(); + canUndo = m_canUndoCallback ? canUndo + : m_currentEditorWidget->document()->isUndoAvailable(); + canCopy = m_currentEditorWidget->textCursor().hasSelection(); + } } + + updateRedoAction(canRedo); + updateUndoAction(canUndo); + updateCopyAction(canCopy); + updateOptionalActions(); } @@ -553,19 +576,19 @@ void TextEditorActionHandlerPrivate::updateCurrentEditor(Core::IEditor *editor) m_currentEditor = editor; if (editor && editor->document()->id() == m_editorId) { - TextEditorWidget *editorWidget = m_findTextWidget(editor); - QTC_ASSERT(editorWidget, return); // editor has our id, so shouldn't happen - m_currentEditorWidget = editorWidget; - connect(editorWidget, &QPlainTextEdit::undoAvailable, - this, &TextEditorActionHandlerPrivate::updateUndoAction); - connect(editorWidget, &QPlainTextEdit::redoAvailable, - this, &TextEditorActionHandlerPrivate::updateRedoAction); - connect(editorWidget, &QPlainTextEdit::copyAvailable, - this, &TextEditorActionHandlerPrivate::updateCopyAction); - connect(editorWidget, &TextEditorWidget::readOnlyChanged, - this, &TextEditorActionHandlerPrivate::updateActions); - connect(editorWidget, &TextEditorWidget::optionalActionMaskChanged, - this, &TextEditorActionHandlerPrivate::updateOptionalActions); + m_currentEditorWidget = m_findTextWidget(editor); + if (m_currentEditorWidget) { + connect(m_currentEditorWidget, &QPlainTextEdit::undoAvailable, + this, &TextEditorActionHandlerPrivate::updateUndoAction); + connect(m_currentEditorWidget, &QPlainTextEdit::redoAvailable, + this, &TextEditorActionHandlerPrivate::updateRedoAction); + connect(m_currentEditorWidget, &QPlainTextEdit::copyAvailable, + this, &TextEditorActionHandlerPrivate::updateCopyAction); + connect(m_currentEditorWidget, &TextEditorWidget::readOnlyChanged, + this, &TextEditorActionHandlerPrivate::updateActions); + connect(m_currentEditorWidget, &TextEditorWidget::optionalActionMaskChanged, + this, &TextEditorActionHandlerPrivate::updateOptionalActions); + } } updateActions(); } @@ -614,4 +637,9 @@ void TextEditorActionHandler::setCanRedoCallback(const Predicate &callback) d->m_canRedoCallback = callback; } +void TextEditorActionHandler::setUnhandledCallback(const UnhandledCallback &callback) +{ + d->m_unhandledCallback = callback; +} + } // namespace TextEditor diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h index e1d06f7e3cd..344a4a553e1 100644 --- a/src/plugins/texteditor/texteditoractionhandler.h +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -58,6 +58,9 @@ public: void setCanUndoCallback(const Predicate &callback); void setCanRedoCallback(const Predicate &callback); + using UnhandledCallback = std::function; + void setUnhandledCallback(const UnhandledCallback &callback); + private: Internal::TextEditorActionHandlerPrivate *d; };