2023-02-20 08:56:33 +01:00
|
|
|
// Copyright (C) 2023 Tasuku Suzuki
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
#include "markdowneditor.h"
|
|
|
|
|
|
|
|
|
|
#include "textdocument.h"
|
|
|
|
|
#include "texteditor.h"
|
|
|
|
|
#include "texteditortr.h"
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/coreconstants.h>
|
|
|
|
|
#include <coreplugin/coreplugintr.h>
|
2023-04-19 11:03:58 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2023-02-20 08:56:33 +01:00
|
|
|
#include <coreplugin/minisplitter.h>
|
2023-04-19 11:03:58 +02:00
|
|
|
#include <utils/stringutils.h>
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QScrollBar>
|
|
|
|
|
#include <QTextBrowser>
|
|
|
|
|
#include <QToolButton>
|
|
|
|
|
|
|
|
|
|
namespace TextEditor::Internal {
|
|
|
|
|
|
|
|
|
|
const char MARKDOWNVIEWER_ID[] = "Editors.MarkdownViewer";
|
2023-04-19 15:09:49 +02:00
|
|
|
const char MARKDOWNVIEWER_TEXT_CONTEXT[] = "Editors.MarkdownViewer.Text";
|
2023-02-20 08:56:33 +01:00
|
|
|
const char MARKDOWNVIEWER_MIME_TYPE[] = "text/markdown";
|
2023-04-17 12:48:24 +02:00
|
|
|
const char MARKDOWNVIEWER_TEXTEDITOR_RIGHT[] = "Markdown.TextEditorRight";
|
|
|
|
|
const bool kTextEditorRightDefault = true;
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
class MarkdownEditor : public Core::IEditor
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MarkdownEditor()
|
|
|
|
|
: m_document(new TextDocument(MARKDOWNVIEWER_ID))
|
|
|
|
|
{
|
|
|
|
|
m_document->setMimeType(MARKDOWNVIEWER_MIME_TYPE);
|
|
|
|
|
|
2023-04-17 12:48:24 +02:00
|
|
|
QSettings *s = Core::ICore::settings();
|
|
|
|
|
const bool textEditorRight
|
|
|
|
|
= s->value(MARKDOWNVIEWER_TEXTEDITOR_RIGHT, kTextEditorRightDefault).toBool();
|
|
|
|
|
|
2023-02-20 08:56:33 +01:00
|
|
|
// Left side
|
2023-04-17 12:48:24 +02:00
|
|
|
auto browser = new QTextBrowser();
|
2023-02-20 08:56:33 +01:00
|
|
|
browser->setOpenExternalLinks(true);
|
|
|
|
|
browser->setFrameShape(QFrame::NoFrame);
|
2023-04-19 11:03:58 +02:00
|
|
|
new Utils::MarkdownHighlighter(browser->document());
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
// Right side (hidable)
|
2023-04-17 12:48:24 +02:00
|
|
|
m_textEditorWidget = new TextEditorWidget;
|
2023-04-19 15:09:49 +02:00
|
|
|
m_textEditorWidget->setTextDocument(m_document);
|
|
|
|
|
m_textEditorWidget->setupGenericHighlighter();
|
|
|
|
|
m_textEditorWidget->setMarksVisible(false);
|
|
|
|
|
auto context = new Core::IContext(this);
|
|
|
|
|
context->setWidget(m_textEditorWidget);
|
|
|
|
|
context->setContext(Core::Context(MARKDOWNVIEWER_TEXT_CONTEXT));
|
|
|
|
|
Core::ICore::addContextObject(context);
|
2023-02-20 08:56:33 +01:00
|
|
|
|
2023-04-17 12:48:24 +02:00
|
|
|
if (textEditorRight) {
|
|
|
|
|
m_widget.addWidget(browser);
|
|
|
|
|
m_widget.addWidget(m_textEditorWidget);
|
|
|
|
|
} else {
|
|
|
|
|
m_widget.addWidget(m_textEditorWidget);
|
|
|
|
|
m_widget.addWidget(browser);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-20 08:56:33 +01:00
|
|
|
setContext(Core::Context(MARKDOWNVIEWER_ID));
|
|
|
|
|
setWidget(&m_widget);
|
|
|
|
|
|
2023-04-24 13:10:08 +02:00
|
|
|
auto togglePreviewVisible = new QToolButton;
|
|
|
|
|
togglePreviewVisible->setText(Tr::tr("Show Preview"));
|
|
|
|
|
togglePreviewVisible->setCheckable(true);
|
|
|
|
|
togglePreviewVisible->setChecked(true);
|
|
|
|
|
|
2023-02-20 08:56:33 +01:00
|
|
|
auto toggleEditorVisible = new QToolButton;
|
2023-04-24 13:10:08 +02:00
|
|
|
toggleEditorVisible->setText(Tr::tr("Show Editor"));
|
2023-02-20 08:56:33 +01:00
|
|
|
toggleEditorVisible->setCheckable(true);
|
|
|
|
|
toggleEditorVisible->setChecked(true);
|
|
|
|
|
|
2023-04-17 12:48:24 +02:00
|
|
|
auto swapViews = new QToolButton;
|
|
|
|
|
swapViews->setText(Tr::tr("Swap Views"));
|
|
|
|
|
|
2023-04-24 13:10:08 +02:00
|
|
|
auto toolbarLayout = new QHBoxLayout(&m_toolbar);
|
|
|
|
|
toolbarLayout->setSpacing(0);
|
|
|
|
|
toolbarLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
toolbarLayout->addStretch();
|
|
|
|
|
if (textEditorRight) {
|
|
|
|
|
toolbarLayout->addWidget(togglePreviewVisible);
|
|
|
|
|
toolbarLayout->addWidget(toggleEditorVisible);
|
|
|
|
|
} else {
|
|
|
|
|
toolbarLayout->addWidget(toggleEditorVisible);
|
|
|
|
|
toolbarLayout->addWidget(togglePreviewVisible);
|
|
|
|
|
}
|
|
|
|
|
toolbarLayout->addWidget(swapViews);
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
connect(m_document.data(), &TextDocument::mimeTypeChanged,
|
|
|
|
|
m_document.data(), &TextDocument::changed);
|
|
|
|
|
|
2023-04-24 13:10:08 +02:00
|
|
|
const auto viewToggled =
|
|
|
|
|
[swapViews](QWidget *view, bool visible, QWidget *otherView, QToolButton *otherButton) {
|
|
|
|
|
if (view->isVisible() == visible)
|
|
|
|
|
return;
|
|
|
|
|
view->setVisible(visible);
|
|
|
|
|
if (visible) {
|
|
|
|
|
view->setFocus();
|
|
|
|
|
} else if (otherView->isVisible()) {
|
|
|
|
|
otherView->setFocus();
|
|
|
|
|
} else {
|
|
|
|
|
// make sure at least one view is visible
|
|
|
|
|
otherButton->toggle();
|
|
|
|
|
}
|
|
|
|
|
swapViews->setEnabled(view->isVisible() && otherView->isVisible());
|
|
|
|
|
};
|
|
|
|
|
|
2023-04-19 15:09:49 +02:00
|
|
|
connect(toggleEditorVisible,
|
|
|
|
|
&QToolButton::toggled,
|
2023-04-24 13:10:08 +02:00
|
|
|
this,
|
|
|
|
|
[this, browser, togglePreviewVisible, viewToggled](bool visible) {
|
|
|
|
|
viewToggled(m_textEditorWidget, visible, browser, togglePreviewVisible);
|
|
|
|
|
});
|
|
|
|
|
connect(togglePreviewVisible,
|
|
|
|
|
&QToolButton::toggled,
|
|
|
|
|
this,
|
|
|
|
|
[this, browser, toggleEditorVisible, viewToggled](bool visible) {
|
|
|
|
|
viewToggled(browser, visible, m_textEditorWidget, toggleEditorVisible);
|
2023-04-19 15:09:49 +02:00
|
|
|
});
|
2023-02-20 08:56:33 +01:00
|
|
|
|
2023-04-24 13:10:08 +02:00
|
|
|
connect(swapViews, &QToolButton::clicked, m_textEditorWidget, [this, toolbarLayout] {
|
2023-04-17 12:48:24 +02:00
|
|
|
QTC_ASSERT(m_widget.count() > 1, return);
|
2023-04-24 13:10:08 +02:00
|
|
|
// switch views
|
2023-04-17 12:48:24 +02:00
|
|
|
auto placeholder = std::make_unique<QWidget>();
|
|
|
|
|
auto second = m_widget.replaceWidget(1, placeholder.get());
|
|
|
|
|
auto first = m_widget.replaceWidget(0, second);
|
|
|
|
|
m_widget.replaceWidget(1, first);
|
2023-04-24 13:10:08 +02:00
|
|
|
// switch buttons
|
|
|
|
|
const int rightIndex = toolbarLayout->count() - 2;
|
|
|
|
|
QLayoutItem *right = toolbarLayout->takeAt(rightIndex);
|
|
|
|
|
toolbarLayout->insertItem(rightIndex - 1, right);
|
|
|
|
|
// save settings
|
2023-04-17 12:48:24 +02:00
|
|
|
Utils::QtcSettings *s = Core::ICore::settings();
|
|
|
|
|
s->setValueWithDefault(MARKDOWNVIEWER_TEXTEDITOR_RIGHT,
|
|
|
|
|
!s->value(MARKDOWNVIEWER_TEXTEDITOR_RIGHT,
|
|
|
|
|
kTextEditorRightDefault)
|
|
|
|
|
.toBool(),
|
|
|
|
|
kTextEditorRightDefault);
|
|
|
|
|
});
|
|
|
|
|
|
2023-02-20 08:56:33 +01:00
|
|
|
connect(m_document->document(), &QTextDocument::contentsChanged, this, [this, browser] {
|
|
|
|
|
QHash<QScrollBar *, int> positions;
|
2023-02-22 16:47:55 +01:00
|
|
|
const auto scrollBars = browser->findChildren<QScrollBar *>();
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
// save scroll positions
|
|
|
|
|
for (QScrollBar *scrollBar : scrollBars)
|
|
|
|
|
positions.insert(scrollBar, scrollBar->value());
|
|
|
|
|
|
|
|
|
|
browser->setMarkdown(m_document->plainText());
|
|
|
|
|
|
|
|
|
|
// restore scroll positions
|
|
|
|
|
for (QScrollBar *scrollBar : scrollBars)
|
|
|
|
|
scrollBar->setValue(positions.value(scrollBar));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QWidget *toolBar() override { return &m_toolbar; }
|
|
|
|
|
|
|
|
|
|
Core::IDocument *document() const override { return m_document.data(); }
|
2023-04-19 15:09:49 +02:00
|
|
|
TextEditorWidget *textEditorWidget() const { return m_textEditorWidget; }
|
2023-02-20 08:56:33 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Core::MiniSplitter m_widget;
|
2023-04-19 15:09:49 +02:00
|
|
|
TextEditorWidget *m_textEditorWidget;
|
2023-02-20 08:56:33 +01:00
|
|
|
TextDocumentPtr m_document;
|
|
|
|
|
QWidget m_toolbar;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MarkdownEditorFactory::MarkdownEditorFactory()
|
2023-04-19 15:09:49 +02:00
|
|
|
: m_actionHandler(MARKDOWNVIEWER_ID,
|
|
|
|
|
MARKDOWNVIEWER_TEXT_CONTEXT,
|
|
|
|
|
TextEditor::TextEditorActionHandler::None,
|
|
|
|
|
[](Core::IEditor *editor) {
|
|
|
|
|
return static_cast<MarkdownEditor *>(editor)->textEditorWidget();
|
|
|
|
|
})
|
2023-02-20 08:56:33 +01:00
|
|
|
{
|
|
|
|
|
setId(MARKDOWNVIEWER_ID);
|
|
|
|
|
setDisplayName(::Core::Tr::tr("Markdown Viewer"));
|
|
|
|
|
addMimeType(MARKDOWNVIEWER_MIME_TYPE);
|
|
|
|
|
setEditorCreator([] { return new MarkdownEditor; });
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 12:48:24 +02:00
|
|
|
} // namespace TextEditor::Internal
|