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 {
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,
const std::shared_ptr<CompilerSettings> &compilerSettings)
const std::shared_ptr<CompilerSettings> &compilerSettings,
QUndoStack *undoStack)
: m_sourceSettings(sourceSettings)
, m_compilerSettings(compilerSettings)
{
@@ -266,7 +267,7 @@ CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSett
m_delayTimer,
qOverload<>(&QTimer::start));
m_asmEditor = new AsmEditorWidget;
m_asmEditor = new AsmEditorWidget(undoStack);
m_asmDocument = QSharedPointer<TextDocument>(new TextDocument);
m_asmDocument->setFilePath("asm.asm");
m_asmEditor->setTextDocument(m_asmDocument);
@@ -498,10 +499,7 @@ EditorWidget::EditorWidget(const QSharedPointer<JsonSettingsDocument> &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<SourceSettings> &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<SourceSettings> &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<SourceSettings> &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<SourceSettings> &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<SourceSettings>(
[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<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
{
for (const QDockWidget *sourceWidget : m_sourceWidgets) {
@@ -728,12 +752,10 @@ TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const
return nullptr;
}
class Editor : public Core::IEditor
{
public:
Editor(TextEditorActionHandler &actionHandler)
Editor::Editor(TextEditorActionHandler &actionHandler)
: m_document(new JsonSettingsDocument(&m_undoStack))
{
setContext(Core::Context(Constants::CE_EDITOR_ID));
setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler));
connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] {
@@ -744,33 +766,26 @@ public:
});
}
~Editor()
Editor::~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));
CompilerExplorer::settings().defaultDocument.setValue(
QString::fromUtf8(doc.toJson()));
}
}
delete widget();
}
Core::IDocument *document() const override { return m_document.data(); }
QWidget *toolBar() override { return nullptr; }
static bool childHasFocus(QWidget *parent)
{
if (parent->hasFocus())
return true;
QSharedPointer<JsonSettingsDocument> m_document;
QUndoStack m_undoStack;
};
for (QWidget *child : parent->findChildren<QWidget *>())
if (childHasFocus(child))
return true;
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<EditorWidget *>(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

View File

@@ -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> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings);
const std::shared_ptr<CompilerSettings> &compilerSettings,
QUndoStack *undoStack);
Core::SearchableTerminal *createTerminal();
@@ -172,6 +179,19 @@ private:
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
{
Q_OBJECT
@@ -194,6 +214,8 @@ protected:
void setupHelpWidget();
QWidget *createHelpWidget() const;
void addNewSource();
void addCompiler(const std::shared_ptr<SourceSettings> &sourceSettings,
const std::shared_ptr<CompilerSettings> &compilerSettings,
int idx,
@@ -207,8 +229,6 @@ protected:
QVariantMap windowStateCallback();
private:
Core::IContext *m_context;
QSharedPointer<JsonSettingsDocument> m_document;
QUndoStack *m_undoStack;
TextEditor::TextEditorActionHandler &m_actionHandler;
@@ -217,6 +237,19 @@ private:
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
{
public:

View File

@@ -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());

View File

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

View File

@@ -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);
}
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) {
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);
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,20 +576,20 @@ 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,
m_currentEditorWidget = m_findTextWidget(editor);
if (m_currentEditorWidget) {
connect(m_currentEditorWidget, &QPlainTextEdit::undoAvailable,
this, &TextEditorActionHandlerPrivate::updateUndoAction);
connect(editorWidget, &QPlainTextEdit::redoAvailable,
connect(m_currentEditorWidget, &QPlainTextEdit::redoAvailable,
this, &TextEditorActionHandlerPrivate::updateRedoAction);
connect(editorWidget, &QPlainTextEdit::copyAvailable,
connect(m_currentEditorWidget, &QPlainTextEdit::copyAvailable,
this, &TextEditorActionHandlerPrivate::updateCopyAction);
connect(editorWidget, &TextEditorWidget::readOnlyChanged,
connect(m_currentEditorWidget, &TextEditorWidget::readOnlyChanged,
this, &TextEditorActionHandlerPrivate::updateActions);
connect(editorWidget, &TextEditorWidget::optionalActionMaskChanged,
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

View File

@@ -58,6 +58,9 @@ public:
void setCanUndoCallback(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:
Internal::TextEditorActionHandlerPrivate *d;
};