support Graphviz

This commit is contained in:
Le Tan 2018-04-07 09:56:36 +08:00
parent 11e111ed7b
commit 2206102945
18 changed files with 327 additions and 39 deletions

View File

@ -912,10 +912,13 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
m_plantUMLJarEdit = new VLineEdit();
m_plantUMLJarEdit->setToolTip(tr("Location to the PlantUML JAR executable for local PlantUML"));
m_plantUMLDotEdit = new VLineEdit();
m_plantUMLDotEdit->setPlaceholderText(tr("Empty to detect automatically"));
m_plantUMLDotEdit->setToolTip(tr("Location to the GraphViz executable for local PlantUML "
"(empty to let PlantUML detect it automatically)"));
// Graphviz.
m_graphvizCB = new QCheckBox(tr("Graphviz"));
m_graphvizCB->setToolTip(tr("Enable Graphviz for drawing graph"));
m_graphvizDotEdit = new VLineEdit();
m_graphvizDotEdit->setPlaceholderText(tr("Empty to detect automatically"));
m_graphvizDotEdit->setToolTip(tr("Location to the GraphViz dot executable"));
QFormLayout *mainLayout = new QFormLayout();
mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
@ -924,7 +927,8 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
mainLayout->addRow(tr("PlantUML:"), m_plantUMLModeCombo);
mainLayout->addRow(tr("PlantUML server:"), m_plantUMLServerEdit);
mainLayout->addRow(tr("PlantUML JAR:"), m_plantUMLJarEdit);
mainLayout->addRow(tr("Graphviz executable:"), m_plantUMLDotEdit);
mainLayout->addRow(m_graphvizCB);
mainLayout->addRow(tr("Graphviz executable:"), m_graphvizDotEdit);
setLayout(mainLayout);
}
@ -947,6 +951,10 @@ bool VMarkdownTab::loadConfiguration()
return false;
}
if (!loadGraphviz()) {
return false;
}
return true;
}
@ -968,6 +976,10 @@ bool VMarkdownTab::saveConfiguration()
return false;
}
if (!saveGraphviz()) {
return false;
}
return true;
}
@ -1044,7 +1056,6 @@ bool VMarkdownTab::loadPlantUML()
m_plantUMLModeCombo->setCurrentIndex(m_plantUMLModeCombo->findData(g_config->getPlantUMLMode()));
m_plantUMLServerEdit->setText(g_config->getPlantUMLServer());
m_plantUMLJarEdit->setText(g_config->getPlantUMLJar());
m_plantUMLDotEdit->setText(g_config->getPlantUMLDot());
return true;
}
@ -1053,6 +1064,19 @@ bool VMarkdownTab::savePlantUML()
g_config->setPlantUMLMode(m_plantUMLModeCombo->currentData().toInt());
g_config->setPlantUMLServer(m_plantUMLServerEdit->text());
g_config->setPlantUMLJar(m_plantUMLJarEdit->text());
g_config->setPlantUMLDot(m_plantUMLDotEdit->text());
return true;
}
bool VMarkdownTab::loadGraphviz()
{
m_graphvizCB->setChecked(g_config->getEnableGraphviz());
m_graphvizDotEdit->setText(g_config->getGraphvizDot());
return true;
}
bool VMarkdownTab::saveGraphviz()
{
g_config->setEnableGraphviz(m_graphvizCB->isChecked());
g_config->setGraphvizDot(m_graphvizDotEdit->text());
return true;
}

View File

@ -160,6 +160,9 @@ private:
bool loadPlantUML();
bool savePlantUML();
bool loadGraphviz();
bool saveGraphviz();
// Default note open mode for markdown.
QComboBox *m_openModeCombo;
@ -174,7 +177,10 @@ private:
QComboBox *m_plantUMLModeCombo;
VLineEdit *m_plantUMLServerEdit;
VLineEdit *m_plantUMLJarEdit;
VLineEdit *m_plantUMLDotEdit;
// Graphviz.
QCheckBox *m_graphvizCB;
VLineEdit *m_graphvizDotEdit;
};
class VSettingsDialog : public QDialog

View File

