support local PlantUml and Graphviz (#1776)

This commit is contained in:
Le Tan 2021-05-28 18:24:55 -07:00 committed by GitHub
parent 1d4e2b14b6
commit 79abddd802
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1168 additions and 46 deletions

@ -1 +1 @@
Subproject commit 9b9aa9dd1d8ebec02daee23cb26d0b12ae00cf69
Subproject commit 5e02a011cb7e4979e897c0670fe8ac8f1060feb4

View File

@ -24,7 +24,7 @@
using namespace vnotex;
#ifndef QT_NO_DEBUG
// #define VX_DEBUG_WEB
// #define VX_DEBUG_WEB
#endif
const QString ConfigMgr::c_orgName = "VNote";

View File

@ -6,6 +6,8 @@
#include <QJsonObject>
#include <QScopedPointer>
#include "noncopyable.h"
namespace vnotex
{
class MainConfig;
@ -14,7 +16,7 @@ namespace vnotex
class EditorConfig;
class WidgetConfig;
class ConfigMgr : public QObject
class ConfigMgr : public QObject, private Noncopyable
{
Q_OBJECT
public:

View File

@ -47,6 +47,7 @@ HEADERS += \
$$PWD/logger.h \
$$PWD/mainconfig.h \
$$PWD/markdowneditorconfig.h \
$$PWD/noncopyable.h \
$$PWD/quickaccesshelper.h \
$$PWD/singleinstanceguard.h \
$$PWD/iconfig.h \

View File

@ -11,6 +11,9 @@ namespace vnotex
{
ViewWindowMode m_mode = ViewWindowMode::Read;
// Force to enter m_mode.
bool m_forceMode = false;
// Whether focus to the opened window.
bool m_focus = true;

View File

@ -30,8 +30,15 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u
loadExportResource(appObj, userObj);
m_webPlantUml = READBOOL(QStringLiteral("web_plantuml"));
m_plantUmlJar = READSTR(QStringLiteral("plantuml_jar"));
m_plantUmlCommand = READSTR(QStringLiteral("plantuml_command"));
m_webGraphviz = READBOOL(QStringLiteral("web_graphviz"));
m_graphvizExe = READSTR(QStringLiteral("graphviz_exe"));
m_prependDotInRelativeLink = READBOOL(QStringLiteral("prepend_dot_in_relative_link"));
m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images"));
m_insertFileNameAsTitle = READBOOL(QStringLiteral("insert_file_name_as_title"));
@ -63,7 +70,10 @@ QJsonObject MarkdownEditorConfig::toJson() const
obj[QStringLiteral("viewer_resource")] = saveViewerResource();
obj[QStringLiteral("export_resource")] = saveExportResource();
obj[QStringLiteral("web_plantuml")] = m_webPlantUml;
obj[QStringLiteral("plantuml_jar")] = m_plantUmlJar;
obj[QStringLiteral("plantuml_command")] = m_plantUmlCommand;
obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
obj[QStringLiteral("graphviz_exe")] = m_graphvizExe;
obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages;
obj[QStringLiteral("insert_file_name_as_title")] = m_insertFileNameAsTitle;
@ -167,11 +177,46 @@ bool MarkdownEditorConfig::getWebPlantUml() const
return m_webPlantUml;
}
void MarkdownEditorConfig::setWebPlantUml(bool p_enabled)
{
updateConfig(m_webPlantUml, p_enabled, this);
}
const QString &MarkdownEditorConfig::getPlantUmlJar() const
{
return m_plantUmlJar;
}
void MarkdownEditorConfig::setPlantUmlJar(const QString &p_jar)
{
updateConfig(m_plantUmlJar, p_jar, this);
}
const QString &MarkdownEditorConfig::getPlantUmlCommand() const
{
return m_plantUmlCommand;
}
bool MarkdownEditorConfig::getWebGraphviz() const
{
return m_webGraphviz;
}
void MarkdownEditorConfig::setWebGraphviz(bool p_enabled)
{
updateConfig(m_webGraphviz, p_enabled, this);
}
const QString &MarkdownEditorConfig::getGraphvizExe() const
{
return m_graphvizExe;
}
void MarkdownEditorConfig::setGraphvizExe(const QString &p_exe)
{
updateConfig(m_graphvizExe, p_exe, this);
}
bool MarkdownEditorConfig::getPrependDotInRelativeLink() const
{
return m_prependDotInRelativeLink;

View File

@ -48,8 +48,18 @@ namespace vnotex
const WebResource &getExportResource() const;
bool getWebPlantUml() const;
void setWebPlantUml(bool p_enabled);
const QString &getPlantUmlJar() const;
void setPlantUmlJar(const QString &p_jar);
const QString &getPlantUmlCommand() const;
bool getWebGraphviz() const;
void setWebGraphviz(bool p_enabled);
const QString &getGraphvizExe() const;
void setGraphvizExe(const QString &p_exe);
bool getPrependDotInRelativeLink() const;
@ -124,8 +134,18 @@ namespace vnotex
// Whether use javascript or external program to render PlantUML.
bool m_webPlantUml = true;
// File path of the JAR to render PlantUmL.
QString m_plantUmlJar;
// Command to render PlantUml. If set, will ignore m_plantUmlJar.
// %1: the format to render in.
QString m_plantUmlCommand;
bool m_webGraphviz = true;
// Graphviz executable file.
QString m_graphvizExe;
// Whether prepend a dot in front of the relative link, like images.
bool m_prependDotInRelativeLink = false;

18
src/core/noncopyable.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef NONCOPYABLE_H
#define NONCOPYABLE_H
namespace vnotex
{
class Noncopyable
{
protected:
Noncopyable() = default;
virtual ~Noncopyable() = default;
Noncopyable(const Noncopyable&) = delete;
Noncopyable &operator=(const Noncopyable&) = delete;
};
}
#endif // NONCOPYABLE_H

View File

@ -4,6 +4,7 @@
#include <QObject>
#include <QScopedPointer>
#include "noncopyable.h"
#include "thememgr.h"
#include "global.h"
@ -18,7 +19,7 @@ namespace vnotex
class Notebook;
struct ComplexLocation;
class VNoteX : public QObject
class VNoteX : public QObject, private Noncopyable
{
Q_OBJECT
public:
@ -28,9 +29,6 @@ namespace vnotex
return inst;
}
VNoteX(const VNoteX &) = delete;
void operator=(const VNoteX &) = delete;
// MUST be called to load some heavy data.
// It is good to call it after MainWindow is shown.
void initLoad();

View File

@ -261,8 +261,15 @@
},
"//comment" : "Whether use javascript or external program to render PlantUML",
"web_plantuml" : true,
"//commnet" : "Local PlantUML JAR file to render PlantUML",
"plantuml_jar" : "",
"//commnet" : "Command to render PlantUML via stdin and stdout (overrides plantuml_jar)",
"//commnet" : "- %1: the format to render in",
"plantuml_command" : "",
"//comment" : "Whether use javascript or external program to render Graphviz",
"web_graphviz" : true,
"//commnet" : "Local Graphviz executable file to render Graphviz",
"graphviz_exe" : "",
"//comment" : "Whether prepend a dot at front in relative link like images",
"prepend_dot_in_relative_link" : false,
"//comment" : "Whether ask for user confirmation before clearing obsolete images",

View File

@ -73,6 +73,9 @@
"background-color" : "#333842",
"selected-text-color" : "#e3e5e9",
"selected-background-color" : "#0c7bff"
},
"Preview" : {
"background-color" : "#b0bec5"
}
},
"markdown-syntax-styles" : {

View File

@ -30,7 +30,7 @@ class GraphRenderer extends VxWorker {
registerInternal() {
this.vnotex.on('basicMarkdownRendered', () => {
this.reset();
this.renderCodeNodes(this.vnotex.contentContainer);
this.renderCodeNodes();
});
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
@ -78,8 +78,9 @@ class GraphRenderer extends VxWorker {
// Interface 2.
// Get code nodes from markdownIt directly.
renderCodeNodes(p_node) {
renderCodeNodes() {
this.nodesToRender = this.vnotex.getWorker('markdownit').getCodeNodes(this.langs);
this.numOfRenderedNodes = 0;
this.doRender();
}

View File

@ -14,21 +14,30 @@ class Graphviz extends GraphRenderer {
this.format = 'svg';
this.langs = ['dot'];
this.useWeb = true;
this.nextLocalGraphIndex = 1;
}
registerInternal() {
this.vnotex.on('basicMarkdownRendered', () => {
this.reset();
this.renderCodeNodes(this.vnotex.contentContainer,
window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
this.renderCodeNodes(window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
});
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
this.useWeb = window.vxOptions.webGraphviz;
if (!this.useWeb) {
this.extraScripts = [];
}
}
initialize(p_callback) {
return super.initialize(() => {
this.viz = new Viz();
if (this.useWeb) {
this.viz = new Viz();
}
p_callback();
});
}
@ -41,13 +50,22 @@ class Graphviz extends GraphRenderer {
}
// Interface 2.
renderCodeNodes(p_node, p_format) {
renderCodeNodes(p_format) {
this.format = p_format;
super.renderCodeNodes(p_node);
super.renderCodeNodes();
}
renderOne(p_node, p_idx) {
if (this.useWeb) {
this.renderOnline(p_node, p_idx);
} else {
this.renderLocal(p_node);
}
}
renderOnline(p_node, p_idx) {
console.assert(this.viz);
let func = function(p_graphviz, p_renderNode) {
let graphviz = p_graphviz;
let node = p_renderNode;
@ -85,13 +103,61 @@ class Graphviz extends GraphRenderer {
});
}
return true;
}
renderLocal(p_node) {
let func = function(p_graphviz, p_renderNode) {
let graphviz = p_graphviz;
let node = p_renderNode;
return function(format, data) {
if (node && data.length > 0) {
let obj = null;
if (format == 'svg') {
obj = document.createElement('div');
obj.classList.add(graphviz.graphDivClass);
obj.innerHTML = data;
window.vxImageViewer.setupSVGToView(obj.children[0], false);
} else {
obj = document.createElement('div');
obj.classList.add(graphviz.graphDivClass);
let imgObj = document.createElement('img');
obj.appendChild(imgObj);
imgObj.src = "data:image/" + format + ";base64, " + data;
window.vxImageViewer.setupIMGToView(imgObj);
}
Utils.checkSourceLine(p_node, obj);
Utils.replaceNodeWithPreCheck(p_node, obj);
}
graphviz.finishRenderingOne();
};
};
let callback = func(this, p_node);
this.vnotex.renderGraph(this.id,
this.nextLocalGraphIndex++,
this.format,
'dot',
p_node.textContent,
function(id, index, format, data) {
callback(format, data);
});
}
// Render a graph from @p_text in SVG format.
// p_callback(svgNode).
renderText(p_text, p_callback) {
console.assert(this.useWeb, "renderText() should be called only when web Graphviz is enabled");
let func = () => {
if (!this.viz) {
console.log("viz is not ready yet");
return;
}
this.viz.renderSVGElement(p_text)
.then(p_callback)
.catch(function(err) {

View File

@ -47,6 +47,10 @@ new QWebChannel(qt.webChannelTransport,
window.vnotex.saveContent();
});
adapter.graphRenderDataReady.connect(function(p_id, p_index, p_format, p_data) {
window.vnotex.graphRenderDataReady(p_id, p_index, p_format, p_data);
});
console.log('QWebChannel has been set up');
if (window.vnotex.initialized) {
window.vnotex.kickOffMarkdown();

View File

@ -14,16 +14,24 @@ class PlantUml extends GraphRenderer {
this.format = 'svg';
this.langs = ['plantuml', 'puml'];
this.useWeb = true;
this.nextLocalGraphIndex = 1;
}
registerInternal() {
this.vnotex.on('basicMarkdownRendered', () => {
this.reset();
this.renderCodeNodes(this.vnotex.contentContainer,
window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
this.renderCodeNodes(window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
});
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
this.useWeb = window.vxOptions.webPlantUml;
if (!this.useWeb) {
this.extraScripts = [];
}
}
@ -35,10 +43,10 @@ class PlantUml extends GraphRenderer {
}
// Interface 2.
renderCodeNodes(p_node, p_format) {
renderCodeNodes(p_format) {
this.format = p_format;
super.renderCodeNodes(p_node);
super.renderCodeNodes();
}
renderOne(p_node, p_idx) {
@ -46,20 +54,26 @@ class PlantUml extends GraphRenderer {
let plantUml = p_plantUml;
let node = p_node;
return function(p_format, p_data) {
plantUml.handlePlantUmlResult(node, 0, p_format, p_data);
plantUml.handlePlantUmlResult(node, p_format, p_data);
};
};
this.renderOnline(this.serverUrl,
this.format,
p_node.textContent,
func(this, p_node));
if (this.useWeb) {
this.renderOnline(this.serverUrl,
this.format,
p_node.textContent,
func(this, p_node));
} else {
this.renderLocal(this.format, p_node.textContent, func(this, p_node));
}
return true;
}
// Render a graph from @p_text in SVG format.
// p_callback(format, data).
renderText(p_text, p_callback) {
console.assert(this.useWeb, "renderText() should be called only when web PlantUml is enabled");
let func = () => {
this.renderOnline(this.serverUrl,
'svg',
@ -111,7 +125,19 @@ class PlantUml extends GraphRenderer {
return url;
}
handlePlantUmlResult(p_node, p_timeStamp, p_format, p_result) {
// A helper function to render PlantUml via local JAR.
renderLocal(p_format, p_text, p_callback) {
this.vnotex.renderGraph(this.id,
this.nextLocalGraphIndex++,
p_format,
'puml',
p_text,
function(id, index, format, data) {
p_callback(format, data);
});
}
handlePlantUmlResult(p_node, p_format, p_result) {
if (p_node && p_result.length > 0) {
let obj = null;
if (p_format == 'svg') {
@ -120,9 +146,13 @@ class PlantUml extends GraphRenderer {
obj.innerHTML = p_result;
window.vxImageViewer.setupSVGToView(obj.children[0], false);
} else {
obj = document.createElement('img');
obj.src = "data:image/" + p_format + ";base64, " + p_result;
window.vxImageViewer.setupIMGToView(obj);
obj = document.createElement('div');
obj.classList.add(this.graphDivClass);
let imgObj = document.createElement('img');
obj.appendChild(imgObj);
imgObj.src = "data:image/" + p_format + ";base64, " + p_result;
window.vxImageViewer.setupIMGToView(imgObj);
}
Utils.checkSourceLine(p_node, obj);

View File

@ -39,6 +39,9 @@ class VNoteX extends EventEmitter {
this.sectionNumberBaseLevel = 2;
// Dict mapping from {id, index} to callback for renderGraph().
this.renderGraphCallbacks = {}
window.addEventListener('load', () => {
console.log('window load finished');
@ -307,6 +310,19 @@ class VNoteX extends EventEmitter {
}
}
renderGraph(p_id, p_index, p_format, p_lang, p_text, p_callback) {
this.renderGraphCallbacks[p_id + '_' + p_index] = p_callback;
window.vxMarkdownAdapter.renderGraph(p_id, p_index, p_format, p_lang, p_text);
}
graphRenderDataReady(p_id, p_index, p_format, p_data) {
let key = p_id + '_' + p_index;
if (key in this.renderGraphCallbacks) {
this.renderGraphCallbacks[key](p_id, p_index, p_format, p_data);
delete this.renderGraphCallbacks[key];
}
}
static detectOS() {
let osName="Unknown OS";
if (navigator.appVersion.indexOf("Win")!=-1) {

View File

@ -3,6 +3,11 @@ class VxWorker {
constructor() {
this.name = '';
this.vnotex = null;
if (!window.vxWorkerId) {
window.vxWorkerId = 1;
}
this.id = window.vxWorkerId++;
}
// Called when registering this worker.

View File

@ -8,6 +8,8 @@
#include <QComboBox>
#include <QSpinBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <widgets/widgetsfactory.h>
#include <core/editorconfig.h>
@ -16,6 +18,10 @@
#include <utils/widgetutils.h>
#include "editorpage.h"
#include <widgets/locationinputwithbrowsebutton.h>
#include <widgets/messageboxhelper.h>
#include <widgets/editors/plantumlhelper.h>
#include <widgets/editors/graphvizhelper.h>
using namespace vnotex;
@ -76,6 +82,20 @@ void MarkdownEditorPage::loadInternal()
m_smartTableCheckBox->setChecked(markdownConfig.getSmartTableEnabled());
m_spellCheckCheckBox->setChecked(markdownConfig.isSpellCheckEnabled());
{
int idx = m_plantUmlModeComboBox->findData(markdownConfig.getWebPlantUml() ? 0 : 1);
m_plantUmlModeComboBox->setCurrentIndex(idx);
}
m_plantUmlJarFileInput->setText(markdownConfig.getPlantUmlJar());
{
int idx = m_graphvizModeComboBox->findData(markdownConfig.getWebGraphviz() ? 0 : 1);
m_graphvizModeComboBox->setCurrentIndex(idx);
}
m_graphvizFileInput->setText(markdownConfig.getGraphvizExe());
}
void MarkdownEditorPage::saveInternal()
@ -118,6 +138,14 @@ void MarkdownEditorPage::saveInternal()
markdownConfig.setSpellCheckEnabled(m_spellCheckCheckBox->isChecked());
markdownConfig.setWebPlantUml(m_plantUmlModeComboBox->currentData().toInt() == 0);
markdownConfig.setPlantUmlJar(m_plantUmlJarFileInput->text());
markdownConfig.setWebGraphviz(m_graphvizModeComboBox->currentData().toInt() == 0);
markdownConfig.setGraphvizExe(m_graphvizFileInput->text());
EditorPage::notifyEditorConfigChange();
}
@ -264,7 +292,7 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup()
{
auto sectionLayout = new QHBoxLayout();
m_sectionNumberComboBox = WidgetsFactory::createComboBox(this);
m_sectionNumberComboBox = WidgetsFactory::createComboBox(box);
m_sectionNumberComboBox->setToolTip(tr("Section number mode"));
m_sectionNumberComboBox->addItem(tr("None"), (int)MarkdownEditorConfig::SectionNumberMode::None);
m_sectionNumberComboBox->addItem(tr("Read"), (int)MarkdownEditorConfig::SectionNumberMode::Read);
@ -273,7 +301,7 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup()
connect(m_sectionNumberComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MarkdownEditorPage::pageIsChanged);
m_sectionNumberBaseLevelSpinBox = WidgetsFactory::createSpinBox(this);
m_sectionNumberBaseLevelSpinBox = WidgetsFactory::createSpinBox(box);
m_sectionNumberBaseLevelSpinBox->setToolTip(tr("Base level to start section numbering in edit mode"));
m_sectionNumberBaseLevelSpinBox->setRange(1, 6);
m_sectionNumberBaseLevelSpinBox->setSingleStep(1);
@ -281,7 +309,7 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup()
connect(m_sectionNumberBaseLevelSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &MarkdownEditorPage::pageIsChanged);
m_sectionNumberStyleComboBox = WidgetsFactory::createComboBox(this);
m_sectionNumberStyleComboBox = WidgetsFactory::createComboBox(box);
m_sectionNumberStyleComboBox->setToolTip(tr("Section number style"));
m_sectionNumberStyleComboBox->addItem(tr("1.1."), (int)MarkdownEditorConfig::SectionNumberStyle::DigDotDigDot);
m_sectionNumberStyleComboBox->addItem(tr("1.1"), (int)MarkdownEditorConfig::SectionNumberStyle::DigDotDig);
@ -300,5 +328,122 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup()
addSearchItem(label, m_sectionNumberComboBox->toolTip(), m_sectionNumberComboBox);
}
{
m_plantUmlModeComboBox = WidgetsFactory::createComboBox(box);
m_plantUmlModeComboBox->setToolTip(tr("Use online service or local JAR file to render PlantUml graphs"));
m_plantUmlModeComboBox->addItem(tr("Online Service"), 0);
m_plantUmlModeComboBox->addItem(tr("Local JAR"), 1);
const QString label(tr("PlantUml:"));
layout->addRow(label, m_plantUmlModeComboBox);
addSearchItem(label, m_plantUmlModeComboBox->toolTip(), m_plantUmlModeComboBox);
connect(m_plantUmlModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MarkdownEditorPage::pageIsChanged);
}
{
auto jarLayout = new QHBoxLayout();
m_plantUmlJarFileInput = new LocationInputWithBrowseButton(box);
m_plantUmlJarFileInput->setToolTip(tr("Local JAR file to render PlantUML graphs"));
connect(m_plantUmlJarFileInput, &LocationInputWithBrowseButton::clicked,
this, [this]() {
auto filePath = QFileDialog::getOpenFileName(this,
tr("Select PlantUml JAR File"),
QDir::homePath(),
"PlantUml JAR (*.jar)");
if (!filePath.isEmpty()) {
m_plantUmlJarFileInput->setText(filePath);
}
});
jarLayout->addWidget(m_plantUmlJarFileInput, 1);
auto testBtn = new QPushButton(tr("Test"), box);
testBtn->setToolTip(tr("Test PlantUml JAR and Java Runtime Environment"));
connect(testBtn, &QPushButton::clicked,
this, [this]() {
const auto jar = m_plantUmlJarFileInput->text();
if (jar.isEmpty() || !QFileInfo::exists(jar)) {
MessageBoxHelper::notify(MessageBoxHelper::Warning,
tr("The JAR file (%1) specified does not exist.").arg(jar),
this);
return;
}
auto testRet = PlantUmlHelper::testPlantUml(jar);
MessageBoxHelper::notify(MessageBoxHelper::Information,
tr("Test %1.").arg(testRet.first ? tr("succeeded") : tr("failed")),
QString(),
testRet.second,
this);
});
jarLayout->addWidget(testBtn);
const QString label(tr("PlantUml JAR file:"));
layout->addRow(label, jarLayout);
addSearchItem(label, m_plantUmlJarFileInput->toolTip(), m_plantUmlJarFileInput);
connect(m_plantUmlJarFileInput, &LocationInputWithBrowseButton::textChanged,
this, &MarkdownEditorPage::pageIsChanged);
}
{
m_graphvizModeComboBox = WidgetsFactory::createComboBox(box);
m_graphvizModeComboBox->setToolTip(tr("Use online service or local executable file to render Graphviz graphs"));
m_graphvizModeComboBox->addItem(tr("Online Service"), 0);
m_graphvizModeComboBox->addItem(tr("Local Executable"), 1);
const QString label(tr("Graphviz:"));
layout->addRow(label, m_graphvizModeComboBox);
addSearchItem(label, m_graphvizModeComboBox->toolTip(), m_graphvizModeComboBox);
connect(m_graphvizModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MarkdownEditorPage::pageIsChanged);
}
{
auto fileLayout = new QHBoxLayout();
m_graphvizFileInput = new LocationInputWithBrowseButton(box);
m_graphvizFileInput->setToolTip(tr("Local executable file to render Graphviz graphs"));
connect(m_graphvizFileInput, &LocationInputWithBrowseButton::clicked,
this, [this]() {
auto filePath = QFileDialog::getOpenFileName(this,
tr("Select Graphviz Executable File"),
QDir::homePath());
if (!filePath.isEmpty()) {
m_graphvizFileInput->setText(filePath);
}
});
fileLayout->addWidget(m_graphvizFileInput, 1);
auto testBtn = new QPushButton(tr("Test"), box);
testBtn->setToolTip(tr("Test Graphviz executable file"));
connect(testBtn, &QPushButton::clicked,
this, [this]() {
const auto exe = m_graphvizFileInput->text();
if (exe.isEmpty() || !QFileInfo::exists(exe)) {
MessageBoxHelper::notify(MessageBoxHelper::Warning,
tr("The executable file (%1) specified does not exist.").arg(exe),
this);
return;
}
auto testRet = GraphvizHelper::testGraphviz(exe);
MessageBoxHelper::notify(MessageBoxHelper::Information,
tr("Test %1.").arg(testRet.first ? tr("succeeded") : tr("failed")),
QString(),
testRet.second,
this);
});
fileLayout->addWidget(testBtn);
const QString label(tr("Graphviz executable file:"));
layout->addRow(label, fileLayout);
addSearchItem(label, m_graphvizFileInput->toolTip(), m_graphvizFileInput);
connect(m_graphvizFileInput, &LocationInputWithBrowseButton::textChanged,
this, &MarkdownEditorPage::pageIsChanged);
}
return box;
}

View File

@ -11,6 +11,8 @@ class QComboBox;
namespace vnotex
{
class LocationInputWithBrowseButton;
class MarkdownEditorPage : public SettingsPage
{
Q_OBJECT
@ -60,6 +62,14 @@ namespace vnotex
QCheckBox *m_smartTableCheckBox = nullptr;
QCheckBox *m_spellCheckCheckBox = nullptr;
QComboBox *m_plantUmlModeComboBox = nullptr;
LocationInputWithBrowseButton *m_plantUmlJarFileInput = nullptr;
QComboBox *m_graphvizModeComboBox = nullptr;
LocationInputWithBrowseButton *m_graphvizFileInput = nullptr;
};
}

View File

@ -0,0 +1,201 @@
#include "graphhelper.h"
#include <QDebug>
#include <QFileInfo>
#include <utils/processutils.h>
using namespace vnotex;
#define TaskIdProperty "GraphTaskId"
#define TaskTimeStampProperty "GraphTaskTimeStamp"
GraphHelper::GraphHelper()
: m_cache(100, CacheItem())
{
}
QStringList GraphHelper::getArgsToUse(const QStringList &p_args)
{
if (p_args.isEmpty()) {
return QStringList();
}
if (p_args[0] == "-c") {
// Combine all the arguments except the first one.
QStringList args;
args << p_args[0];
QString subCmd;
for (int i = 1; i < p_args.size(); ++i) {
subCmd += " " + p_args[i];
}
args << subCmd;
return args;
} else {
return p_args;
}
}
void GraphHelper::process(quint64 p_id,
TimeStamp p_timeStamp,
const QString &p_format,
const QString &p_text,
const ResultCallback &p_callback)
{
Task task;
task.m_id = p_id;
task.m_timeStamp = p_timeStamp;
task.m_format = p_format;
task.m_text = p_text;
task.m_callback = p_callback;
m_tasks.enqueue(task);
processOneTask();
}
void GraphHelper::processOneTask()
{
if (m_taskOngoing || m_tasks.isEmpty()) {
return;
}
m_taskOngoing = true;
const auto &task = m_tasks.head();
const auto &cachedData = m_cache.get(task.m_text);
if (!cachedData.isNull() && cachedData.m_format == task.m_format) {
finishOneTask(cachedData.m_data);
return;
}
if (!m_programValid) {
qWarning() << "program to execute for rendering is not valid" << m_program;
finishOneTask(QString());
return;
}
// Will be released in finishOneTask.
QProcess *process = new QProcess();
process->setProperty(TaskIdProperty, task.m_id);
process->setProperty(TaskTimeStampProperty, task.m_timeStamp);
QObject::connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
[this, process](int exitCode, QProcess::ExitStatus exitStatus) {
finishOneTask(process, exitCode, exitStatus);
});
if (m_overriddenCommand.isEmpty()) {
Q_ASSERT(!m_program.isEmpty());
QStringList args(m_args);
args << getFormatArgs(task.m_format);
process->start(m_program, getArgsToUse(args));
} else {
auto cmd = getCommandToUse(m_overriddenCommand, task.m_format);
process->start(cmd);
}
if (process->write(task.m_text.toUtf8()) == -1) {
qWarning() << "Graph task" << task.m_id << "failed to write to process stdin:" << process->errorString();
}
process->closeWriteChannel();
}
void GraphHelper::finishOneTask(QProcess *p_process, int p_exitCode, QProcess::ExitStatus p_exitStatus)
{
Q_ASSERT(m_taskOngoing && !m_tasks.isEmpty());
const auto task = m_tasks.dequeue();
const quint64 id = p_process->property(TaskIdProperty).toULongLong();
const quint64 timeStamp = p_process->property(TaskTimeStampProperty).toULongLong();
Q_ASSERT(task.m_id == id && task.m_timeStamp == timeStamp);
qDebug() << "Graph task" << id << timeStamp << "finished";
bool failed = true;
if (p_exitStatus == QProcess::NormalExit) {
if (p_exitCode < 0) {
qWarning() << "Graph task" << id << "failed:" << p_exitCode;
} else {
failed = false;
const auto outBa = p_process->readAllStandardOutput();
QString data;
if (task.m_format == QStringLiteral("svg")) {
data = QString::fromLocal8Bit(outBa);
task.m_callback(id, timeStamp, task.m_format, data);
} else {
data = QString::fromLocal8Bit(outBa.toBase64());
task.m_callback(id, timeStamp, task.m_format, data);
}
CacheItem item;
item.m_format = task.m_format;
item.m_data = data;
m_cache.set(task.m_text, item);
}
} else {
qWarning() << "Graph task" << id << "failed to start" << p_exitCode << p_exitStatus;
}
const QByteArray errBa = p_process->readAllStandardError();
if (!errBa.isEmpty()) {
QString errStr(QString::fromLocal8Bit(errBa));
if (failed) {
qWarning() << "Graph task" << id << "stderr:" << errStr;
} else {
qDebug() << "Graph task" << id << "stderr:" << errStr;
}
}
if (failed) {
task.m_callback(id, task.m_timeStamp, task.m_format, QString());
}
p_process->deleteLater();
m_taskOngoing = false;
processOneTask();
}
void GraphHelper::finishOneTask(const QString &p_data)
{
Q_ASSERT(m_taskOngoing && !m_tasks.isEmpty());
const auto task = m_tasks.dequeue();
qDebug() << "Graph task" << task.m_id << task.m_timeStamp << "finished by cache" << p_data.size();
task.m_callback(task.m_id, task.m_timeStamp, task.m_format, p_data);
m_taskOngoing = false;
processOneTask();
}
QString GraphHelper::getCommandToUse(const QString &p_command, const QString &p_format)
{
auto cmd(p_command);
cmd.replace("%1", p_format);
return cmd;
}
void GraphHelper::clearCache()
{
m_cache.clear();
}
void GraphHelper::checkValidProgram()
{
m_programValid = true;
if (m_overriddenCommand.isEmpty()) {
if (m_program.isEmpty()) {
m_programValid = false;
} else {
QFileInfo finfo(m_program);
m_programValid = !finfo.isAbsolute() || finfo.isExecutable();
}
}
}

View File

@ -0,0 +1,91 @@
#ifndef GRAPHHELPER_H
#define GRAPHHELPER_H
#include <QProcess>
#include <QStringList>
#include <QPair>
#include <QQueue>
#include <core/noncopyable.h>
#include <core/global.h>
#include <vtextedit/lrucache.h>
namespace vnotex
{
class GraphHelper : private Noncopyable
{
public:
typedef std::function<void(quint64, TimeStamp, const QString &, const QString &)> ResultCallback;
GraphHelper();
void process(quint64 p_id,
TimeStamp p_timeStamp,
const QString &p_format,
const QString &p_text,
const ResultCallback &p_callback);
protected:
virtual QStringList getFormatArgs(const QString &p_format) = 0;
void clearCache();
void checkValidProgram();
static QStringList getArgsToUse(const QStringList &p_args);
static QString getCommandToUse(const QString &p_command,
const QString &p_format);
QString m_program;
QStringList m_args;
// If this is not empty, @m_program and @m_args will be ignored.
QString m_overriddenCommand;
private:
struct Task
{
quint64 m_id = 0;
TimeStamp m_timeStamp = 0;
QString m_format;
QString m_text;
ResultCallback m_callback;
};
struct CacheItem
{
bool isNull() const
{
return m_data.isNull();
}
QString m_format;
QString m_data;
};
void processOneTask();
void finishOneTask(QProcess *p_process, int p_exitCode, QProcess::ExitStatus p_exitStatus);
void finishOneTask(const QString &p_data);
QQueue<Task> m_tasks;
bool m_taskOngoing = false;
// {text} -> CacheItem.
vte::LruCache<QString, CacheItem> m_cache;
// Whether @m_program is valid.
bool m_programValid = true;
};
}
#endif // GRAPHHELPER_H

View File

@ -0,0 +1,74 @@
#include "graphvizhelper.h"
#include <QDebug>
#include <utils/processutils.h>
using namespace vnotex;
void GraphvizHelper::init(const QString &p_graphvizFile)
{
if (m_initialized) {
return;
}
m_initialized = true;
update(p_graphvizFile);
}
void GraphvizHelper::update(const QString &p_graphvizFile)
{
if (!m_initialized) {
return;
}
prepareProgramAndArgs(p_graphvizFile, m_program, m_args);
checkValidProgram();
clearCache();
}
void GraphvizHelper::prepareProgramAndArgs(const QString &p_graphvizFile,
QString &p_program,
QStringList &p_args)
{
p_program = p_graphvizFile.isEmpty() ? QStringLiteral("dot") : p_graphvizFile;
p_args.clear();
}
QPair<bool, QString> GraphvizHelper::testGraphviz(const QString &p_graphvizFile)
{
auto ret = qMakePair(false, QString());
QString program;
QStringList args;
prepareProgramAndArgs(p_graphvizFile, program, args);
args << "-Tsvg";
const QString testGraph("digraph G {VNote->Markdown}");
int exitCode = -1;
QByteArray outData;
QByteArray errData;
auto state = ProcessUtils::start(program,
args,
testGraph.toUtf8(),
exitCode,
outData,
errData);
ret.first = (state == ProcessUtils::Succeeded) && (exitCode == 0);
ret.second = QString("%1 %2\n\nExitcode: %3\n\nOutput: %4\n\nError: %5")
.arg(program, args.join(' '), QString::number(exitCode), QString::fromLocal8Bit(outData), QString::fromLocal8Bit(errData));
return ret;
}
QStringList GraphvizHelper::getFormatArgs(const QString &p_format)
{
QStringList args;
args << ("-T" + p_format);
return args;
}

View File

@ -0,0 +1,36 @@
#ifndef GRAPHVIZHELPER_H
#define GRAPHVIZHELPER_H
#include "graphhelper.h"
namespace vnotex
{
class GraphvizHelper : public GraphHelper
{
public:
void init(const QString &p_graphvizFile);
void update(const QString &p_graphvizFile);
static GraphvizHelper &getInst()
{
static GraphvizHelper inst;
return inst;
}
static QPair<bool, QString> testGraphviz(const QString &p_graphvizFile);
private:
GraphvizHelper() = default;
QStringList getFormatArgs(const QString &p_format) Q_DECL_OVERRIDE;
static void prepareProgramAndArgs(const QString &p_graphvizFile,
QString &p_program,
QStringList &p_args);
bool m_initialized = false;
};
}
#endif // GRAPHVIZHELPER_H

View File

@ -21,6 +21,7 @@
#include <vtextedit/vtextedit.h>
#include <vtextedit/texteditutils.h>
#include <vtextedit/networkutils.h>
#include <vtextedit/theme.h>
#include <widgets/dialogs/linkinsertdialog.h>
#include <widgets/dialogs/imageinsertdialog.h>
@ -1274,3 +1275,10 @@ void MarkdownEditor::setupTableHelper()
connect(getHighlighter(), &vte::PegMarkdownHighlighter::tableBlocksUpdated,
m_tableHelper, &MarkdownTableHelper::updateTableBlocks);
}
QRgb MarkdownEditor::getPreviewBackground() const
{
auto th = theme();
const auto &fmt = th->editorStyle(vte::Theme::EditorStyle::Preview);
return fmt.m_backgroundColor;
}

View File

@ -100,6 +100,8 @@ namespace vnotex
void updateFromConfig(bool p_initialized = true);
QRgb getPreviewBackground() const;
public slots:
void handleHtmlToMarkdownData(quint64 p_id, TimeStamp p_timeStamp, const QString &p_text);

View File

@ -4,6 +4,8 @@
#include <QMap>
#include "../outlineprovider.h"
#include "plantumlhelper.h"
#include "graphvizhelper.h"
using namespace vnotex;
@ -371,3 +373,35 @@ void MarkdownViewerAdapter::reset()
m_currentHeadingIndex = -1;
m_crossCopyTargets.clear();
}
void MarkdownViewerAdapter::renderGraph(quint64 p_id,
quint64 p_index,
const QString &p_format,
const QString &p_lang,
const QString &p_text)
{
if (p_text.isEmpty()) {
emit graphRenderDataReady(p_id, p_index, p_format, QString());
return;
}
if (p_lang == QStringLiteral("puml")) {
PlantUmlHelper::getInst().process(p_id,
p_index,
p_format,
p_text,
[this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) {
emit graphRenderDataReady(id, timeStamp, format, data);
});
} else if (p_lang == QStringLiteral("dot")) {
GraphvizHelper::getInst().process(p_id,
p_index,
p_format,
p_text,
[this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) {
emit graphRenderDataReady(id, timeStamp, format, data);
});
} else {
Q_ASSERT(false);
}
}

View File

@ -172,6 +172,13 @@ namespace vnotex
void setSavedContent(const QString &p_headContent, const QString &p_styleContent, const QString &p_content, const QString &p_bodyClassList);
// Call local CPP code to render graph.
void renderGraph(quint64 p_id,
quint64 p_index,
const QString &p_format,
const QString &p_lang,
const QString &p_text);
// Signals to be connected at web side.
signals:
// Current Markdown text is updated.
@ -208,6 +215,11 @@ namespace vnotex
// Request to get the whole HTML content.
void contentRequested();
void graphRenderDataReady(quint64 p_id,
quint64 p_index,
const QString &p_format,
const QString &p_data);
// Signals to be connected at cpp side.
signals:
void graphPreviewDataReady(const PreviewData &p_data);

View File

@ -0,0 +1,104 @@
#include "plantumlhelper.h"
#include <QDebug>
#include <utils/processutils.h>
using namespace vnotex;
void PlantUmlHelper::init(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
const QString &p_overriddenCommand)
{
if (m_initialized) {
return;
}
m_initialized = true;
update(p_plantUmlJarFile, p_graphvizFile, p_overriddenCommand);
}
void PlantUmlHelper::update(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
const QString &p_overriddenCommand)
{
if (!m_initialized) {
return;
}
m_overriddenCommand = p_overriddenCommand;
if (m_overriddenCommand.isEmpty()) {
prepareProgramAndArgs(p_plantUmlJarFile, p_graphvizFile, m_program, m_args);
} else {
m_program.clear();
m_args.clear();
}
checkValidProgram();
clearCache();
}
void PlantUmlHelper::prepareProgramAndArgs(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
QString &p_program,
QStringList &p_args)
{
#if defined(Q_OS_WIN)
p_program = "java";
#else
p_program = "/bin/sh";
p_args << "-c";
p_args << "java";
#endif
p_args << "-Djava.awt.headless=true";
p_args << "-jar" << p_plantUmlJarFile;
p_args << "-charset" << "UTF-8";
if (!p_graphvizFile.isEmpty()) {
p_args << "-graphvizdot" << p_graphvizFile;
}
p_args << "-pipe";
}
QPair<bool, QString> PlantUmlHelper::testPlantUml(const QString &p_plantUmlJarFile)
{
auto ret = qMakePair(false, QString());
QString program;
QStringList args;
prepareProgramAndArgs(p_plantUmlJarFile, QString(), program, args);
args << "-tsvg";
args = getArgsToUse(args);
const QString testGraph("VNote->Markdown : Hello");
int exitCode = -1;
QByteArray outData;
QByteArray errData;
auto state = ProcessUtils::start(program,
args,
testGraph.toUtf8(),
exitCode,
outData,
errData);
ret.first = (state == ProcessUtils::Succeeded) && (exitCode == 0);
ret.second = QString("%1 %2\n\nExitcode: %3\n\nOutput: %4\n\nError: %5")
.arg(program, args.join(' '), QString::number(exitCode), QString::fromLocal8Bit(outData), QString::fromLocal8Bit(errData));
return ret;
}
QStringList PlantUmlHelper::getFormatArgs(const QString &p_format)
{
QStringList args;
args << ("-t" + p_format);
return args;
}

View File

@ -0,0 +1,41 @@
#ifndef PLANTUMLHELPER_H
#define PLANTUMLHELPER_H
#include "graphhelper.h"
namespace vnotex
{
class PlantUmlHelper : public GraphHelper
{
public:
void init(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
const QString &p_overriddenCommand);
void update(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
const QString &p_overriddenCommand);
static PlantUmlHelper &getInst()
{
static PlantUmlHelper inst;
return inst;
}
static QPair<bool, QString> testPlantUml(const QString &p_plantUmlJarFile);
private:
PlantUmlHelper() = default;
QStringList getFormatArgs(const QString &p_format) Q_DECL_OVERRIDE;
static void prepareProgramAndArgs(const QString &p_plantUmlJarFile,
const QString &p_graphvizFile,
QString &p_program,
QStringList &p_args);
bool m_initialized = false;
};
}
#endif // PLANTUMLHELPER_H

View File

@ -12,6 +12,8 @@
#include <utils/utils.h>
#include "markdowneditor.h"
#include "plantumlhelper.h"
#include "graphvizhelper.h"
using namespace vnotex;
@ -134,9 +136,11 @@ void PreviewHelper::codeBlocksUpdated(vte::TimeStamp p_timeStamp,
++m_codeBlockTimeStamp;
m_codeBlocksData.clear();
bool needUpdateEditorInplacePreview = true;
QVector<int> needPreviewBlocks;
for (int i = 0; i < p_codeBlocks.size(); ++i) {
const auto &cb = p_codeBlocks[i];
for (const auto &cb : p_codeBlocks) {
const auto needPreview = isLangNeedPreview(cb.m_lang);
if (!needPreview.first && !needPreview.second) {
continue;
@ -158,16 +162,16 @@ void PreviewHelper::codeBlocksUpdated(vte::TimeStamp p_timeStamp,
}
if (m_inplacePreviewEnabled && needPreview.first && !cacheHit) {
// No need to update in-place preview for now.
needUpdateEditorInplacePreview = false;
m_codeBlocksData[blockPreviewIdx].m_text = cb.m_text;
inplacePreviewCodeBlock(blockPreviewIdx);
needPreviewBlocks.push_back(blockPreviewIdx);
}
}
if (needUpdateEditorInplacePreview) {
updateEditorInplacePreviewCodeBlock();
for (auto idx : needPreviewBlocks) {
inplacePreviewCodeBlock(idx);
}
updateEditorInplacePreviewCodeBlock();
}
bool PreviewHelper::checkPreviewSourceLang(SourceFlag p_flag, const QString &p_lang) const
@ -221,13 +225,38 @@ void PreviewHelper::inplacePreviewCodeBlock(int p_blockPreviewIdx)
if (checkPreviewSourceLang(SourceFlag::FlowChart, blockData.m_lang)
|| checkPreviewSourceLang(SourceFlag::WaveDrom, blockData.m_lang)
|| checkPreviewSourceLang(SourceFlag::Mermaid, blockData.m_lang)
|| checkPreviewSourceLang(SourceFlag::PlantUml, blockData.m_lang)
|| checkPreviewSourceLang(SourceFlag::Graphviz, blockData.m_lang)
|| (checkPreviewSourceLang(SourceFlag::PlantUml, blockData.m_lang) && m_webPlantUmlEnabled)
|| (checkPreviewSourceLang(SourceFlag::Graphviz, blockData.m_lang) && m_webGraphvizEnabled)
|| checkPreviewSourceLang(SourceFlag::Math, blockData.m_lang)) {
emit graphPreviewRequested(p_blockPreviewIdx,
m_codeBlockTimeStamp,
blockData.m_lang,
TextUtils::removeCodeBlockFence(blockData.m_text));
return;
}
if (!m_webPlantUmlEnabled && checkPreviewSourceLang(SourceFlag::PlantUml, blockData.m_lang)) {
// Local PlantUml.
PlantUmlHelper::getInst().process(static_cast<quint64>(p_blockPreviewIdx),
m_codeBlockTimeStamp,
QStringLiteral("svg"),
TextUtils::removeCodeBlockFence(blockData.m_text),
[this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) {
handleLocalData(id, timeStamp, format, data, true);
});
return;
}
if (!m_webGraphvizEnabled && checkPreviewSourceLang(SourceFlag::Graphviz, blockData.m_lang)) {
// Local PlantUml.
GraphvizHelper::getInst().process(static_cast<quint64>(p_blockPreviewIdx),
m_codeBlockTimeStamp,
QStringLiteral("svg"),
TextUtils::removeCodeBlockFence(blockData.m_text),
[this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) {
handleLocalData(id, timeStamp, format, data, false);
});
return;
}
}
@ -242,10 +271,11 @@ void PreviewHelper::handleGraphPreviewData(const MarkdownViewerAdapter::PreviewD
}
auto &blockData = m_codeBlocksData[p_data.m_id];
const bool forcedBackground = needForcedBackground(blockData.m_lang);
auto previewData = QSharedPointer<GraphPreviewData>::create(p_data.m_timeStamp,
p_data.m_format,
p_data.m_data,
0,
forcedBackground ? m_editor->getPreviewBackground() : 0,
p_data.m_needScale ? getEditorScaleFactor() : 1);
m_codeBlockCache.set(blockData.m_text, previewData);
blockData.m_text.clear();
@ -414,3 +444,58 @@ qreal PreviewHelper::getEditorScaleFactor() const
return 1;
}
void PreviewHelper::setWebPlantUmlEnabled(bool p_enabled)
{
m_webPlantUmlEnabled = p_enabled;
}
void PreviewHelper::setWebGraphvizEnabled(bool p_enabled)
{
m_webGraphvizEnabled = p_enabled;
}
void PreviewHelper::handleLocalData(quint64 p_id,
TimeStamp p_timeStamp,
const QString &p_format,
const QString &p_data,
bool p_forcedBackground)
{
if (p_timeStamp != m_codeBlockTimeStamp) {
return;
}
Q_UNUSED(p_format);
Q_ASSERT(p_format == QStringLiteral("svg"));
if (p_id >= static_cast<quint64>(m_codeBlocksData.size()) || p_data.isEmpty()) {
updateEditorInplacePreviewCodeBlock();
return;
}
auto &blockData = m_codeBlocksData[p_id];
auto previewData = QSharedPointer<GraphPreviewData>::create(p_timeStamp,
p_format,
p_data.toUtf8(),
p_forcedBackground ? m_editor->getPreviewBackground() : 0,
getEditorScaleFactor());
m_codeBlockCache.set(blockData.m_text, previewData);
blockData.m_text.clear();
blockData.updateInplacePreview(m_document,
previewData->m_image,
previewData->m_name,
previewData->m_background,
m_tabStopWidth);
updateEditorInplacePreviewCodeBlock();
}
bool PreviewHelper::needForcedBackground(const QString &p_lang) const
{
if (checkPreviewSourceLang(SourceFlag::PlantUml, p_lang)) {
return true;
}
return false;
}

View File

@ -47,6 +47,10 @@ namespace vnotex
void setMarkdownEditor(MarkdownEditor *p_editor);
void setWebPlantUmlEnabled(bool p_enabled);
void setWebGraphvizEnabled(bool p_enabled);
public slots:
void codeBlocksUpdated(vte::TimeStamp p_timeStamp,
const QVector<vte::peg::FencedCodeBlock> &p_codeBlocks);
@ -180,8 +184,16 @@ namespace vnotex
void updateEditorInplacePreviewMathBlock();
void handleLocalData(quint64 p_id,
TimeStamp p_timeStamp,
const QString &p_format,
const QString &p_data,
bool p_forcedBackground);
qreal getEditorScaleFactor() const;
bool needForcedBackground(const QString &p_lang) const;
MarkdownEditor *m_editor = nullptr;
QTextDocument *m_document = nullptr;
@ -213,6 +225,10 @@ namespace vnotex
vte::LruCache<QString, QSharedPointer<GraphPreviewData>> m_codeBlockCache;
vte::LruCache<QString, QSharedPointer<GraphPreviewData>> m_mathBlockCache;
bool m_webPlantUmlEnabled = true;
bool m_webGraphvizEnabled = true;
};
}

View File

@ -12,6 +12,7 @@ LocationInputWithBrowseButton::LocationInputWithBrowseButton(QWidget *p_parent)
: QWidget(p_parent)
{
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_lineEdit = WidgetsFactory::createLineEdit(this);
layout->addWidget(m_lineEdit, 1);

View File

@ -29,6 +29,8 @@
#include "toolbarhelper.h"
#include "findandreplacewidget.h"
#include "editors/statuswidget.h"
#include "editors/plantumlhelper.h"
#include "editors/graphvizhelper.h"
using namespace vnotex;
@ -40,7 +42,7 @@ MarkdownViewWindow::MarkdownViewWindow(QWidget *p_parent)
setupUI();
m_previewHelper = new PreviewHelper(nullptr, this);
setupPreviewHelper();
}
MarkdownViewWindow::~MarkdownViewWindow()
@ -176,7 +178,12 @@ void MarkdownViewWindow::handleEditorConfigChange()
if (markdownEditorConfig.revision() != m_markdownEditorConfigRevision) {
m_markdownEditorConfigRevision = markdownEditorConfig.revision();
m_previewHelper->setWebPlantUmlEnabled(markdownEditorConfig.getWebPlantUml());
m_previewHelper->setWebGraphvizEnabled(markdownEditorConfig.getWebGraphviz());
HtmlTemplateHelper::updateMarkdownViewerTemplate(markdownEditorConfig);
if (m_editor) {
auto config = createMarkdownEditorConfig(markdownEditorConfig);
m_editor->setConfig(config);
@ -236,7 +243,7 @@ void MarkdownViewWindow::handleBufferChangedInternal(const QSharedPointer<FileOp
TextViewWindowHelper::handleBufferChanged(this);
handleFileOpenParameters(p_paras);
handleFileOpenParameters(p_paras, false);
}
void MarkdownViewWindow::setupToolBar()
@ -890,7 +897,7 @@ void MarkdownViewWindow::handleFindAndReplaceWidgetOpened()
m_findAndReplace->setReplaceEnabled(!isReadMode());
}
void MarkdownViewWindow::handleFileOpenParameters(const QSharedPointer<FileOpenParameters> &p_paras)
void MarkdownViewWindow::handleFileOpenParameters(const QSharedPointer<FileOpenParameters> &p_paras, bool p_twice)
{
if (!p_paras) {
return;
@ -905,7 +912,9 @@ void MarkdownViewWindow::handleFileOpenParameters(const QSharedPointer<FileOpenP
m_editor->insertText(title);
}
} else {
setMode(p_paras->m_mode);
if (!p_twice || p_paras->m_forceMode) {
setMode(p_paras->m_mode);
}
scrollToLine(p_paras->m_lineNumber);
}
@ -934,7 +943,7 @@ bool MarkdownViewWindow::isReadMode() const
void MarkdownViewWindow::openTwice(const QSharedPointer<FileOpenParameters> &p_paras)
{
Q_ASSERT(!p_paras || !p_paras->m_newFile);
handleFileOpenParameters(p_paras);
handleFileOpenParameters(p_paras, true);
}
ViewWindowSession MarkdownViewWindow::saveSession() const
@ -946,3 +955,19 @@ ViewWindowSession MarkdownViewWindow::saveSession() const
}
return session;
}
void MarkdownViewWindow::setupPreviewHelper()
{
Q_ASSERT(!m_previewHelper);
m_previewHelper = new PreviewHelper(nullptr, this);
const auto &markdownEditorConfig = ConfigMgr::getInst().getEditorConfig().getMarkdownEditorConfig();
m_previewHelper->setWebPlantUmlEnabled(markdownEditorConfig.getWebPlantUml());
m_previewHelper->setWebGraphvizEnabled(markdownEditorConfig.getWebGraphviz());
PlantUmlHelper::getInst().init(markdownEditorConfig.getPlantUmlJar(),
markdownEditorConfig.getGraphvizExe(),
markdownEditorConfig.getPlantUmlCommand());
GraphvizHelper::getInst().init(markdownEditorConfig.getGraphvizExe());
}

View File

@ -97,6 +97,8 @@ namespace vnotex
void setupViewer();
void setupPreviewHelper();
void syncTextEditorFromBuffer(bool p_syncPositionFromReadMode);
void syncViewerFromBuffer(bool p_syncPositionFromEditMode);
@ -131,7 +133,7 @@ namespace vnotex
void setModeInternal(ViewWindowMode p_mode, bool p_syncBuffer);
void handleFileOpenParameters(const QSharedPointer<FileOpenParameters> &p_paras);
void handleFileOpenParameters(const QSharedPointer<FileOpenParameters> &p_paras, bool p_twice);
void scrollToLine(int p_lineNumber);

View File

@ -22,10 +22,14 @@
#include <core/vnotex.h>
#include <core/configmgr.h>
#include <core/coreconfig.h>
#include <core/editorconfig.h>
#include <core/markdowneditorconfig.h>
#include <core/sessionconfig.h>
#include <core/fileopenparameters.h>
#include <notebook/node.h>
#include <notebook/notebook.h>
#include "editors/plantumlhelper.h"
#include "editors/graphvizhelper.h"
using namespace vnotex;
@ -87,6 +91,12 @@ ViewArea::ViewArea(QWidget *p_parent)
p_win->handleEditorConfigChange();
return true;
});
const auto &markdownEditorConfig = ConfigMgr::getInst().getEditorConfig().getMarkdownEditorConfig();
PlantUmlHelper::getInst().update(markdownEditorConfig.getPlantUmlJar(),
markdownEditorConfig.getGraphvizExe(),
markdownEditorConfig.getPlantUmlCommand());
GraphvizHelper::getInst().update(markdownEditorConfig.getGraphvizExe());
});
m_fileCheckTimer = new QTimer(this);

View File

@ -28,11 +28,14 @@ SOURCES += \
$$PWD/dialogs/tableinsertdialog.cpp \
$$PWD/dragdropareaindicator.cpp \
$$PWD/editors/editormarkdownvieweradapter.cpp \
$$PWD/editors/graphhelper.cpp \
$$PWD/editors/graphvizhelper.cpp \
$$PWD/editors/markdowneditor.cpp \
$$PWD/editors/markdowntable.cpp \
$$PWD/editors/markdowntablehelper.cpp \
$$PWD/editors/markdownviewer.cpp \
$$PWD/editors/markdownvieweradapter.cpp \
$$PWD/editors/plantumlhelper.cpp \
$$PWD/editors/previewhelper.cpp \
$$PWD/editors/statuswidget.cpp \
$$PWD/editors/texteditor.cpp \
@ -123,11 +126,14 @@ HEADERS += \
$$PWD/dialogs/tableinsertdialog.h \
$$PWD/dragdropareaindicator.h \
$$PWD/editors/editormarkdownvieweradapter.h \
$$PWD/editors/graphhelper.h \
$$PWD/editors/graphvizhelper.h \
$$PWD/editors/markdowneditor.h \
$$PWD/editors/markdowntable.h \
$$PWD/editors/markdowntablehelper.h \
$$PWD/editors/markdownviewer.h \
$$PWD/editors/markdownvieweradapter.h \
$$PWD/editors/plantumlhelper.h \
$$PWD/editors/previewhelper.h \
$$PWD/editors/statuswidget.h \
$$PWD/editors/texteditor.h \