CompilerExplorer: Fix undo

Fixes context handling for the Editor. This allows Undo/Redo actions to
activate correctly.

Change-Id: Ieb7fa27215f5746cf5f26e8e7b3b74f44023481c
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Marcus Tillmanns
2023-09-26 10:43:33 +02:00
parent 399e12c973
commit fe430bebbe
7 changed files with 187 additions and 84 deletions

View File

@@ -5,5 +5,4 @@
namespace CompilerExplorer::Constants { namespace CompilerExplorer::Constants {
const char CE_EDITOR_ID[] = "CompilerExplorer.Editor"; const char CE_EDITOR_ID[] = "CompilerExplorer.Editor";
const char CE_EDITOR_CONTEXT_ID[] = "CompilerExplorer.Editor.Context";
} }

View File

@@ -249,7 +249,8 @@ QString SourceEditorWidget::sourceCode()
} }
CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSettings, CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings) const std::shared_ptr<CompilerSettings> &compilerSettings,
QUndoStack *undoStack)
: m_sourceSettings(sourceSettings) : m_sourceSettings(sourceSettings)
, m_compilerSettings(compilerSettings) , m_compilerSettings(compilerSettings)
{ {
@@ -266,7 +267,7 @@ CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSett
m_delayTimer, m_delayTimer,
qOverload<>(&QTimer::start)); qOverload<>(&QTimer::start));
m_asmEditor = new AsmEditorWidget; m_asmEditor = new AsmEditorWidget(undoStack);
m_asmDocument = QSharedPointer<TextDocument>(new TextDocument); m_asmDocument = QSharedPointer<TextDocument>(new TextDocument);
m_asmDocument->setFilePath("asm.asm"); m_asmDocument->setFilePath("asm.asm");
m_asmEditor->setTextDocument(m_asmDocument); m_asmEditor->setTextDocument(m_asmDocument);
@@ -498,10 +499,7 @@ EditorWidget::EditorWidget(const QSharedPointer<JsonSettingsDocument> &document,
actionHandler.updateCurrentEditor(); actionHandler.updateCurrentEditor();
}); });
m_context = new Core::IContext(this); setupHelpWidget();
m_context->setWidget(this);
m_context->setContext(Core::Context(Constants::CE_EDITOR_CONTEXT_ID));
Core::ICore::addContextObject(m_context);
} }
EditorWidget::~EditorWidget() EditorWidget::~EditorWidget()
@@ -521,7 +519,7 @@ void EditorWidget::addCompiler(const std::shared_ptr<SourceSettings> &sourceSett
int idx, int idx,
QDockWidget *parentDockWidget) QDockWidget *parentDockWidget)
{ {
auto compiler = new CompilerWidget(sourceSettings, compilerSettings); auto compiler = new CompilerWidget(sourceSettings, compilerSettings, m_undoStack);
compiler->setWindowTitle("Compiler #" + QString::number(idx)); compiler->setWindowTitle("Compiler #" + QString::number(idx));
compiler->setObjectName("compiler_" + QString::number(idx)); compiler->setObjectName("compiler_" + QString::number(idx));
QDockWidget *dockWidget = addDockForWidget(compiler, parentDockWidget); QDockWidget *dockWidget = addDockForWidget(compiler, parentDockWidget);
@@ -573,7 +571,6 @@ void EditorWidget::addSourceEditor(const std::shared_ptr<SourceSettings> &source
sourceSettings->compilers.clear(); sourceSettings->compilers.clear();
m_document->settings()->m_sources.removeItem(sourceSettings->shared_from_this()); m_document->settings()->m_sources.removeItem(sourceSettings->shared_from_this());
m_undoStack->endMacro(); m_undoStack->endMacro();
setupHelpWidget(); setupHelpWidget();
}); });
@@ -615,12 +612,16 @@ void EditorWidget::addSourceEditor(const std::shared_ptr<SourceSettings> &source
== compilerSettings; == compilerSettings;
}); });
QTC_ASSERT(it != m_compilerWidgets.end(), return); QTC_ASSERT(it != m_compilerWidgets.end(), return);
if (!m_sourceWidgets.isEmpty())
m_sourceWidgets.first()->widget()->setFocus(Qt::OtherFocusReason);
delete *it; delete *it;
m_compilerWidgets.erase(it); m_compilerWidgets.erase(it);
}); });
m_sourceWidgets.append(dockWidget); m_sourceWidgets.append(dockWidget);
sourceEditor->setFocus(Qt::OtherFocusReason);
setupHelpWidget(); setupHelpWidget();
} }
@@ -636,6 +637,8 @@ void EditorWidget::removeSourceEditor(const std::shared_ptr<SourceSettings> &sou
QTC_ASSERT(it != m_sourceWidgets.end(), return); QTC_ASSERT(it != m_sourceWidgets.end(), return);
delete *it; delete *it;
m_sourceWidgets.erase(it); m_sourceWidgets.erase(it);
setupHelpWidget();
} }
void EditorWidget::recreateEditors() void EditorWidget::recreateEditors()
@@ -677,24 +680,25 @@ void EditorWidget::setupHelpWidget()
{ {
if (m_document->settings()->m_sources.size() == 0) { if (m_document->settings()->m_sources.size() == 0) {
setCentralWidget(createHelpWidget()); setCentralWidget(createHelpWidget());
centralWidget()->setFocus(Qt::OtherFocusReason);
} else { } else {
delete takeCentralWidget(); delete takeCentralWidget();
} }
} }
QWidget *EditorWidget::createHelpWidget() const HelperWidget::HelperWidget()
{ {
using namespace Layouting; using namespace Layouting;
setFocusPolicy(Qt::ClickFocus);
setAttribute(Qt::WA_TransparentForMouseEvents, false);
auto addSourceButton = new QPushButton(Tr::tr("Add source code")); auto addSourceButton = new QPushButton(Tr::tr("Add source code"));
connect(addSourceButton, &QPushButton::clicked, this, [this] {
auto newSource = std::make_shared<SourceSettings>( connect(addSourceButton, &QPushButton::clicked, this, &HelperWidget::addSource);
[settings = m_document->settings()] { return settings->apiConfig(); });
m_document->settings()->m_sources.addItem(newSource);
});
// clang-format off // clang-format off
return Column { Column {
st, st,
Row { Row {
st, st,
@@ -705,10 +709,30 @@ QWidget *EditorWidget::createHelpWidget() const
st, st,
}, },
st, st,
}.emerge(); }.attachTo(this);
// clang-format on // clang-format on
} }
void HelperWidget::mousePressEvent(QMouseEvent *event)
{
setFocus(Qt::MouseFocusReason);
event->accept();
}
void EditorWidget::addNewSource()
{
auto newSource = std::make_shared<SourceSettings>(
[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 TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const
{ {
for (const QDockWidget *sourceWidget : m_sourceWidgets) { for (const QDockWidget *sourceWidget : m_sourceWidgets) {
@@ -728,49 +752,40 @@ TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const
return nullptr; return nullptr;
} }
class Editor : public Core::IEditor Editor::Editor(TextEditorActionHandler &actionHandler)
: m_document(new JsonSettingsDocument(&m_undoStack))
{ {
public: setContext(Core::Context(Constants::CE_EDITOR_ID));
Editor(TextEditorActionHandler &actionHandler) setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler));
: m_document(new JsonSettingsDocument(&m_undoStack))
{
setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler));
connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] { connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] {
actionHandler.updateActions(); actionHandler.updateActions();
}); });
connect(&m_undoStack, &QUndoStack::canRedoChanged, this, [&actionHandler] { connect(&m_undoStack, &QUndoStack::canRedoChanged, this, [&actionHandler] {
actionHandler.updateActions(); actionHandler.updateActions();
}); });
} }
~Editor() Editor::~Editor()
{ {
if (m_document->isModified()) { delete widget();
auto settings = m_document->settings(); }
if (settings->isDirty()) {
settings->apply();
Utils::Store store;
settings->toMap(store);
QJsonDocument doc = QJsonDocument::fromVariant(Utils::mapFromStore(store));
CompilerExplorer::settings().defaultDocument.setValue( static bool childHasFocus(QWidget *parent)
QString::fromUtf8(doc.toJson())); {
} if (parent->hasFocus())
} return true;
delete widget();
}
Core::IDocument *document() const override { return m_document.data(); } for (QWidget *child : parent->findChildren<QWidget *>())
QWidget *toolBar() override { return nullptr; } if (childHasFocus(child))
return true;
QSharedPointer<JsonSettingsDocument> m_document; return false;
QUndoStack m_undoStack; }
};
EditorFactory::EditorFactory() EditorFactory::EditorFactory()
: m_actionHandler(Constants::CE_EDITOR_ID, : m_actionHandler(Constants::CE_EDITOR_ID,
Constants::CE_EDITOR_CONTEXT_ID, Constants::CE_EDITOR_ID,
TextEditor::TextEditorActionHandler::None, TextEditor::TextEditorActionHandler::None,
[](Core::IEditor *editor) -> TextEditorWidget * { [](Core::IEditor *editor) -> TextEditorWidget * {
return static_cast<EditorWidget *>(editor->widget())->focusedEditorWidget(); return static_cast<EditorWidget *>(editor->widget())->focusedEditorWidget();
@@ -798,7 +813,32 @@ EditorFactory::EditorFactory()
return false; 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); }); setEditorCreator([this]() { return new Editor(m_actionHandler); });
} }
AsmEditorWidget::AsmEditorWidget(QUndoStack *stack)
: m_undoStack(stack)
{}
} // namespace CompilerExplorer } // namespace CompilerExplorer