@ -25,6 +25,7 @@ var updateHtml = function(html) {
var codes = document.getElementsByTagName('code');
mermaidIdx = 0;
plantUMLIdx = 0;
graphvizIdx = 0;
for (var i = 0; i < codes.length; ++i) {
var code = codes[i];
if (code.parentElement.tagName.toLowerCase() == 'pre') {
@ -59,9 +60,15 @@ var updateHtml = function(html) {
renderPlantUMLOneLocal(code);
}
continue;
} else if (VEnableGraphviz
&& code.classList.contains('language-dot')) {
// Graphviz code block.
renderGraphvizOneLocal(code);
continue;
}
if (listContainsRegex(code.classList, /language-.*/)) {
hljs.highlightBlock(code);
}

View File

@ -116,6 +116,7 @@ var updateText = function(text) {
renderMermaid('lang-mermaid');
renderFlowchart(['lang-flowchart', 'lang-flow']);
renderPlantUML('lang-puml');
renderGraphviz('lang-dot');
addClassToCodeBlock();
renderCodeBlockLineNumber();

View File

@ -1,3 +1,5 @@
var channelInitialized = false;
var content;
// Current header index in all headers.
@ -45,6 +47,18 @@ if (typeof VPlantUMLServer == 'undefined') {
VPlantUMLServer = 'http://www.plantuml.com/plantuml';
}
if (typeof VPlantUMLFormat == 'undefined') {
VPlantUMLFormat = 'svg';
}
if (typeof VEnableGraphviz == 'undefined') {
VEnableGraphviz = false;
}
if (typeof VGraphvizFormat == 'undefined') {
VGraphvizFormat = 'svg';
}
// Add a caption (using alt text) under the image.
var VImageCenterClass = 'img-center';
var VImageCaptionClass = 'img-caption';
@ -123,14 +137,7 @@ var htmlContent = function() {
new QWebChannel(qt.webChannelTransport,
function(channel) {
content = channel.objects.content;
if (typeof updateHtml == "function") {
updateHtml(content.html);
content.htmlChanged.connect(updateHtml);
}
if (typeof updateText == "function") {
content.textChanged.connect(updateText);
content.updateText();
}
content.requestScrollToAnchor.connect(scrollToAnchor);
if (typeof highlightText == "function") {
@ -148,6 +155,19 @@ new QWebChannel(qt.webChannelTransport,
}
content.plantUMLResultReady.connect(handlePlantUMLResult);
content.graphvizResultReady.connect(handleGraphvizResult);
if (typeof updateHtml == "function") {
updateHtml(content.html);
content.htmlChanged.connect(updateHtml);
}
if (typeof updateText == "function") {
content.textChanged.connect(updateText);
content.updateText();
}
channelInitialized = true;
});
var VHighlightedAnchorClass = 'highlighted-anchor';
@ -686,6 +706,33 @@ var renderPlantUMLOneLocal = function(code) {
plantUMLIdx++;
};
var graphvizIdx = 0;
var graphvizCodeClass = 'graphviz_code_';
// @className, the class name of the Graghviz code block, such as 'lang-dot'.
var renderGraphviz = function(className) {
if (!VEnableGraphviz) {
return;
}
graphvizIdx = 0;
var codes = document.getElementsByTagName('code');
for (var i = 0; i < codes.length; ++i) {
var code = codes[i];
if (code.classList.contains(className)) {
renderGraphvizOneLocal(code);
}
}
};
var renderGraphvizOneLocal = function(code) {
++asyncJobsCount;
code.classList.add(graphvizCodeClass + graphvizIdx);
content.processGraphviz(graphvizIdx, VGraphvizFormat, code.textContent);
graphvizIdx++;
};
var isImageBlock = function(img) {
var pn = img.parentNode;
return (pn.children.length == 1) && (pn.textContent == '');
@ -1260,7 +1307,8 @@ var specialCodeBlock = function(lang) {
return (VEnableMathjax && lang == 'mathjax')
|| (VEnableMermaid && lang == 'mermaid')
|| (VEnableFlowchart && (lang == 'flowchart' || lang == 'flow'))
|| (VPlantUMLMode != 0 && lang == 'puml');
|| (VPlantUMLMode != 0 && lang == 'puml')
|| (VEnableGraphviz && lang == 'dot');
};
var handlePlantUMLResult = function(id, format, result) {
@ -1282,3 +1330,22 @@ var handlePlantUMLResult = function(id, format, result) {
finishOneAsyncJob();
};
var handleGraphvizResult = function(id, format, result) {
var code = document.getElementsByClassName(graphvizCodeClass + id)[0];
if (code && result.length > 0) {
var obj = null;
if (format == 'svg') {
obj = document.createElement('p');
obj.innerHTML = result;
} else {
obj = document.createElement('img');
obj.src = "data:image/" + format + ";base64, " + result;
}
var preNode = code.parentNode;
preNode.parentNode.replaceChild(obj, preNode);
}
finishOneAsyncJob();
};

View File

@ -60,6 +60,7 @@ var updateText = function(text) {
renderMermaid('lang-mermaid');
renderFlowchart(['lang-flowchart', 'lang-flow']);
renderPlantUML('lang-puml');
renderGraphviz('lang-dot');
addClassToCodeBlock();
renderCodeBlockLineNumber();

View File

@ -49,7 +49,12 @@ var mdHasTocSection = function(markdown) {
return n != -1;
};
var highlightCodeBlocks = function(doc, enableMermaid, enableFlowchart, enableMathJax, enablePlantUML) {
var highlightCodeBlocks = function(doc,
enableMermaid,
enableFlowchart,
enableMathJax,
enablePlantUML,
enableGraphviz) {
var codes = doc.getElementsByTagName('code');
for (var i = 0; i < codes.length; ++i) {
var code = codes[i];
@ -68,6 +73,9 @@ var highlightCodeBlocks = function(doc, enableMermaid, enableFlowchart, enableMa
} else if (enablePlantUML && code.classList.contains('language-puml')) {
// PlantUML code block.
continue;
} else if (enableGraphviz && code.classList.contains('language-dot')) {
// Graphviz code block.
continue;
}
if (listContainsRegex(code.classList, /language-.*/)) {
@ -89,10 +97,16 @@ var updateText = function(text) {
placeholder.innerHTML = html;
handleToc(needToc);
insertImageCaption();
highlightCodeBlocks(document, VEnableMermaid, VEnableFlowchart, VEnableMathjax, VPlantUMLMode != 0);
highlightCodeBlocks(document,
VEnableMermaid,
VEnableFlowchart,
VEnableMathjax,
VPlantUMLMode != 0,
VEnableGraphviz);
renderMermaid('language-mermaid');
renderFlowchart(['language-flowchart', 'language-flow']);
renderPlantUML('language-puml');
renderGraphviz('language-dot');
addClassToCodeBlock();
renderCodeBlockLineNumber();

View File

@ -49,6 +49,9 @@ enable_flowchart=false
; 2 - local PlantUML
plantuml_mode=0
; Enable Graphviz
enable_graphviz=false
; -1 - calculate the factor
web_zoom_factor=-1
@ -277,8 +280,8 @@ plantuml_server=http://www.plantuml.com/plantuml
; PlantUML JAR location
plantuml_jar=
; PlantUML Graphviz Dot location
plantuml_dot=
; Graphviz Dot location
graphviz_dot=
[shortcuts]
; Define shortcuts here, with each item in the form "operation=keysequence".

View File

@ -126,7 +126,8 @@ SOURCES += main.cpp\
vhelpue.cpp \
vlistfolderue.cpp \
dialog/vfixnotebookdialog.cpp \
vplantumlhelper.cpp
vplantumlhelper.cpp \
vgraphvizhelper.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -243,7 +244,8 @@ HEADERS += vmainwindow.h \
vhelpue.h \
vlistfolderue.h \
dialog/vfixnotebookdialog.h \
vplantumlhelper.h
vplantumlhelper.h \
vgraphvizhelper.h
RESOURCES += \
vnote.qrc \

View File

@ -736,6 +736,13 @@ QString VUtils::generateHtmlTemplate(const QString &p_template,
extraFile += QString("<script>var VPlantUMLFormat = '%1';</script>\n").arg(format);
}
if (g_config->getEnableGraphviz()) {
extraFile += "<script>var VEnableGraphviz = true;</script>\n";
QString format = p_isPDF ? "png" : "svg";
extraFile += QString("<script>var VGraphvizFormat = '%1';</script>\n").arg(format);
}
if (g_config->getEnableImageCaption()) {
extraFile += "<script>var VEnableImageCaption = true;</script>\n";
}

View File

@ -288,7 +288,9 @@ void VConfigManager::initialize()
m_plantUMLMode = getConfigFromSettings("global", "plantuml_mode").toInt();
m_plantUMLServer = getConfigFromSettings("web", "plantuml_server").toString();
m_plantUMLJar = getConfigFromSettings("web", "plantuml_jar").toString();
m_plantUMLDot = getConfigFromSettings("web", "plantuml_dot").toString();
m_enableGraphviz = getConfigFromSettings("global", "enable_graphviz").toBool();
m_graphvizDot = getConfigFromSettings("web", "graphviz_dot").toString();
}
void VConfigManager::initSettings()

View File

@ -210,6 +210,9 @@ public:
bool getEnableMathjax() const;
void setEnableMathjax(bool p_enabled);
bool getEnableGraphviz() const;
void setEnableGraphviz(bool p_enabled);
int getPlantUMLMode() const;
void setPlantUMLMode(int p_mode);
@ -469,8 +472,8 @@ public:
const QString &getPlantUMLJar() const;
void setPlantUMLJar(const QString &p_jarPath);
const QString &getPlantUMLDot() const;
void setPlantUMLDot(const QString &p_dotPath);
const QString &getGraphvizDot() const;
void setGraphvizDot(const QString &p_dotPath);
private:
// Look up a config from user and default settings.
@ -634,6 +637,11 @@ private:
// Enable Mathjax.
bool m_enableMathjax;
// Enable Graphviz.
bool m_enableGraphviz;
QString m_graphvizDot;
// Zoom factor of the QWebEngineView.
qreal m_webZoomFactor;
@ -871,8 +879,6 @@ private:
QString m_plantUMLJar;
QString m_plantUMLDot;
// The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile;
@ -1351,6 +1357,21 @@ inline void VConfigManager::setEnableMathjax(bool p_enabled)
setConfigToSettings("global", "enable_mathjax", m_enableMathjax);
}
inline bool VConfigManager::getEnableGraphviz() const
{
return m_enableGraphviz;
}
inline void VConfigManager::setEnableGraphviz(bool p_enabled)
{
if (m_enableGraphviz == p_enabled) {
return;
}
m_enableGraphviz = p_enabled;
setConfigToSettings("global", "enable_graphviz", m_enableGraphviz);
}
inline int VConfigManager::getPlantUMLMode() const
{
return m_plantUMLMode;
@ -2217,18 +2238,18 @@ inline void VConfigManager::setPlantUMLJar(const QString &p_jarPath)
setConfigToSettings("web", "plantuml_jar", p_jarPath);
}
inline const QString &VConfigManager::getPlantUMLDot() const
inline const QString &VConfigManager::getGraphvizDot() const
{
return m_plantUMLDot;
return m_graphvizDot;
}
inline void VConfigManager::setPlantUMLDot(const QString &p_dotPath)
inline void VConfigManager::setGraphvizDot(const QString &p_dotPath)
{
if (m_plantUMLDot == p_dotPath) {
if (m_graphvizDot == p_dotPath) {
return;
}
m_plantUMLDot = p_dotPath;
setConfigToSettings("web", "plantuml_dot", p_dotPath);
m_graphvizDot = p_dotPath;
setConfigToSettings("web", "graphviz_dot", p_dotPath);
}
#endif // VCONFIGMANAGER_H

View File

@ -4,12 +4,14 @@
#include "vfile.h"
#include "vplantumlhelper.h"
#include "vgraphvizhelper.h"
VDocument::VDocument(const VFile *v_file, QObject *p_parent)
: QObject(p_parent),
m_file(v_file),
m_readyToHighlight(false),
m_plantUMLHelper(NULL)
m_plantUMLHelper(NULL),
m_graphvizHelper(NULL)
{
}
@ -142,10 +144,19 @@ void VDocument::processPlantUML(int p_id, const QString &p_format, const QString
if (!m_plantUMLHelper) {
m_plantUMLHelper = new VPlantUMLHelper(this);
connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
this, [this](int p_id, const QString &p_format, const QString &p_result) {
emit plantUMLResultReady(p_id, p_format, p_result);
});
this, &VDocument::plantUMLResultReady);
}
m_plantUMLHelper->processAsync(p_id, p_format, p_text);
}
void VDocument::processGraphviz(int p_id, const QString &p_format, const QString &p_text)
{
if (!m_graphvizHelper) {
m_graphvizHelper = new VGraphvizHelper(this);
connect(m_graphvizHelper, &VGraphvizHelper::resultReady,
this, &VDocument::graphvizResultReady);
}
m_graphvizHelper->processAsync(p_id, p_format, p_text);
}

View File

@ -8,6 +8,7 @@
class VFile;
class VPlantUMLHelper;
class VGraphvizHelper;
class VDocument : public QObject
{
@ -86,6 +87,9 @@ public slots:
// Web-side call this to process PlantUML locally.
void processPlantUML(int p_id, const QString &p_format, const QString &p_text);
// Web-side call this to process Graphviz locally.
void processGraphviz(int p_id, const QString &p_format, const QString &p_text);
signals:
void textChanged(const QString &text);
@ -124,6 +128,8 @@ signals:
void plantUMLResultReady(int p_id, const QString &p_format, const QString &p_result);
void graphvizResultReady(int p_id, const QString &p_format, const QString &p_result);
private:
QString m_toc;
QString m_header;
@ -145,6 +151,8 @@ private:
VWordCountInfo m_wordCountInfo;
VPlantUMLHelper *m_plantUMLHelper;
VGraphvizHelper *m_graphvizHelper;
};
inline bool VDocument::isReadyToHighlight() const

View File

@ -1012,6 +1012,7 @@ bool VEditArea::activateNextTabByCaptain(void *p_target, void *p_data)
VEditWindow *win = obj->getCurrentWindow();
if (win) {
win->focusNextTab(true);
return false;
}
return true;

83
src/vgraphvizhelper.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "vgraphvizhelper.h"
#include <QDebug>
#include <QThread>
#include "vconfigmanager.h"
extern VConfigManager *g_config;
#define TaskIdProperty "GraphvizTaskId"
#define TaskFormatProperty "GraphvizTaskFormat"
VGraphvizHelper::VGraphvizHelper(QObject *p_parent)
: QObject(p_parent)
{
prepareCommand(m_program, m_args);
}
void VGraphvizHelper::processAsync(int p_id, const QString &p_format, const QString &p_text)
{
QProcess *process = new QProcess(this);
process->setProperty(TaskIdProperty, p_id);
process->setProperty(TaskFormatProperty, p_format);
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(handleProcessFinished(int, QProcess::ExitStatus)));
QStringList args(m_args);
args << ("-T" + p_format);
qDebug() << m_program << args;
process->start(m_program, args);
process->write(p_text.toUtf8());
process->closeWriteChannel();
}
void VGraphvizHelper::prepareCommand(QString &p_program, QStringList &p_args) const
{
const QString &dot = g_config->getGraphvizDot();
if (dot.isEmpty()) {
p_program = "dot";
} else {
p_program = dot;
}
p_args.clear();
}
void VGraphvizHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus)
{
QProcess *process = static_cast<QProcess *>(sender());
int id = process->property(TaskIdProperty).toInt();
QString format = process->property(TaskFormatProperty).toString();
qDebug() << "process finished" << id << format << p_exitCode << p_exitStatus;
bool failed = true;
if (p_exitStatus == QProcess::NormalExit) {
if (p_exitCode < 0) {
qWarning() << "Graphviz fail" << p_exitCode;
} else {
failed = false;
QByteArray outBa = process->readAllStandardOutput();
if (format == "svg") {
emit resultReady(id, format, QString::fromLocal8Bit(outBa));
} else {
emit resultReady(id, format, QString::fromLocal8Bit(outBa.toBase64()));
}
}
} else {
qWarning() << "fail to start Graphviz process" << p_exitCode << p_exitStatus;
}
if (failed) {
QByteArray errBa = process->readAllStandardError();
if (!errBa.isEmpty()) {
QString errStr(QString::fromLocal8Bit(errBa));
qWarning() << "Graphviz stderr:" << errStr;
}
emit resultReady(id, format, "");
}
process->deleteLater();
}

30
src/vgraphvizhelper.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef VGRAPHVIZHELPER_H
#define VGRAPHVIZHELPER_H
#include <QObject>
#include <QStringList>
#include <QProcess>
class VGraphvizHelper : public QObject
{
Q_OBJECT
public:
explicit VGraphvizHelper(QObject *p_parent = nullptr);
void processAsync(int p_id, const QString &p_format, const QString &p_text);
void prepareCommand(QString &p_cmd, QStringList &p_args) const;
signals:
void resultReady(int p_id, const QString &p_format, const QString &p_result);
private slots:
void handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus);
private:
QString m_program;
QStringList m_args;
};
#endif // VGRAPHVIZHELPER_H

View File

@ -44,7 +44,7 @@ void VPlantUMLHelper::prepareCommand(QString &p_program, QStringList &p_args) co
int nbthread = QThread::idealThreadCount();
p_args << "-nbthread" << QString::number(nbthread > 0 ? nbthread : 1);
const QString &dot = g_config->getPlantUMLDot();
const QString &dot = g_config->getGraphvizDot();
if (!dot.isEmpty()) {
p_args << "-graphvizdot";
p_args << dot;