diff --git a/src/dialog/vexportdialog.cpp b/src/dialog/vexportdialog.cpp index cfc23383..0b65d6dc 100644 --- a/src/dialog/vexportdialog.cpp +++ b/src/dialog/vexportdialog.cpp @@ -25,6 +25,7 @@ #include "vnote.h" #include "vexporter.h" #include "vlineedit.h" +#include "utils/vprocessutils.h" extern VConfigManager *g_config; @@ -521,22 +522,24 @@ bool VExportDialog::checkWkhtmltopdfExecutable(const QString &p_file) { QStringList args; args << "--version"; - int ret = QProcess::execute(p_file, args); + QByteArray out, err; + int exitCode = -1; + int ret = VProcessUtils::startProcess(p_file, args, exitCode, out, err); switch (ret) { case -2: - appendLogLine(tr("Fail to start wkhtmltopdf.")); + appendLogLine(tr("Fail to start wkhtmltopdf (%1).").arg(p_file)); break; case -1: - appendLogLine(tr("wkhtmltopdf crashed.")); + appendLogLine(tr("wkhtmltopdf crashed (%1).").arg(p_file)); break; case 0: - appendLogLine(tr("Use %1.").arg(p_file)); + appendLogLine(tr("Use %1 (%2).").arg(p_file).arg(exitCode)); break; default: - appendLogLine(tr("wkhtmltopdf returned %1.").arg(ret)); + Q_ASSERT(false); break; } diff --git a/src/dialog/vsettingsdialog.cpp b/src/dialog/vsettingsdialog.cpp index c71101ef..6847f8ee 100644 --- a/src/dialog/vsettingsdialog.cpp +++ b/src/dialog/vsettingsdialog.cpp @@ -7,6 +7,8 @@ #include "utils/vutils.h" #include "vconstants.h" #include "vlineedit.h" +#include "vplantumlhelper.h" +#include "vgraphvizhelper.h" extern VConfigManager *g_config; @@ -1011,6 +1013,48 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent) m_plantUMLJarEdit = new VLineEdit(); m_plantUMLJarEdit->setToolTip(tr("Location to the PlantUML JAR executable for local PlantUML")); + QPushButton *plantUMLJarTestBtn = new QPushButton(tr("Test")); + plantUMLJarTestBtn->setToolTip(tr("Test PlantUML JAR configuration")); + connect(plantUMLJarTestBtn, &QPushButton::clicked, + this, [this]() { + QString jar = m_plantUMLJarEdit->text(); + if (jar.isEmpty() || !QFileInfo::exists(jar)) { + VUtils::showMessage(QMessageBox::Warning, + tr("Warning"), + tr("The JAR file specified does not exist."), + tr("Please input the right absolute file path to the JAR file."), + QMessageBox::Ok, + QMessageBox::Ok, + this); + return; + } + + if (!jar.trimmed().toLower().endsWith(".jar")) { + VUtils::showMessage(QMessageBox::Warning, + tr("Warning"), + tr("Please specify the absolute file path to the JAR file."), + tr("It should be something like \"/path/to/plantuml.jar\"."), + QMessageBox::Ok, + QMessageBox::Ok, + this); + return; + } + + QString msg; + bool ret = VPlantUMLHelper::testPlantUMLJar(jar, msg); + VUtils::showMessage(QMessageBox::Information, + tr("Information"), + tr("Test %1.").arg((ret ? tr("succeeded") : tr("failed"))), + msg, + QMessageBox::Ok, + QMessageBox::Ok, + this); + }); + + QHBoxLayout *plantUMLLayout = new QHBoxLayout(); + plantUMLLayout->addWidget(m_plantUMLJarEdit); + plantUMLLayout->addWidget(plantUMLJarTestBtn); + // Graphviz. m_graphvizCB = new QCheckBox(tr("Graphviz")); m_graphvizCB->setToolTip(tr("Enable Graphviz for drawing graph")); @@ -1019,6 +1063,30 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent) m_graphvizDotEdit->setPlaceholderText(tr("Empty to detect automatically")); m_graphvizDotEdit->setToolTip(tr("Location to the GraphViz dot executable")); + QPushButton *graphvizTestBtn = new QPushButton(tr("Test")); + graphvizTestBtn->setToolTip(tr("Test Graphviz executable configuration")); + connect(graphvizTestBtn, &QPushButton::clicked, + this, [this]() { + QString dot = m_graphvizDotEdit->text(); + if (dot.isEmpty()) { + dot = "dot"; + } + + QString msg; + bool ret = VGraphvizHelper::testGraphviz(dot, msg); + VUtils::showMessage(QMessageBox::Information, + tr("Information"), + tr("Test %1.").arg((ret ? tr("succeeded") : tr("failed"))), + msg, + QMessageBox::Ok, + QMessageBox::Ok, + this); + }); + + QHBoxLayout *graphvizLayout = new QHBoxLayout(); + graphvizLayout->addWidget(m_graphvizDotEdit); + graphvizLayout->addWidget(graphvizTestBtn); + QFormLayout *mainLayout = new QFormLayout(); mainLayout->addRow(tr("Open mode:"), m_openModeCombo); mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout); @@ -1026,9 +1094,9 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent) mainLayout->addRow(tr("MathJax configuration:"), m_mathjaxConfigEdit); mainLayout->addRow(tr("PlantUML:"), m_plantUMLModeCombo); mainLayout->addRow(tr("PlantUML server:"), m_plantUMLServerEdit); - mainLayout->addRow(tr("PlantUML JAR:"), m_plantUMLJarEdit); + mainLayout->addRow(tr("PlantUML JAR:"), plantUMLLayout); mainLayout->addRow(m_graphvizCB); - mainLayout->addRow(tr("Graphviz executable:"), m_graphvizDotEdit); + mainLayout->addRow(tr("Graphviz executable:"), graphvizLayout); setLayout(mainLayout); } diff --git a/src/resources/mathjax_preview.js b/src/resources/mathjax_preview.js index 3c0c2a61..4efb2af5 100644 --- a/src/resources/mathjax_preview.js +++ b/src/resources/mathjax_preview.js @@ -81,7 +81,7 @@ var postProcessMathJax = function(identifier, id, timeStamp, container, isBlock) return; } - var hei = (isBlock ? container.clientHeight * 1.5 : container.clientHeight * 1.8) + 5; + var hei = (isBlock ? container.clientHeight * 1.5 : container.clientHeight * 1.6) + 5; domtoimage.toPng(container, { height: hei }).then(function (dataUrl) { var png = dataUrl.substring(dataUrl.indexOf(',') + 1); content.mathjaxResultReady(identifier, id, timeStamp, 'png', png); diff --git a/src/src.pro b/src/src.pro index 250059dd..89679ede 100644 --- a/src/src.pro +++ b/src/src.pro @@ -135,7 +135,8 @@ SOURCES += main.cpp\ vhistorylist.cpp \ vexplorer.cpp \ vlistue.cpp \ - vuetitlecontentpanel.cpp + vuetitlecontentpanel.cpp \ + utils/vprocessutils.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -264,7 +265,8 @@ HEADERS += vmainwindow.h \ vexplorer.h \ vexplorerentry.h \ vlistue.h \ - vuetitlecontentpanel.h + vuetitlecontentpanel.h \ + utils/vprocessutils.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vprocessutils.cpp b/src/utils/vprocessutils.cpp new file mode 100644 index 00000000..da3547f3 --- /dev/null +++ b/src/utils/vprocessutils.cpp @@ -0,0 +1,88 @@ +#include "vprocessutils.h" + +#include +#include +#include + +int VProcessUtils::startProcess(const QString &p_program, + const QStringList &p_args, + const QByteArray &p_in, + int &p_exitCode, + QByteArray &p_out, + QByteArray &p_err) +{ + int ret = 0; + QScopedPointer process(new QProcess()); + process->start(p_program, p_args); + + if (!p_in.isEmpty()) { + if (process->write(p_in) == -1) { + process->closeWriteChannel(); + qWarning() << "fail to write to QProcess:" << process->errorString(); + return -1; + } else { + process->closeWriteChannel(); + } + } + + bool finished = false; + bool started = false; + while (true) { + QProcess::ProcessError err = process->error(); + if (err == QProcess::FailedToStart + || err == QProcess::Crashed) { + if (err == QProcess::FailedToStart) { + ret = -2; + } else { + ret = -1; + } + + break; + } + + if (started) { + if (process->state() == QProcess::NotRunning) { + finished = true; + } + } else { + if (process->state() != QProcess::NotRunning) { + started = true; + } + } + + if (process->waitForFinished(500)) { + // Finished. + finished = true; + } + + if (finished) { + QProcess::ExitStatus sta = process->exitStatus(); + if (sta == QProcess::CrashExit) { + ret = -1; + break; + } + + p_exitCode = process->exitCode(); + break; + } + } + + p_out = process->readAllStandardOutput(); + p_err = process->readAllStandardError(); + + return ret; +} + +int VProcessUtils::startProcess(const QString &p_program, + const QStringList &p_args, + int &p_exitCode, + QByteArray &p_out, + QByteArray &p_err) +{ + return startProcess(p_program, + p_args, + QByteArray(), + p_exitCode, + p_out, + p_err); +} diff --git a/src/utils/vprocessutils.h b/src/utils/vprocessutils.h new file mode 100644 index 00000000..afa6ac7b --- /dev/null +++ b/src/utils/vprocessutils.h @@ -0,0 +1,32 @@ +#ifndef VPROCESSUTILS_H +#define VPROCESSUTILS_H + +#include +#include + + +class VProcessUtils +{ +public: + // p_exitCode: exit code of the program, valid only when returning 0. + // 0: execute successfully; + // -1: crashed; + // -2: fail to start. + static int startProcess(const QString &p_program, + const QStringList &p_args, + int &p_exitCode, + QByteArray &p_out, + QByteArray &p_err); + + static int startProcess(const QString &p_program, + const QStringList &p_args, + const QByteArray &p_in, + int &p_exitCode, + QByteArray &p_out, + QByteArray &p_err); + +private: + VProcessUtils() {} +}; + +#endif // VPROCESSUTILS_H diff --git a/src/vgraphvizhelper.cpp b/src/vgraphvizhelper.cpp index b6d17ee9..d89c5721 100644 --- a/src/vgraphvizhelper.cpp +++ b/src/vgraphvizhelper.cpp @@ -4,6 +4,7 @@ #include #include "vconfigmanager.h" +#include "utils/vprocessutils.h" extern VConfigManager *g_config; @@ -96,3 +97,25 @@ void VGraphvizHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus process->deleteLater(); } + +bool VGraphvizHelper::testGraphviz(const QString &p_dot, QString &p_msg) +{ + QString program(p_dot); + QStringList args; + args << "-Tsvg"; + + QString testGraph("digraph G {VNote->Markdown}"); + + int exitCode = -1; + QByteArray out, err; + int ret = VProcessUtils::startProcess(program, args, testGraph.toUtf8(), exitCode, out, err); + + p_msg = QString("Command: %1 %2\nExitCode: %3\nOutput: %4\nError: %5") + .arg(program) + .arg(args.join(' ')) + .arg(exitCode) + .arg(QString::fromLocal8Bit(out)) + .arg(QString::fromLocal8Bit(err)); + + return ret == 0 && exitCode == 0; +} diff --git a/src/vgraphvizhelper.h b/src/vgraphvizhelper.h index ae2b2bf8..6a84327d 100644 --- a/src/vgraphvizhelper.h +++ b/src/vgraphvizhelper.h @@ -18,6 +18,8 @@ public: void prepareCommand(QString &p_cmd, QStringList &p_args) const; + static bool testGraphviz(const QString &p_dot, QString &p_msg); + signals: void resultReady(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_result); diff --git a/src/vplantumlhelper.cpp b/src/vplantumlhelper.cpp index ad8daf43..1d005391 100644 --- a/src/vplantumlhelper.cpp +++ b/src/vplantumlhelper.cpp @@ -4,6 +4,7 @@ #include #include "vconfigmanager.h" +#include "utils/vprocessutils.h" extern VConfigManager *g_config; @@ -120,3 +121,35 @@ void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus process->deleteLater(); } + +bool VPlantUMLHelper::testPlantUMLJar(const QString &p_jar, QString &p_msg) +{ + QString program("java"); + QStringList args; + args << "-jar" << p_jar; + args << "-charset" << "UTF-8"; + + const QString &dot = g_config->getGraphvizDot(); + if (!dot.isEmpty()) { + args << "-graphvizdot"; + args << dot; + } + + args << "-pipe"; + args << "-tsvg"; + + QString testGraph("VNote->Markdown : hello"); + + int exitCode = -1; + QByteArray out, err; + int ret = VProcessUtils::startProcess(program, args, testGraph.toUtf8(), exitCode, out, err); + + p_msg = QString("Command: %1 %2\nExitCode: %3\nOutput: %4\nError: %5") + .arg(program) + .arg(args.join(' ')) + .arg(exitCode) + .arg(QString::fromLocal8Bit(out)) + .arg(QString::fromLocal8Bit(err)); + + return ret == 0 && exitCode == 0; +} diff --git a/src/vplantumlhelper.h b/src/vplantumlhelper.h index 8de56908..58277400 100644 --- a/src/vplantumlhelper.h +++ b/src/vplantumlhelper.h @@ -21,6 +21,8 @@ public: void prepareCommand(QString &p_customCmd, QString &p_cmd, QStringList &p_args) const; + static bool testPlantUMLJar(const QString &p_jar, QString &p_msg); + signals: void resultReady(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_result);