View File

@@ -61,7 +61,7 @@ class AsmEditorWidget : public TextEditor::TextEditorWidget
Q_OBJECT Q_OBJECT
public: public:
using TextEditor::TextEditorWidget::TextEditorWidget; AsmEditorWidget(QUndoStack *undoStack);
void focusInEvent(QFocusEvent *event) override void focusInEvent(QFocusEvent *event) override
{ {
@@ -69,8 +69,14 @@ public:
emit gotFocus(); emit gotFocus();
} }
void undo() override { m_undoStack->undo(); }
void redo() override { m_undoStack->redo(); }
signals: signals:
void gotFocus(); void gotFocus();
private:
QUndoStack *m_undoStack;
}; };
class JsonSettingsDocument : public Core::IDocument class JsonSettingsDocument : public Core::IDocument
@@ -138,7 +144,8 @@ class CompilerWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSettings, CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings); const std::shared_ptr<CompilerSettings> &compilerSettings,
QUndoStack *undoStack);
Core::SearchableTerminal *createTerminal(); Core::SearchableTerminal *createTerminal();
@@ -172,6 +179,19 @@ private:
QList<TextEditor::TextMark *> m_marks; QList<TextEditor::TextMark *> m_marks;
}; };
class HelperWidget : public QWidget
{
Q_OBJECT
public:
HelperWidget();
protected:
void mousePressEvent(QMouseEvent *event) override;
signals:
void addSource();
};
class EditorWidget : public Utils::FancyMainWindow class EditorWidget : public Utils::FancyMainWindow
{ {
Q_OBJECT Q_OBJECT
@@ -194,6 +214,8 @@ protected:
void setupHelpWidget(); void setupHelpWidget();
QWidget *createHelpWidget() const; QWidget *createHelpWidget() const;
void addNewSource();
void addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings, void addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings, const std::shared_ptr<CompilerSettings> &compilerSettings,
int idx, int idx,
@@ -207,8 +229,6 @@ protected:
QVariantMap windowStateCallback(); QVariantMap windowStateCallback();
private: private:
Core::IContext *m_context;
QSharedPointer<JsonSettingsDocument> m_document; QSharedPointer<JsonSettingsDocument> m_document;
QUndoStack *m_undoStack; QUndoStack *m_undoStack;
TextEditor::TextEditorActionHandler &m_actionHandler; TextEditor::TextEditorActionHandler &m_actionHandler;
@@ -217,6 +237,19 @@ private:
QList<QDockWidget *> m_sourceWidgets; QList<QDockWidget *> 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<JsonSettingsDocument> m_document;
QUndoStack m_undoStack;
};
class EditorFactory : public Core::IEditorFactory class EditorFactory : public Core::IEditorFactory
{ {
public: public:

View File

@@ -38,7 +38,7 @@ public:
auto action = new QAction(Tr::tr("Open Compiler Explorer"), this); auto action = new QAction(Tr::tr("Open Compiler Explorer"), this);
connect(action, &QAction::triggered, this, [] { connect(action, &QAction::triggered, this, [] {
QString name("Compiler Explorer"); QString name("Compiler Explorer $");
Core::EditorManager::openEditorWithContents(Constants::CE_EDITOR_ID, Core::EditorManager::openEditorWithContents(Constants::CE_EDITOR_ID,
&name, &name,
settings().defaultDocument().toUtf8()); settings().defaultDocument().toUtf8());

View File

@@ -199,7 +199,6 @@ void CompilerSettings::fillLibraries(const LibrarySelectionAspect::ResultCallbac
auto future = Api::libraries(m_apiConfigFunction(), lang); auto future = Api::libraries(m_apiConfigFunction(), lang);
auto watcher = new QFutureWatcher<Api::Libraries>(this); auto watcher = new QFutureWatcher<Api::Libraries>(this);
watcher->setFuture(future);
QObject::connect(watcher, QObject::connect(watcher,
&QFutureWatcher<Api::Libraries>::finished, &QFutureWatcher<Api::Libraries>::finished,
this, this,
@@ -212,6 +211,7 @@ void CompilerSettings::fillLibraries(const LibrarySelectionAspect::ResultCallbac
return; return;
} }
}); });
watcher->setFuture(future);
} }
void SourceSettings::fillLanguageIdModel(const Utils::StringSelectionAspect::ResultCallback &cb) 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 future = Api::languages(m_apiConfigFunction());
auto watcher = new QFutureWatcher<Api::Languages>(this); auto watcher = new QFutureWatcher<Api::Languages>(this);
watcher->setFuture(future);
QObject::connect(watcher, QObject::connect(watcher,
&QFutureWatcher<Api::Languages>::finished, &QFutureWatcher<Api::Languages>::finished,
this, this,
@@ -254,6 +253,7 @@ void SourceSettings::fillLanguageIdModel(const Utils::StringSelectionAspect::Res
return; return;
} }
}); });
watcher->setFuture(future);
} }
void CompilerSettings::fillCompilerModel(const Utils::StringSelectionAspect::ResultCallback &cb) 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 future = Api::compilers(m_apiConfigFunction(), m_languageId);
auto watcher = new QFutureWatcher<Api::Compilers>(this); auto watcher = new QFutureWatcher<Api::Compilers>(this);
watcher->setFuture(future);
QObject::connect(watcher, QObject::connect(watcher,
&QFutureWatcher<Api::Compilers>::finished, &QFutureWatcher<Api::Compilers>::finished,
this, this,
@@ -295,6 +294,7 @@ void CompilerSettings::fillCompilerModel(const Utils::StringSelectionAspect::Res
return; return;
} }
}); });
watcher->setFuture(future);
} }
CompilerExplorerSettings::CompilerExplorerSettings() CompilerExplorerSettings::CompilerExplorerSettings()

View File

@@ -62,8 +62,18 @@ public:
Utils::Id menueGroup = Utils::Id(), Utils::Id menueGroup = Utils::Id(),
Core::ActionContainer *container = nullptr) Core::ActionContainer *container = nullptr)
{ {
return registerActionHelper(id, scriptable, title, keySequence, menueGroup, container, return registerActionHelper(id,
[this, slot](bool) { if (m_currentEditorWidget) slot(m_currentEditorWidget); }); 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, QAction *registerBoolAction(Utils::Id id,
@@ -135,6 +145,8 @@ public:
TextEditorActionHandler::Predicate m_canUndoCallback; TextEditorActionHandler::Predicate m_canUndoCallback;
TextEditorActionHandler::Predicate m_canRedoCallback; TextEditorActionHandler::Predicate m_canRedoCallback;
TextEditorActionHandler::UnhandledCallback m_unhandledCallback;
}; };
TextEditorActionHandlerPrivate::TextEditorActionHandlerPrivate TextEditorActionHandlerPrivate::TextEditorActionHandlerPrivate
@@ -478,17 +490,28 @@ void TextEditorActionHandlerPrivate::updateActions()
m_textWrappingAction->setChecked(m_currentEditorWidget->displaySettings().m_textWrapping); m_textWrappingAction->setChecked(m_currentEditorWidget->displaySettings().m_textWrapping);
} }
if (m_currentEditorWidget) { bool canRedo = false;
updateRedoAction(m_canRedoCallback ? m_canRedoCallback(m_currentEditor) bool canUndo = false;
: m_currentEditorWidget->document()->isRedoAvailable()); bool canCopy = false;
updateUndoAction(m_canUndoCallback ? m_canUndoCallback(m_currentEditor)
: m_currentEditorWidget->document()->isUndoAvailable()); if (m_currentEditor && m_currentEditor->document()
updateCopyAction(m_currentEditorWidget->textCursor().hasSelection()); && m_currentEditor->document()->id() == m_editorId) {
} else { canRedo = m_canRedoCallback ? m_canRedoCallback(m_currentEditor) : false;
updateRedoAction(false); canUndo = m_canUndoCallback ? m_canUndoCallback(m_currentEditor) : false;
updateUndoAction(false);
updateCopyAction(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(); updateOptionalActions();
} }
@@ -553,19 +576,19 @@ void TextEditorActionHandlerPrivate::updateCurrentEditor(Core::IEditor *editor)
m_currentEditor = editor; m_currentEditor = editor;
if (editor && editor->document()->id() == m_editorId) { if (editor && editor->document()->id() == m_editorId) {
TextEditorWidget *editorWidget = m_findTextWidget(editor); m_currentEditorWidget = m_findTextWidget(editor);
QTC_ASSERT(editorWidget, return); // editor has our id, so shouldn't happen if (m_currentEditorWidget) {
m_currentEditorWidget = editorWidget; connect(m_currentEditorWidget, &QPlainTextEdit::undoAvailable,
connect(editorWidget, &QPlainTextEdit::undoAvailable, this, &TextEditorActionHandlerPrivate::updateUndoAction);
this, &TextEditorActionHandlerPrivate::updateUndoAction); connect(m_currentEditorWidget, &QPlainTextEdit::redoAvailable,
connect(editorWidget, &QPlainTextEdit::redoAvailable, this, &TextEditorActionHandlerPrivate::updateRedoAction);
this, &TextEditorActionHandlerPrivate::updateRedoAction); connect(m_currentEditorWidget, &QPlainTextEdit::copyAvailable,
connect(editorWidget, &QPlainTextEdit::copyAvailable, this, &TextEditorActionHandlerPrivate::updateCopyAction);
this, &TextEditorActionHandlerPrivate::updateCopyAction); connect(m_currentEditorWidget, &TextEditorWidget::readOnlyChanged,
connect(editorWidget, &TextEditorWidget::readOnlyChanged, this, &TextEditorActionHandlerPrivate::updateActions);
this, &TextEditorActionHandlerPrivate::updateActions); connect(m_currentEditorWidget, &TextEditorWidget::optionalActionMaskChanged,
connect(editorWidget, &TextEditorWidget::optionalActionMaskChanged, this, &TextEditorActionHandlerPrivate::updateOptionalActions);
this, &TextEditorActionHandlerPrivate::updateOptionalActions); }
} }
updateActions(); updateActions();
} }
@@ -614,4 +637,9 @@ void TextEditorActionHandler::setCanRedoCallback(const Predicate &callback)
d->m_canRedoCallback = callback; d->m_canRedoCallback = callback;
} }
void TextEditorActionHandler::setUnhandledCallback(const UnhandledCallback &callback)
{
d->m_unhandledCallback = callback;
}
} // namespace TextEditor } // namespace TextEditor

View File

@@ -58,6 +58,9 @@ public:
void setCanUndoCallback(const Predicate &callback); void setCanUndoCallback(const Predicate &callback);
void setCanRedoCallback(const Predicate &callback); void setCanRedoCallback(const Predicate &callback);
using UnhandledCallback = std::function<void(Utils::Id commandId, Core::IEditor *editor)>;
void setUnhandledCallback(const UnhandledCallback &callback);
private: private:
Internal::TextEditorActionHandlerPrivate *d; Internal::TextEditorActionHandlerPrivate *d;
}; };