diff --git a/src/dialog/vexportdialog.cpp b/src/dialog/vexportdialog.cpp new file mode 100644 index 00000000..9c5a986d --- /dev/null +++ b/src/dialog/vexportdialog.cpp @@ -0,0 +1,1505 @@ +#include "vexportdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_PRINTER +#include +#include +#endif + +#include "utils/vutils.h" +#include "utils/vclipboardutils.h" +#include "vlineedit.h" +#include "vnotebook.h" +#include "vfile.h" +#include "vdirectory.h" +#include "vcart.h" +#include "vconfigmanager.h" +#include "vnotefile.h" +#include "vnote.h" +#include "vexporter.h" +#include "vlineedit.h" +#include "utils/vprocessutils.h" + +extern VConfigManager *g_config; + +extern VNote *g_vnote; + +QString VExportDialog::s_lastOutputFolder; + +ExportOption VExportDialog::s_opt; + +#define LOGERR(x) do { QString msg = (x); \ + VUtils::addErrMsg(p_errMsg, msg); \ + appendLogLine(msg); \ + } while (0) + +VExportDialog::VExportDialog(VNotebook *p_notebook, + VDirectory *p_directory, + VFile *p_file, + VCart *p_cart, + MarkdownConverterType p_renderer, + QWidget *p_parent) + : QDialog(p_parent), + m_notebook(p_notebook), + m_directory(p_directory), + m_file(p_file), + m_cart(p_cart), + m_pageLayout(QPageLayout(QPageSize(QPageSize::A4), + QPageLayout::Portrait, + QMarginsF(10, 16, 10, 10), + QPageLayout::Millimeter)), + m_inExport(false), + m_askedToStop(false) +{ + if (s_lastOutputFolder.isEmpty()) { + s_lastOutputFolder = g_config->getExportFolderPath(); + } + + setupUI(); + + m_exporter = new VExporter(this); + connect(m_exporter, &VExporter::outputLog, + this, [this](const QString &p_log) { + appendLogLine(p_log); + }); + + initUIFields(p_renderer); + + handleInputChanged(); +} + +void VExportDialog::setupUI() +{ + // Notes to export. + m_srcCB = VUtils::getComboBox(); + m_srcCB->setToolTip(tr("Choose notes to export")); + m_srcCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); + connect(m_srcCB, SIGNAL(currentIndexChanged(int)), + this, SLOT(handleCurrentSrcChanged(int))); + + // Target format. + m_formatCB = VUtils::getComboBox(); + m_formatCB->setToolTip(tr("Choose target format to export as")); + m_formatCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); + connect(m_formatCB, SIGNAL(currentIndexChanged(int)), + this, SLOT(handleCurrentFormatChanged(int))); + + // Markdown renderer. + m_rendererCB = VUtils::getComboBox(); + m_rendererCB->setToolTip(tr("Choose converter to render Markdown")); + m_rendererCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + // Markdown rendering background. + m_renderBgCB = VUtils::getComboBox(); + m_renderBgCB->setToolTip(tr("Choose rendering background color for Markdown")); + + // Markdown rendering style. + m_renderStyleCB = VUtils::getComboBox(); + m_renderStyleCB->setToolTip(tr("Choose rendering style for Markdown")); + + // Markdown rendering code block style. + m_renderCodeBlockStyleCB = VUtils::getComboBox(); + m_renderCodeBlockStyleCB->setToolTip(tr("Choose rendering code block style for Markdown")); + + // Output directory. + m_outputEdit = new VLineEdit(s_lastOutputFolder); + connect(m_outputEdit, &QLineEdit::textChanged, + this, &VExportDialog::handleInputChanged); + QPushButton *browseBtn = new QPushButton(tr("&Browse")); + connect(browseBtn, &QPushButton::clicked, + this, &VExportDialog::handleOutputBrowseBtnClicked); + QHBoxLayout *outputLayout = new QHBoxLayout(); + outputLayout->addWidget(m_outputEdit); + outputLayout->addWidget(browseBtn); + + m_basicBox = new QGroupBox(tr("Information")); + + m_settingBox = new QGroupBox(tr("Advanced Settings")); + + m_consoleEdit = new QPlainTextEdit(); + m_consoleEdit->setReadOnly(true); + m_consoleEdit->setLineWrapMode(QPlainTextEdit::WidgetWidth); + m_consoleEdit->setProperty("LineEdit", true); + m_consoleEdit->setPlaceholderText(tr("Output logs will be shown here")); + + // Ok is the default button. + m_btnBox = new QDialogButtonBox(QDialogButtonBox::Close); + m_exportBtn = m_btnBox->addButton(tr("Export"), QDialogButtonBox::ActionRole); + m_openBtn = m_btnBox->addButton(tr("Open Directory"), QDialogButtonBox::ActionRole); + m_openBtn->setToolTip(tr("Open output directory")); + m_copyBtn = m_btnBox->addButton(tr("Copy Content"), QDialogButtonBox::ActionRole); + m_copyBtn->setToolTip(tr("Copy the content of the exported file")); + m_copyBtn->setEnabled(false); + connect(m_btnBox, &QDialogButtonBox::rejected, + this, [this]() { + if (m_inExport) { + // Just cancel the export. Do not exit. + m_askedToStop = true; + m_exporter->setAskedToStop(true); + appendLogLine(tr("Cancelling the export...")); + } else { + QDialog::reject(); + } + }); + + m_exportBtn->setProperty("SpecialBtn", true); + connect(m_exportBtn, &QPushButton::clicked, + this, &VExportDialog::startExport); + + connect(m_openBtn, &QPushButton::clicked, + this, [this]() { + QUrl url = QUrl::fromLocalFile(getOutputDirectory()); + QDesktopServices::openUrl(url); + }); + + connect(m_copyBtn, &QPushButton::clicked, + this, [this]() { + if (m_exportedFile.isEmpty()) { + return; + } + + bool ret = false; + QString content = VUtils::readFileFromDisk(m_exportedFile); + if (!content.isNull()) { + QMimeData *data = new QMimeData(); + data->setText(content); + VClipboardUtils::setMimeDataToClipboard(QApplication::clipboard(), data, QClipboard::Clipboard); + ret = true; + } + + if (ret) { + appendLogLine(tr("Copied content of file %1").arg(m_exportedFile)); + } else { + appendLogLine(tr("Fail to copy content of file %1").arg(m_exportedFile)); + } + }); + + // Progress bar. + m_proBar = new QProgressBar(); + m_proBar->setRange(0, 0); + m_proBar->hide(); + + QHBoxLayout *btnLayout = new QHBoxLayout(); + btnLayout->addWidget(m_proBar); + btnLayout->addWidget(m_btnBox); + + QFormLayout *basicLayout = new QFormLayout(); + basicLayout->addRow(tr("Notes to export:"), m_srcCB); + basicLayout->addRow(tr("Target format:"), m_formatCB); + basicLayout->addRow(tr("Markdown renderer:"), m_rendererCB); + basicLayout->addRow(tr("Markdown rendering background:"), m_renderBgCB); + basicLayout->addRow(tr("Markdown rendering style:"), m_renderStyleCB); + basicLayout->addRow(tr("Markdown rendering code block style:"), m_renderCodeBlockStyleCB); + basicLayout->addRow(tr("Output directory:"), outputLayout); + + m_basicBox->setLayout(basicLayout); + + // Settings box. + m_generalSettings = setupGeneralAdvancedSettings(); + m_htmlSettings = setupHTMLAdvancedSettings(); + m_pdfSettings = setupPDFAdvancedSettings(); + m_customSettings = setupCustomAdvancedSettings(); + + QVBoxLayout *advLayout = new QVBoxLayout(); + advLayout->addWidget(m_generalSettings); + advLayout->addWidget(m_htmlSettings); + advLayout->addWidget(m_pdfSettings); + advLayout->addWidget(m_customSettings); + + m_settingBox->setLayout(advLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout(); + mainLayout->addWidget(m_basicBox); + mainLayout->addWidget(m_settingBox); + mainLayout->addWidget(m_consoleEdit); + mainLayout->addLayout(btnLayout); + + setLayout(mainLayout); + + setWindowTitle(tr("Export")); +} + +QWidget *VExportDialog::setupPDFAdvancedSettings() +{ + // Page layout settings. + m_layoutLabel = new QLabel(); + QPushButton *layoutBtn = new QPushButton(tr("Settings")); + +#ifndef QT_NO_PRINTER + connect(layoutBtn, &QPushButton::clicked, + this, &VExportDialog::handleLayoutBtnClicked); +#else + layoutBtn->hide(); +#endif + + updatePageLayoutLabel(); + + // Enable table of contents. + m_tableOfContentsCB = new QCheckBox(tr("Enable Table Of Contents")); + m_tableOfContentsCB->setToolTip(tr("Add a table of contents to the document")); + + // Use wkhtmltopdf. + m_wkhtmltopdfCB = new QCheckBox(tr("Use wkhtmltopdf")); + m_wkhtmltopdfCB->setToolTip(tr("Use wkhtmltopdf tool to generate PDF (wkhtmltopdf needed to be installed)")); + connect(m_wkhtmltopdfCB, &QCheckBox::stateChanged, + this, [this](int p_state) { + bool checked = p_state == Qt::Checked; + m_wkPathEdit->setEnabled(checked); + m_wkPathBrowseBtn->setEnabled(checked); + m_wkBackgroundCB->setEnabled(checked); + m_wkPageNumberCB->setEnabled(checked); + m_wkExtraArgsEdit->setEnabled(checked); + }); + + QPushButton *wkBtn = new QPushButton(tr("Download wkhtmltopdf")); + connect(wkBtn, &QPushButton::clicked, + this, [this]() { + QString url("https://wkhtmltopdf.org/downloads.html"); + QDesktopServices::openUrl(QUrl(url)); + }); + + // wkhtmltopdf Path. + m_wkPathEdit = new VLineEdit(); + m_wkPathEdit->setToolTip(tr("Tell VNote where to find wkhtmltopdf tool")); + m_wkPathEdit->setEnabled(false); + + m_wkPathBrowseBtn = new QPushButton(tr("&Browse")); + connect(m_wkPathBrowseBtn, &QPushButton::clicked, + this, &VExportDialog::handleWkPathBrowseBtnClicked); + m_wkPathBrowseBtn->setEnabled(false); + + m_wkTitleEdit = new VLineEdit(); + m_wkTitleEdit->setPlaceholderText(tr("Empty to use the name of the first source file")); + m_wkTitleEdit->setToolTip(tr("Title of the generated PDF file")); + m_wkTitleEdit->setEnabled(false); + + m_wkTargetFileNameEdit = new VLineEdit(); + m_wkTargetFileNameEdit->setPlaceholderText(tr("Empty to use the name of the first source file")); + m_wkTargetFileNameEdit->setToolTip(tr("Name of the generated PDF file")); + QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), + m_wkTargetFileNameEdit); + m_wkTargetFileNameEdit->setValidator(validator); + m_wkTargetFileNameEdit->setEnabled(false); + + // wkhtmltopdf enable background. + m_wkBackgroundCB = new QCheckBox(tr("Enable background")); + m_wkBackgroundCB->setToolTip(tr("Enable background when printing")); + m_wkBackgroundCB->setEnabled(false); + + // wkhtmltopdf page number. + m_wkPageNumberCB = VUtils::getComboBox(); + m_wkPageNumberCB->setToolTip(tr("Append page number as footer")); + m_wkPageNumberCB->setEnabled(false); + + // wkhtmltopdf extra argumnets. + m_wkExtraArgsEdit = new VLineEdit(); + m_wkExtraArgsEdit->setToolTip(tr("Additional global options passed to wkhtmltopdf")); + m_wkExtraArgsEdit->setPlaceholderText(tr("Use \" to enclose options containing spaces")); + m_wkExtraArgsEdit->setEnabled(false); + + QGridLayout *advLayout = new QGridLayout(); + advLayout->addWidget(new QLabel(tr("Page layout:")), 0, 0); + advLayout->addWidget(m_layoutLabel, 0, 1); + advLayout->addWidget(layoutBtn, 0, 2); + advLayout->addWidget(m_tableOfContentsCB, 0, 4, 1, 2); + + advLayout->addWidget(m_wkhtmltopdfCB, 1, 1, 1, 2); + advLayout->addWidget(wkBtn, 1, 4, 1, 2); + + advLayout->addWidget(new QLabel(tr("wkhtmltopdf path:")), 2, 0); + advLayout->addWidget(m_wkPathEdit, 2, 1, 1, 4); + advLayout->addWidget(m_wkPathBrowseBtn, 2, 5); + + advLayout->addWidget(new QLabel(tr("Title:")), 3, 0); + advLayout->addWidget(m_wkTitleEdit, 3, 1, 1, 2); + + advLayout->addWidget(new QLabel(tr("Output file name:")), 3, 3); + advLayout->addWidget(m_wkTargetFileNameEdit, 3, 4, 1, 2); + + advLayout->addWidget(new QLabel(tr("Page number:")), 4, 0); + advLayout->addWidget(m_wkPageNumberCB, 4, 1, 1, 2); + advLayout->addWidget(m_wkBackgroundCB, 4, 4, 1, 2); + + advLayout->addWidget(new QLabel(tr("Additional options:")), 5, 0); + advLayout->addWidget(m_wkExtraArgsEdit, 5, 1, 1, 5); + + advLayout->setContentsMargins(0, 0, 0, 0); + + QWidget *wid = new QWidget(); + wid->setLayout(advLayout); + + return wid; +} + +QWidget *VExportDialog::setupHTMLAdvancedSettings() +{ + // Embed CSS styles. + m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this); + m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file")); + + // Embed images as data URI. + m_embedImagesCB = new QCheckBox(tr("Embed images"), this); + m_embedImagesCB->setToolTip(tr("Embed images as data URI")); + + // Complete HTML. + m_completeHTMLCB = new QCheckBox(tr("Complete page"), this); + m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures " + "which may not keep the HTML link structure of " + "the original page")); + connect(m_completeHTMLCB, &QCheckBox::stateChanged, + this, [this](int p_state) { + bool checked = p_state == Qt::Checked; + m_embedImagesCB->setEnabled(checked); + if (!checked) { + m_embedImagesCB->setChecked(false); + } + }); + + // Mime HTML. + m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this); + m_mimeHTMLCB->setToolTip(tr("Export as a complete web page in MIME HTML format")); + connect(m_mimeHTMLCB, &QCheckBox::stateChanged, + this, [this](int p_state) { + bool checked = p_state == Qt::Checked; + m_embedStyleCB->setEnabled(!checked); + m_completeHTMLCB->setEnabled(!checked); + }); + + // Outline panel. + m_outlinePanelCB = new QCheckBox(tr("Enable outline panel"), this); + m_outlinePanelCB->setToolTip(tr("Add an outline panel in HTML file")); + + QGridLayout *advLayout = new QGridLayout(); + advLayout->addWidget(m_embedStyleCB, 0, 1, 1, 2); + advLayout->addWidget(m_completeHTMLCB, 0, 4, 1, 2); + advLayout->addWidget(m_embedImagesCB, 1, 1, 1, 2); + advLayout->addWidget(m_mimeHTMLCB, 1, 4, 1, 2); + advLayout->addWidget(m_outlinePanelCB, 2, 1, 1, 2); + + advLayout->setContentsMargins(0, 0, 0, 0); + + QWidget *wid = new QWidget(); + wid->setLayout(advLayout); + + return wid; +} + +QWidget *VExportDialog::setupGeneralAdvancedSettings() +{ + // Include subfolders. + m_subfolderCB = new QCheckBox(tr("Process subfolders")); + m_subfolderCB->setToolTip(tr("Process subfolders recursively")); + + QFormLayout *advLayout = new QFormLayout(); + advLayout->addRow(m_subfolderCB); + + advLayout->setContentsMargins(0, 0, 0, 0); + + QWidget *wid = new QWidget(); + wid->setLayout(advLayout); + + return wid; +} + +void VExportDialog::initUIFields(MarkdownConverterType p_renderer) +{ + // Notes to export. + if (m_file) { + m_srcCB->addItem(tr("Current Note (%1)").arg(m_file->getName()), + (int)ExportSource::CurrentNote); + } + + if (m_directory) { + m_srcCB->addItem(tr("Current Folder (%1)").arg(m_directory->getName()), + (int)ExportSource::CurrentFolder); + } + + if (m_notebook) { + m_srcCB->addItem(tr("Current Notebook (%1)").arg(m_notebook->getName()), + (int)ExportSource::CurrentNotebook); + } + + if (m_cart && m_cart->count() > 0) { + m_srcCB->addItem(tr("Cart (%1)").arg(m_cart->count()), + (int)ExportSource::Cart); + } + + m_subfolderCB->setChecked(s_opt.m_processSubfolders); + + // Export format. + m_formatCB->addItem(tr("Markdown"), (int)ExportFormat::Markdown); + m_formatCB->addItem(tr("HTML"), (int)ExportFormat::HTML); + m_formatCB->addItem(tr("PDF"), (int)ExportFormat::PDF); + m_formatCB->addItem(tr("PDF (All In One)"), (int)ExportFormat::OnePDF); + m_formatCB->addItem(tr("Custom"), (int)ExportFormat::Custom); + m_formatCB->setCurrentIndex(m_formatCB->findData((int)s_opt.m_format)); + + // Markdown renderer. + m_rendererCB->addItem(tr("Hoedown"), MarkdownConverterType::Hoedown); + m_rendererCB->addItem(tr("Marked"), MarkdownConverterType::Marked); + m_rendererCB->addItem(tr("Markdown-it"), MarkdownConverterType::MarkdownIt); + m_rendererCB->addItem(tr("Showdown"), MarkdownConverterType::Showdown); + m_rendererCB->setCurrentIndex(m_rendererCB->findData(p_renderer)); + + // Markdown rendering background. + m_renderBgCB->addItem(tr("System"), "System"); + m_renderBgCB->addItem(tr("Transparent"), "Transparent"); + const QVector &bgColors = g_config->getCustomColors(); + for (int i = 0; i < bgColors.size(); ++i) { + m_renderBgCB->addItem(bgColors[i].m_name, bgColors[i].m_name); + } + + if (s_opt.m_renderBg.isEmpty()) { + s_opt.m_renderBg = g_config->getCurRenderBackgroundColor(); + } + + m_renderBgCB->setCurrentIndex(m_renderBgCB->findData(s_opt.m_renderBg)); + + // Markdown rendering style. + QList styles = g_config->getCssStyles(); + for (auto const &style : styles) { + m_renderStyleCB->addItem(style, style); + } + + m_renderStyleCB->setCurrentIndex( + m_renderStyleCB->findData(g_config->getCssStyle())); + + // Markdown rendering code block style. + QList cbStyles = g_config->getCodeBlockCssStyles(); + for (auto const &style : cbStyles) { + m_renderCodeBlockStyleCB->addItem(style, style); + } + + m_renderCodeBlockStyleCB->setCurrentIndex( + m_renderCodeBlockStyleCB->findData(g_config->getCodeBlockCssStyle())); + + m_embedStyleCB->setChecked(s_opt.m_htmlOpt.m_embedCssStyle); + + m_completeHTMLCB->setChecked(s_opt.m_htmlOpt.m_completeHTML); + + m_embedImagesCB->setChecked(s_opt.m_htmlOpt.m_embedImages); + + // m_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML); + m_mimeHTMLCB->setChecked(false); + m_mimeHTMLCB->setEnabled(false); + + m_outlinePanelCB->setChecked(s_opt.m_htmlOpt.m_outlinePanel); + + m_tableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_enableTableOfContents); + + // m_wkhtmltopdfCB->setChecked(s_opt.m_pdfOpt.m_wkhtmltopdf); + m_wkhtmltopdfCB->setChecked(true); + m_wkhtmltopdfCB->setEnabled(false); + + // wkhtmltopdf path. + m_wkPathEdit->setText(g_config->getWkhtmltopdfPath()); + + m_wkBackgroundCB->setChecked(s_opt.m_pdfOpt.m_wkEnableBackground); + + // wkhtmltopdf page number. + m_wkPageNumberCB->addItem(tr("None"), (int)ExportPageNumber::None); + m_wkPageNumberCB->addItem(tr("Left"), (int)ExportPageNumber::Left); + m_wkPageNumberCB->addItem(tr("Center"), (int)ExportPageNumber::Center); + m_wkPageNumberCB->addItem(tr("Right"), (int)ExportPageNumber::Right); + m_wkPageNumberCB->setCurrentIndex(m_wkPageNumberCB->findData((int)s_opt.m_pdfOpt.m_wkPageNumber)); + + m_wkExtraArgsEdit->setText(g_config->getWkhtmltopdfArgs()); + + // Custom export. + // Read from config every time. + ExportCustomOption customOpt(g_config->getCustomExport()); + + m_customSrcFormatCB->addItem(tr("Markdown"), (int)ExportCustomOption::SourceFormat::Markdown); + m_customSrcFormatCB->addItem(tr("HTML"), (int)ExportCustomOption::SourceFormat::HTML); + m_customSrcFormatCB->setCurrentIndex(m_customSrcFormatCB->findData((int)customOpt.m_srcFormat)); + + m_customSuffixEdit->setText(customOpt.m_outputSuffix); + + m_customCmdEdit->setPlainText(customOpt.m_cmd); + + m_customAllInOneCB->setChecked(s_opt.m_customOpt.m_allInOne); + + m_customPdfLikeCB->setChecked(s_opt.m_customOpt.m_pdfLike); + + m_customFolderSepEdit->setText(s_opt.m_customOpt.m_folderSep); +} + +bool VExportDialog::checkWkhtmltopdfExecutable(const QString &p_file) +{ + QStringList args; + args << "--version"; + 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 (%1).").arg(p_file)); + break; + + case -1: + appendLogLine(tr("wkhtmltopdf crashed (%1).").arg(p_file)); + break; + + case 0: + appendLogLine(tr("Use %1 (%2).").arg(p_file).arg(exitCode)); + break; + + default: + Q_ASSERT(false); + break; + } + + return ret == 0; +} + +void VExportDialog::startExport() +{ + if (m_inExport) { + return; + } + + m_exportBtn->setEnabled(false); + m_proBar->show(); + m_askedToStop = false; + m_exporter->setAskedToStop(false); + m_inExport = true; + m_exportedFile.clear(); + m_copyBtn->setEnabled(false); + + QString outputFolder = QDir::cleanPath(QDir(getOutputDirectory()).absolutePath()); + + QString renderStyle = m_renderStyleCB->currentData().toString(); + QString renderCodeBlockStyle = m_renderCodeBlockStyleCB->currentData().toString(); + + s_opt = ExportOption(currentSource(), + currentFormat(), + (MarkdownConverterType)m_rendererCB->currentData().toInt(), + m_renderBgCB->currentData().toString(), + renderStyle, + renderCodeBlockStyle, + m_subfolderCB->isChecked(), + ExportPDFOption(&m_pageLayout, + // m_wkhtmltopdfCB->isChecked(), + true, + QDir::toNativeSeparators(m_wkPathEdit->text()), + m_wkBackgroundCB->isChecked(), + m_tableOfContentsCB->isChecked(), + m_wkTitleEdit->text(), + m_wkTargetFileNameEdit->text(), + (ExportPageNumber) + m_wkPageNumberCB->currentData().toInt(), + m_wkExtraArgsEdit->text()), + ExportHTMLOption(m_embedStyleCB->isChecked(), + m_completeHTMLCB->isChecked(), + m_embedImagesCB->isChecked(), + m_mimeHTMLCB->isChecked(), + m_outlinePanelCB->isChecked()), + ExportCustomOption((ExportCustomOption::SourceFormat) + m_customSrcFormatCB->currentData().toInt(), + m_customSuffixEdit->text(), + m_customCmdEdit->toPlainText(), + g_config->getCssStyleUrl(renderStyle), + g_config->getCodeBlockCssStyleUrl(renderCodeBlockStyle), + m_customAllInOneCB->isChecked(), + m_customPdfLikeCB->isChecked(), + m_customFolderSepEdit->text(), + m_customTargetFileNameEdit->text())); + + m_consoleEdit->clear(); + appendLogLine(tr("Export to %1.").arg(outputFolder)); + + if (s_opt.m_format == ExportFormat::PDF + || s_opt.m_format == ExportFormat::OnePDF + || s_opt.m_format == ExportFormat::HTML) { + if (s_opt.m_format != ExportFormat::OnePDF) { + s_opt.m_pdfOpt.m_wkTitle.clear(); + } + + if ((s_opt.m_format == ExportFormat::PDF + && s_opt.m_pdfOpt.m_wkhtmltopdf) + || s_opt.m_format == ExportFormat::OnePDF) { + g_config->setWkhtmltopdfPath(s_opt.m_pdfOpt.m_wkPath); + g_config->setWkhtmltopdfArgs(s_opt.m_pdfOpt.m_wkExtraArgs); + + if (!checkWkhtmltopdfExecutable(s_opt.m_pdfOpt.m_wkPath)) { + m_inExport = false; + m_exportBtn->setEnabled(true); + m_proBar->hide(); + return; + } + } + + m_exporter->prepareExport(s_opt); + } else if (s_opt.m_format == ExportFormat::Custom) { + const ExportCustomOption &opt = s_opt.m_customOpt; + if (opt.m_srcFormat == ExportCustomOption::HTML) { + m_exporter->prepareExport(s_opt); + } + + // Save it to config. + g_config->setCustomExport(opt.toConfig()); + + if (opt.m_outputSuffix.isEmpty() + || opt.m_cmd.isEmpty() + || (opt.m_allInOne && opt.m_folderSep.isEmpty())) { + appendLogLine(tr("Invalid configurations for custom export.")); + m_inExport = false; + m_exportBtn->setEnabled(true); + m_proBar->hide(); + return; + } + } + + int ret = 0; + QString msg; + + if (s_opt.m_format == ExportFormat::OnePDF) { + QList files; + // Output HTMLs to a tmp folder. + QTemporaryDir tmpDir; + if (!tmpDir.isValid()) { + goto exit; + } + + ret = outputAsHTML(tmpDir.path(), &msg, &files); + if (m_askedToStop) { + ret = 0; + goto exit; + } + + Q_ASSERT(ret == files.size()); + if (!files.isEmpty()) { + ret = doExportPDFAllInOne(files, s_opt, outputFolder, &msg); + } + } else if (s_opt.m_format == ExportFormat::Custom + && s_opt.m_customOpt.m_allInOne) { + QList files; + QTemporaryDir tmpDir; + if (!tmpDir.isValid()) { + goto exit; + } + + if (s_opt.m_customOpt.m_srcFormat == ExportCustomOption::HTML) { + // Output HTMLs to a tmp folder. + ret = outputAsHTML(tmpDir.path(), &msg, &files); + if (m_askedToStop) { + ret = 0; + goto exit; + } + + Q_ASSERT(ret == files.size()); + } else { + // Collect all markdown files. + files = collectFiles(&msg); + } + + if (!files.isEmpty()) { + ret = doExportCustomAllInOne(files, s_opt, outputFolder, &msg); + } + } else { + switch (s_opt.m_source) { + case ExportSource::CurrentNote: + { + QStringList files; + ret = doExport(m_file, s_opt, outputFolder, &msg, &files); + if (ret == 1 && s_opt.m_format == ExportFormat::HTML) { + Q_ASSERT(files.size() == 1); + m_exportedFile = files.first(); + } + + break; + } + + case ExportSource::CurrentFolder: + ret = doExport(m_directory, s_opt, outputFolder, &msg); + break; + + case ExportSource::CurrentNotebook: + ret = doExport(m_notebook, s_opt, outputFolder, &msg); + break; + + case ExportSource::Cart: + ret = doExport(m_cart, s_opt, outputFolder, &msg); + break; + + default: + break; + } + } + +exit: + + if (m_askedToStop) { + appendLogLine(tr("User cancelled the export. Aborted!")); + m_askedToStop = false; + m_exporter->setAskedToStop(false); + } + + if (!msg.isEmpty()) { + VUtils::showMessage(QMessageBox::Warning, + tr("Warning"), + tr("Errors found during export."), + msg, + QMessageBox::Ok, + QMessageBox::Ok, + this); + } + + appendLogLine(tr("%1 notes exported.").arg(ret)); + + m_inExport = false; + m_exportBtn->setEnabled(true); + m_proBar->hide(); + + m_copyBtn->setEnabled(!m_exportedFile.isEmpty()); +} + +QString VExportDialog::getOutputDirectory() const +{ + return m_outputEdit->text(); +} + +void VExportDialog::handleOutputBrowseBtnClicked() +{ + QString initPath = getOutputDirectory(); + if (!QFileInfo::exists(initPath)) { + initPath = g_config->getDocumentPathOrHomePath(); + } + + QString dirPath = QFileDialog::getExistingDirectory(this, + tr("Select Output Directory To Export To"), + initPath, + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + + if (!dirPath.isEmpty()) { + m_outputEdit->setText(dirPath); + s_lastOutputFolder = dirPath; + } +} + +void VExportDialog::handleWkPathBrowseBtnClicked() +{ + QString initPath = m_wkPathEdit->text(); + if (!QFileInfo::exists(initPath)) { + initPath = QCoreApplication::applicationDirPath(); + } + +#if defined(Q_OS_WIN) + QString filter = tr("Executable (*.exe)"); +#else + QString filter; +#endif + + QString filePath = QFileDialog::getOpenFileName(this, + tr("Select wkhtmltopdf Executable"), + initPath, + filter); + + if (!filePath.isEmpty()) { + m_wkPathEdit->setText(filePath); + } +} + +void VExportDialog::handleInputChanged() +{ + // Source. + bool sourceOk = true; + if (m_srcCB->count() == 0) { + sourceOk = false; + } + + // Output folder. + bool pathOk = true; + QString path = getOutputDirectory(); + if (path.isEmpty() || !VUtils::checkPathLegal(path)) { + pathOk = false; + } + + m_exportBtn->setEnabled(sourceOk && pathOk); + m_openBtn->setEnabled(pathOk); +} + +void VExportDialog::appendLogLine(const QString &p_text) +{ + m_consoleEdit->appendPlainText(">>> " + p_text); + m_consoleEdit->ensureCursorVisible(); + QCoreApplication::sendPostedEvents(); +} + +int VExportDialog::doExport(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_ASSERT(p_file); + + appendLogLine(tr("Exporting note %1.").arg(p_file->fetchPath())); + + int ret = 0; + switch (p_opt.m_format) { + case ExportFormat::Markdown: + ret = doExportMarkdown(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportFormat::PDF: + V_FALLTHROUGH; + case ExportFormat::OnePDF: + ret = doExportPDF(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportFormat::HTML: + ret = doExportHTML(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportFormat::Custom: + ret = doExportCustom(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + default: + break; + } + + return ret; +} + +int VExportDialog::doExport(VDirectory *p_directory, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_ASSERT(p_directory); + + bool opened = p_directory->isOpened(); + if (!opened && !p_directory->open()) { + LOGERR(tr("Fail to open folder %1.").arg(p_directory->fetchRelativePath())); + return 0; + } + + int ret = 0; + + QString folderName = VUtils::getDirNameWithSequence(p_outputFolder, + p_directory->getName()); + QString outputPath = QDir(p_outputFolder).filePath(folderName); + if (!VUtils::makePath(outputPath)) { + LOGERR(tr("Fail to create directory %1.").arg(outputPath)); + goto exit; + } + + // Export child notes. + for (auto const & file : p_directory->getFiles()) { + if (!checkUserAction()) { + goto exit; + } + + ret += doExport(file, p_opt, outputPath, p_errMsg, p_outputFiles); + } + + // Export subfolders. + if (p_opt.m_processSubfolders) { + for (auto const & dir : p_directory->getSubDirs()) { + if (!checkUserAction()) { + goto exit; + } + + ret += doExport(dir, p_opt, outputPath, p_errMsg, p_outputFiles); + } + } + +exit: + if (!opened) { + p_directory->close(); + } + + return ret; +} + +int VExportDialog::doExport(VNotebook *p_notebook, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_ASSERT(p_notebook); + + bool opened = p_notebook->isOpened(); + if (!opened && !p_notebook->open()) { + LOGERR(tr("Fail to open notebook %1.").arg(p_notebook->getName())); + return 0; + } + + int ret = 0; + + QString folderName = VUtils::getDirNameWithSequence(p_outputFolder, + p_notebook->getName()); + QString outputPath = QDir(p_outputFolder).filePath(folderName); + if (!VUtils::makePath(outputPath)) { + LOGERR(tr("Fail to create directory %1.").arg(outputPath)); + goto exit; + } + + // Export subfolder. + for (auto const & dir : p_notebook->getRootDir()->getSubDirs()) { + if (!checkUserAction()) { + goto exit; + } + + ret += doExport(dir, p_opt, outputPath, p_errMsg, p_outputFiles); + } + +exit: + if (!opened) { + p_notebook->close(); + } + + return ret; +} + +int VExportDialog::doExport(VCart *p_cart, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_UNUSED(p_cart); + Q_ASSERT(p_cart); + int ret = 0; + + QVector files = m_cart->getFiles(); + for (auto const & it : files) { + VFile *file = g_vnote->getFile(it); + if (!file) { + LOGERR(tr("Fail to open file %1.").arg(it)); + continue; + } + + ret += doExport(file, p_opt, p_outputFolder, p_errMsg, p_outputFiles); + } + + return ret; +} + +int VExportDialog::doExportMarkdown(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_UNUSED(p_opt); + + QString srcFilePath(p_file->fetchPath()); + + if (p_file->getDocType() != DocType::Markdown) { + LOGERR(tr("Skip exporting non-Markdown file %1 as Markdown.").arg(srcFilePath)); + return 0; + } + + // Export it to a folder with the same name. + QString name = VUtils::getDirNameWithSequence(p_outputFolder, p_file->getName()); + QString outputPath = QDir(p_outputFolder).filePath(name); + if (!VUtils::makePath(outputPath)) { + LOGERR(tr("Fail to create directory %1.").arg(outputPath)); + return 0; + } + + // Copy the note file. + QString destPath = QDir(outputPath).filePath(p_file->getName()); + if (!VUtils::copyFile(srcFilePath, destPath, false)) { + LOGERR(tr("Fail to copy the note file %1.").arg(srcFilePath)); + return 0; + } + + // Copy images. + int ret = 1; + int nrImageCopied = 0; + QVector images = VUtils::fetchImagesFromMarkdownFile(p_file, + ImageLink::LocalRelativeInternal); + if (!VNoteFile::copyInternalImages(images, + outputPath, + false, + &nrImageCopied, + p_errMsg)) { + ret = 0; + appendLogLine(tr("Fail to copy images of note %1.").arg(srcFilePath)); + } + + // Copy attachments. + if (p_file->getType() == FileType::Note) { + VNoteFile *noteFile = static_cast(p_file); + QString attaFolder = noteFile->getAttachmentFolder(); + if (!attaFolder.isEmpty()) { + QString attaFolderPath; + attaFolderPath = noteFile->fetchAttachmentFolderPath(); + QString relativePath = QDir(noteFile->fetchBasePath()).relativeFilePath(attaFolderPath); + QString folderPath = QDir(outputPath).filePath(relativePath); + + // Copy attaFolder to folderPath. + if (!VUtils::copyDirectory(attaFolderPath, folderPath, false)) { + LOGERR(tr("Fail to copy attachments folder %1 to %2.") + .arg(attaFolderPath).arg(folderPath)); + ret = 0; + } + } + } + + if (ret) { + if (p_outputFiles) { + p_outputFiles->append(destPath); + } + + appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath)); + } + + return ret; +} + +int VExportDialog::doExportPDF(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_UNUSED(p_opt); + + QString srcFilePath(p_file->fetchPath()); + + if (p_file->getDocType() != DocType::Markdown) { + LOGERR(tr("Skip exporting non-Markdown file %1 as PDF.").arg(srcFilePath)); + return 0; + } + + if (!VUtils::makePath(p_outputFolder)) { + LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder)); + return 0; + } + + // Get output file. + QString suffix = ".pdf"; + QString name = VUtils::getFileNameWithSequence(p_outputFolder, + QFileInfo(p_file->getName()).completeBaseName() + suffix); + QString outputPath = QDir(p_outputFolder).filePath(name); + + if (m_exporter->exportPDF(p_file, p_opt, outputPath, p_errMsg)) { + if (p_outputFiles) { + p_outputFiles->append(outputPath); + } + + appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath)); + return 1; + } else { + appendLogLine(tr("Fail to export note %1.").arg(srcFilePath)); + return 0; + } +} + +int VExportDialog::doExportHTML(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_UNUSED(p_opt); + + QString srcFilePath(p_file->fetchPath()); + + if (p_file->getDocType() != DocType::Markdown) { + LOGERR(tr("Skip exporting non-Markdown file %1 as HTML.").arg(srcFilePath)); + return 0; + } + + if (!VUtils::makePath(p_outputFolder)) { + LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder)); + return 0; + } + + // Get output file. + QString suffix = p_opt.m_htmlOpt.m_mimeHTML ? ".mht" : ".html"; + QString name = VUtils::getFileNameWithSequence(p_outputFolder, + QFileInfo(p_file->getName()).completeBaseName() + suffix); + QString outputPath = QDir(p_outputFolder).filePath(name); + + if (m_exporter->exportHTML(p_file, p_opt, outputPath, p_errMsg)) { + if (p_outputFiles) { + p_outputFiles->append(outputPath); + } + + appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath)); + return 1; + } else { + appendLogLine(tr("Fail to export note %1.").arg(srcFilePath)); + return 0; + } +} + +int VExportDialog::doExportCustom(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + Q_UNUSED(p_opt); + + QString srcFilePath(p_file->fetchPath()); + + if (p_file->getDocType() != DocType::Markdown) { + LOGERR(tr("Skip exporting non-Markdown file %1.").arg(srcFilePath)); + return 0; + } + + if (!VUtils::makePath(p_outputFolder)) { + LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder)); + return 0; + } + + // Get output file. + QString suffix = "." + p_opt.m_customOpt.m_outputSuffix; + QString name = VUtils::getFileNameWithSequence(p_outputFolder, + QFileInfo(p_file->getName()).completeBaseName() + suffix); + QString outputPath = QDir(p_outputFolder).filePath(name); + + if (m_exporter->exportCustom(p_file, p_opt, outputPath, p_errMsg)) { + if (p_outputFiles) { + p_outputFiles->append(outputPath); + } + + appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath)); + return 1; + } else { + appendLogLine(tr("Fail to export note %1.").arg(srcFilePath)); + return 0; + } +} + +bool VExportDialog::checkUserAction() +{ + if (m_askedToStop) { + return false; + } + + QCoreApplication::processEvents(); + + return true; +} + +void VExportDialog::handleLayoutBtnClicked() +{ +#ifndef QT_NO_PRINTER + QPrinter printer; + printer.setPageLayout(m_pageLayout); + + QPageSetupDialog dlg(&printer, this); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + m_pageLayout.setUnits(QPageLayout::Millimeter); + m_pageLayout.setPageSize(printer.pageLayout().pageSize()); + m_pageLayout.setMargins(printer.pageLayout().margins(QPageLayout::Millimeter)); + m_pageLayout.setOrientation(printer.pageLayout().orientation()); + + updatePageLayoutLabel(); +#endif +} + +void VExportDialog::updatePageLayoutLabel() +{ + m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name()) + .arg(m_pageLayout.orientation() == QPageLayout::Portrait ? + tr("Portrait") : tr("Landscape"))); +} + +void VExportDialog::handleCurrentFormatChanged(int p_index) +{ + bool pdfEnabled = false; + bool htmlEnabled = false; + bool pdfTitleNameEnabled = false; + bool customEnabled = false; + + if (p_index >= 0) { + switch (currentFormat()) { + case ExportFormat::PDF: + pdfEnabled = true; + m_wkhtmltopdfCB->setEnabled(true); + break; + + case ExportFormat::HTML: + htmlEnabled = true; + break; + + case ExportFormat::OnePDF: + pdfEnabled = true; + pdfTitleNameEnabled = true; + m_wkhtmltopdfCB->setChecked(true); + m_wkhtmltopdfCB->setEnabled(false); + break; + + case ExportFormat::Custom: + customEnabled = true; + break; + + default: + break; + } + } + + m_pdfSettings->setVisible(pdfEnabled); + m_htmlSettings->setVisible(htmlEnabled); + m_customSettings->setVisible(customEnabled); + + m_wkTitleEdit->setEnabled(pdfTitleNameEnabled); + m_wkTargetFileNameEdit->setEnabled(pdfTitleNameEnabled); + + QTimer::singleShot(100, [this]() { + resize(size().width(), minimumSizeHint().height()); + }); +} + +void VExportDialog::handleCurrentSrcChanged(int p_index) +{ + bool subfolderEnabled = false; + + if (p_index >= 0) { + switch (currentSource()) { + case ExportSource::CurrentFolder: + subfolderEnabled = true; + break; + + default: + break; + } + } + + m_subfolderCB->setVisible(subfolderEnabled); +} + +int VExportDialog::doExportPDFAllInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg) +{ + if (p_files.isEmpty()) { + return 0; + } + + if (!VUtils::makePath(p_outputFolder)) { + LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder)); + return 0; + } + + // Get output file. + const QString suffix = ".pdf"; + QString name = p_opt.m_pdfOpt.m_wkTargetFileName; + if (name.isEmpty()) { + name = VUtils::getFileNameWithSequence(p_outputFolder, + QFileInfo(p_files.first()).completeBaseName() + suffix); + } else if (!name.endsWith(suffix)) { + name += suffix; + } + + QString outputPath = QDir(p_outputFolder).filePath(name); + + int ret = m_exporter->exportPDFInOne(p_files, p_opt, outputPath, p_errMsg); + if (ret > 0) { + appendLogLine(tr("%1 notes exported to %2.").arg(ret).arg(outputPath)); + } else { + appendLogLine(tr("Fail to export %1 notes in one PDF.").arg(p_files.size())); + } + + return ret; +} + +int VExportDialog::doExportCustomAllInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg) +{ + if (p_files.isEmpty()) { + return 0; + } + + if (!VUtils::makePath(p_outputFolder)) { + LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder)); + return 0; + } + + // Get output file. + QString suffix = "." + p_opt.m_customOpt.m_outputSuffix; + QString name = p_opt.m_customOpt.m_targetFileName; + if (name.isEmpty()) { + name = VUtils::getFileNameWithSequence(p_outputFolder, + QFileInfo(p_files.first()).completeBaseName() + suffix); + } else if (!name.endsWith(suffix)) { + name += suffix; + } + + QString outputPath = QDir(p_outputFolder).filePath(name); + + int ret = m_exporter->exportCustomInOne(p_files, p_opt, outputPath, p_errMsg); + if (ret > 0) { + appendLogLine(tr("%1 notes exported to %2.").arg(ret).arg(outputPath)); + } else { + appendLogLine(tr("Fail to export %1 notes in one.").arg(p_files.size())); + } + + return ret; +} + +QWidget *VExportDialog::setupCustomAdvancedSettings() +{ + // Source format. + m_customSrcFormatCB = VUtils::getComboBox(this); + m_customSrcFormatCB->setToolTip(tr("Choose format of the input")); + + // Output suffix. + m_customSuffixEdit = new VLineEdit(this); + m_customSuffixEdit->setPlaceholderText(tr("Without the preceding dot")); + m_customSuffixEdit->setToolTip(tr("Suffix of the output file without the preceding dot")); + QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), + m_customSuffixEdit); + m_customSuffixEdit->setValidator(validator); + + QLabel *tipsLabel = new QLabel(tr("%0 for the input file; " + "%1 for the output file; " + "%2 for the rendering CSS style file; " + "%3 for the input file directory; " + "%4 for the rendering code block CSS style file."), + this); + tipsLabel->setWordWrap(true); + + // Enable All In One. + m_customAllInOneCB = new QCheckBox(tr("Enable All In One"), this); + m_customAllInOneCB->setToolTip(tr("Pass a list of input files to the custom command")); + connect(m_customAllInOneCB, &QCheckBox::stateChanged, + this, [this](int p_state) { + bool checked = p_state == Qt::Checked; + m_customFolderSepEdit->setEnabled(checked); + m_customTargetFileNameEdit->setEnabled(checked); + }); + + // Enable PDF-like. + m_customPdfLikeCB = new QCheckBox(tr("PDF-Like"), this); + m_customPdfLikeCB->setToolTip(tr("Treat the exported file as PDF, such as wrapping line")); + + // Input directory separator. + m_customFolderSepEdit = new VLineEdit(this); + m_customFolderSepEdit->setPlaceholderText(tr("Separator to concatenate input files directories")); + m_customFolderSepEdit->setToolTip(tr("Separator to concatenate input files directories")); + m_customFolderSepEdit->setEnabled(false); + + // Target file name for all in one. + m_customTargetFileNameEdit = new VLineEdit(this); + m_customTargetFileNameEdit->setPlaceholderText(tr("Empty to use the name of the first source file")); + m_customTargetFileNameEdit->setToolTip(tr("Name of the generated All-In-One file")); + validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), + m_customTargetFileNameEdit); + m_customTargetFileNameEdit->setValidator(validator); + m_customTargetFileNameEdit->setEnabled(false); + + // Cmd edit. + m_customCmdEdit = new QPlainTextEdit(this); + m_customCmdEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + QString cmdExamp("pandoc --resource-path=.:\"%3\" --css=\"%2\" --css=\"%4\" -s -o \"%1\" \"%0\""); + m_customCmdEdit->setPlaceholderText(cmdExamp); + m_customCmdEdit->setToolTip(tr("Custom command to be executed")); + m_customCmdEdit->setProperty("LineEdit", true); + + QGridLayout *advLayout = new QGridLayout(); + advLayout->addWidget(new QLabel(tr("Source format:")), 0, 0); + advLayout->addWidget(m_customSrcFormatCB, 0, 1, 1, 2); + + advLayout->addWidget(new QLabel(tr("Output suffix:")), 0, 3); + advLayout->addWidget(m_customSuffixEdit, 0, 4, 1, 2); + + advLayout->addWidget(m_customAllInOneCB, 1, 1, 1, 2); + advLayout->addWidget(m_customPdfLikeCB, 1, 4, 1, 2); + + advLayout->addWidget(new QLabel(tr("Output file name:")), 2, 0); + advLayout->addWidget(m_customTargetFileNameEdit, 2, 1, 1, 2); + + advLayout->addWidget(new QLabel(tr("Input directories separator:")), 2, 3); + advLayout->addWidget(m_customFolderSepEdit, 2, 4, 1, 2); + + advLayout->addWidget(tipsLabel, 3, 0, 1, 6); + + advLayout->addWidget(m_customCmdEdit, 4, 0, 1, 6); + + advLayout->setContentsMargins(0, 0, 0, 0); + + QWidget *wid = new QWidget(); + wid->setLayout(advLayout); + + m_customCmdEdit->setMaximumHeight(m_customSrcFormatCB->height() * 3); + + return wid; +} + +int VExportDialog::outputAsHTML(const QString &p_outputFolder, + QString *p_errMsg, + QList *p_outputFiles) +{ + int ret = 0; + ExportFormat fmt = s_opt.m_format; + s_opt.m_format = ExportFormat::HTML; + switch (s_opt.m_source) { + case ExportSource::CurrentNote: + ret = doExport(m_file, s_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportSource::CurrentFolder: + ret = doExport(m_directory, s_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportSource::CurrentNotebook: + ret = doExport(m_notebook, s_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + case ExportSource::Cart: + ret = doExport(m_cart, s_opt, p_outputFolder, p_errMsg, p_outputFiles); + break; + + default: + break; + } + + s_opt.m_format = fmt; + + return ret; +} + +QList VExportDialog::collectFiles(QString *p_errMsg) +{ + Q_UNUSED(p_errMsg); + + QList files; + switch (s_opt.m_source) { + case ExportSource::CurrentNote: + files.append(m_file->fetchPath()); + break; + + case ExportSource::CurrentFolder: + files = m_directory->collectFiles(); + break; + + case ExportSource::CurrentNotebook: + files = m_notebook->collectFiles(); + break; + + case ExportSource::Cart: + files = m_cart->getFiles().toList(); + break; + + default: + break; + } + + return files; +} diff --git a/src/dialog/vexportdialog.h b/src/dialog/vexportdialog.h new file mode 100644 index 00000000..48a0afec --- /dev/null +++ b/src/dialog/vexportdialog.h @@ -0,0 +1,516 @@ +#ifndef VEXPORTDIALOG_H +#define VEXPORTDIALOG_H + +#include +#include +#include +#include + +#include "vconstants.h" + +class QLabel; +class VLineEdit; +class QDialogButtonBox; +class QPushButton; +class QGroupBox; +class QPlainTextEdit; +class VNotebook; +class VDirectory; +class VFile; +class VCart; +class VExporter; +class QCheckBox; +class VLineEdit; +class QProgressBar; + + +enum class ExportSource +{ + CurrentNote = 0, + CurrentFolder, + CurrentNotebook, + Cart +}; + + +enum class ExportFormat +{ + Markdown = 0, + HTML, + PDF, + OnePDF, + Custom +}; + + +enum class ExportPageNumber +{ + None = 0, + Left, + Center, + Right +}; + + +struct ExportHTMLOption +{ + ExportHTMLOption() + : m_embedCssStyle(true), + m_completeHTML(true), + m_embedImages(true), + m_mimeHTML(false), + m_outlinePanel(true) + { + } + + ExportHTMLOption(bool p_embedCssStyle, + bool p_completeHTML, + bool p_embedImages, + bool p_mimeHTML, + bool p_outlinePanel) + : m_embedCssStyle(p_embedCssStyle), + m_completeHTML(p_completeHTML), + m_embedImages(p_embedImages), + m_mimeHTML(p_mimeHTML), + m_outlinePanel(p_outlinePanel) + { + } + + bool m_embedCssStyle; + bool m_completeHTML; + bool m_embedImages; + bool m_mimeHTML; + bool m_outlinePanel; +}; + + +struct ExportPDFOption +{ + ExportPDFOption() + : m_layout(NULL), + m_wkhtmltopdf(false), + m_wkEnableBackground(true), + m_enableTableOfContents(false), + m_wkPageNumber(ExportPageNumber::None) + { + } + + ExportPDFOption(QPageLayout *p_layout, + bool p_wkhtmltopdf, + const QString &p_wkPath, + bool p_wkEnableBackground, + bool p_enableTableOfContents, + const QString &p_wkTitle, + const QString &p_wkTargetFileName, + ExportPageNumber p_wkPageNumber, + const QString &p_wkExtraArgs) + : m_layout(p_layout), + m_wkhtmltopdf(p_wkhtmltopdf), + m_wkPath(p_wkPath), + m_wkEnableBackground(p_wkEnableBackground), + m_enableTableOfContents(p_enableTableOfContents), + m_wkTitle(p_wkTitle), + m_wkTargetFileName(p_wkTargetFileName), + m_wkPageNumber(p_wkPageNumber), + m_wkExtraArgs(p_wkExtraArgs) + { + } + + QPageLayout *m_layout; + bool m_wkhtmltopdf; + QString m_wkPath; + bool m_wkEnableBackground; + bool m_enableTableOfContents;; + QString m_wkTitle; + QString m_wkTargetFileName; + ExportPageNumber m_wkPageNumber; + QString m_wkExtraArgs; +}; + + +struct ExportCustomOption +{ +#if defined(Q_OS_WIN) + #define DEFAULT_SEP ";" +#else + #define DEFAULT_SEP ":" +#endif + + enum SourceFormat + { + Markdown = 0, + HTML + }; + + ExportCustomOption() + : m_srcFormat(SourceFormat::Markdown), + m_allInOne(false), + m_pdfLike(false), + m_folderSep(DEFAULT_SEP) + { + } + + ExportCustomOption(const QStringList &p_config) + : m_srcFormat(SourceFormat::Markdown), + m_allInOne(false), + m_pdfLike(false), + m_folderSep(DEFAULT_SEP) + { + if (p_config.size() < 3) { + return; + } + + if (p_config.at(0).trimmed() != "0") { + m_srcFormat = SourceFormat::HTML; + } + + m_outputSuffix = p_config.at(1).trimmed(); + + m_cmd = p_config.at(2).trimmed(); + } + + ExportCustomOption(ExportCustomOption::SourceFormat p_srcFormat, + const QString &p_outputSuffix, + const QString &p_cmd, + const QString &p_cssUrl, + const QString &p_codeBlockCssUrl, + bool p_allInOne, + bool p_pdfLike, + const QString &p_folderSep, + const QString &p_targetFileName) + : m_srcFormat(p_srcFormat), + m_outputSuffix(p_outputSuffix), + m_cssUrl(p_cssUrl), + m_codeBlockCssUrl(p_codeBlockCssUrl), + m_allInOne(p_allInOne), + m_pdfLike(p_pdfLike), + m_folderSep(p_folderSep), + m_targetFileName(p_targetFileName) + { + QStringList cmds = p_cmd.split('\n'); + if (!cmds.isEmpty()) { + m_cmd = cmds.first(); + } + } + + QStringList toConfig() const + { + QStringList config; + config << QString::number((int)m_srcFormat); + config << m_outputSuffix; + config << m_cmd; + + return config; + } + + SourceFormat m_srcFormat; + QString m_outputSuffix; + QString m_cmd; + + QString m_cssUrl; + QString m_codeBlockCssUrl; + + bool m_allInOne; + bool m_pdfLike; + + QString m_folderSep; + QString m_targetFileName; +}; + + +struct ExportOption +{ + ExportOption() + : m_source(ExportSource::CurrentNote), + m_format(ExportFormat::Markdown), + m_renderer(MarkdownConverterType::MarkdownIt), + m_processSubfolders(true) + { + } + + ExportOption(ExportSource p_source, + ExportFormat p_format, + MarkdownConverterType p_renderer, + const QString &p_renderBg, + const QString &p_renderStyle, + const QString &p_renderCodeBlockStyle, + bool p_processSubfolders, + const ExportPDFOption &p_pdfOpt, + const ExportHTMLOption &p_htmlOpt, + const ExportCustomOption &p_customOpt) + : m_source(p_source), + m_format(p_format), + m_renderer(p_renderer), + m_renderBg(p_renderBg), + m_renderStyle(p_renderStyle), + m_renderCodeBlockStyle(p_renderCodeBlockStyle), + m_processSubfolders(p_processSubfolders), + m_pdfOpt(p_pdfOpt), + m_htmlOpt(p_htmlOpt), + m_customOpt(p_customOpt) + { + } + + ExportSource m_source; + ExportFormat m_format; + MarkdownConverterType m_renderer; + + // Background name. + QString m_renderBg; + + QString m_renderStyle; + QString m_renderCodeBlockStyle; + + // Whether process subfolders recursively when source is CurrentFolder. + bool m_processSubfolders; + + ExportPDFOption m_pdfOpt; + + ExportHTMLOption m_htmlOpt; + + ExportCustomOption m_customOpt; +}; + + +class VExportDialog : public QDialog +{ + Q_OBJECT +public: + VExportDialog(VNotebook *p_notebook, + VDirectory *p_directory, + VFile *p_file, + VCart *p_cart, + MarkdownConverterType p_renderer, + QWidget *p_parent = nullptr); + +private slots: + void startExport(); + + void handleOutputBrowseBtnClicked(); + + void handleWkPathBrowseBtnClicked(); + + void handleInputChanged(); + + void handleLayoutBtnClicked(); + + void handleCurrentFormatChanged(int p_index); + + void handleCurrentSrcChanged(int p_index); + +private: + void setupUI(); + + QWidget *setupPDFAdvancedSettings(); + + QWidget *setupHTMLAdvancedSettings(); + + QWidget *setupGeneralAdvancedSettings(); + + QWidget *setupCustomAdvancedSettings(); + + void initUIFields(MarkdownConverterType p_renderer); + + QString getOutputDirectory() const; + + void appendLogLine(const QString &p_text); + + // Return number of files exported. + int doExport(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExport(VDirectory *p_directory, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExport(VNotebook *p_notebook, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExport(VCart *p_cart, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExportMarkdown(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExportPDF(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExportHTML(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + int doExportPDFAllInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL); + + int doExportCustomAllInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL); + + int doExportCustom(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + // Return false if we could not continue. + bool checkUserAction(); + + void updatePageLayoutLabel(); + + bool checkWkhtmltopdfExecutable(const QString &p_file); + + ExportSource currentSource() const; + + ExportFormat currentFormat() const; + + int outputAsHTML(const QString &p_outputFolder, + QString *p_errMsg = NULL, + QList *p_outputFiles = NULL); + + // Collect files to be handled. + QList collectFiles(QString *p_errMsg = NULL); + + QComboBox *m_srcCB; + + QComboBox *m_formatCB; + + QComboBox *m_rendererCB; + + QComboBox *m_renderBgCB; + + QComboBox *m_renderStyleCB; + + QComboBox *m_renderCodeBlockStyleCB; + + VLineEdit *m_outputEdit; + + QGroupBox *m_basicBox; + + QGroupBox *m_settingBox; + + QWidget *m_pdfSettings; + + QWidget *m_htmlSettings; + + QWidget *m_generalSettings; + + QWidget *m_customSettings; + + QPlainTextEdit *m_consoleEdit; + + QDialogButtonBox *m_btnBox; + + QPushButton *m_openBtn; + + QPushButton *m_exportBtn; + + QPushButton *m_copyBtn; + + QLabel *m_layoutLabel; + + QCheckBox *m_wkhtmltopdfCB; + + VLineEdit *m_wkPathEdit; + + QPushButton *m_wkPathBrowseBtn; + + VLineEdit *m_wkTitleEdit; + + VLineEdit *m_wkTargetFileNameEdit; + + QCheckBox *m_wkBackgroundCB; + + QCheckBox *m_tableOfContentsCB; + + QComboBox *m_wkPageNumberCB; + + VLineEdit *m_wkExtraArgsEdit; + + QCheckBox *m_embedStyleCB; + + QCheckBox *m_completeHTMLCB; + + QCheckBox *m_embedImagesCB; + + QCheckBox *m_mimeHTMLCB; + + QCheckBox *m_outlinePanelCB; + + QCheckBox *m_subfolderCB; + + QComboBox *m_customSrcFormatCB; + + VLineEdit *m_customSuffixEdit; + + QCheckBox *m_customAllInOneCB; + + QCheckBox *m_customPdfLikeCB; + + QPlainTextEdit *m_customCmdEdit; + + VLineEdit *m_customFolderSepEdit; + + VLineEdit *m_customTargetFileNameEdit; + + VNotebook *m_notebook; + + VDirectory *m_directory; + + VFile *m_file; + + VCart *m_cart; + + QProgressBar *m_proBar; + + QPageLayout m_pageLayout; + + // Whether we are exporting files. + bool m_inExport; + + // Asked to stop exporting by user. + bool m_askedToStop; + + // Exporter used to export PDF and HTML. + VExporter *m_exporter; + + // Last exproted file path. + QString m_exportedFile; + + // Last output folder path. + static QString s_lastOutputFolder; + + static ExportOption s_opt; +}; + +inline ExportSource VExportDialog::currentSource() const +{ + return (ExportSource)m_srcCB->currentData().toInt(); +} + +inline ExportFormat VExportDialog::currentFormat() const +{ + return (ExportFormat)m_formatCB->currentData().toInt(); +} +#endif // VEXPORTDIALOG_H diff --git a/src/src.pro b/src/src.pro index ba2387b8..6beec4c8 100644 --- a/src/src.pro +++ b/src/src.pro @@ -161,7 +161,9 @@ SOURCES += main.cpp\ vwebview.cpp \ websocketclientwrapper.cpp \ websockettransport.cpp \ - vmathjaxwebdocument.cpp + vmathjaxwebdocument.cpp \ + vexporter.cpp \ + dialog/vexportdialog.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -307,7 +309,9 @@ HEADERS += vmainwindow.h \ vwebview.h \ websocketclientwrapper.h \ websockettransport.h \ - vmathjaxwebdocument.h + vmathjaxwebdocument.h \ + vexporter.h \ + dialog/vexportdialog.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 9c11434a..47963669 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -664,6 +664,7 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, const QString &p_renderBg, const QString &p_renderStyle, const QString &p_renderCodeBlockStyle, + quint16 p_port, bool p_isPDF, bool p_wkhtmltopdf, bool p_addToc) @@ -675,7 +676,7 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, g_config->getCodeBlockCssStyleUrl(p_renderCodeBlockStyle), p_isPDF); - return generateHtmlTemplate(templ, p_conType, 0, p_isPDF, p_wkhtmltopdf, p_addToc); + return generateHtmlTemplate(templ, p_conType, p_port, p_isPDF, p_wkhtmltopdf, p_addToc); } QString VUtils::generateHtmlTemplate(const QString &p_template, diff --git a/src/utils/vutils.h b/src/utils/vutils.h index fc163d05..eeb38853 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -194,6 +194,7 @@ public: const QString &p_renderBg, const QString &p_renderStyle, const QString &p_renderCodeBlockStyle, + quint16 p_port, bool p_isPDF, bool p_wkhtmltopdf = false, bool p_addToc = false); diff --git a/src/vconstants.h b/src/vconstants.h index 803ba9ac..82983d81 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -18,6 +18,10 @@ enum class FileType { Note, Orphan }; enum class ClipboardOpType { CopyFile, CopyDir, Invalid }; +enum WebSocketPort { PreviewHelperPort = 10900, + ExportWebViewPort, + LastSpecialPort }; + namespace ClipboardConfig { static const QString c_type = "type"; diff --git a/src/vexporter.cpp b/src/vexporter.cpp new file mode 100644 index 00000000..dc38c5d1 --- /dev/null +++ b/src/vexporter.cpp @@ -0,0 +1,1063 @@ +#include "vexporter.h" + +#include +#include +#include +// #include +#include +#include +#include +#include +#include + +#include "vconfigmanager.h" +#include "vfile.h" +#include "vwebview.h" +#include "utils/vutils.h" +#include "vpreviewpage.h" +#include "vconstants.h" +#include "vmarkdownconverter.h" +#include "vdocument.h" +#include "utils/vwebutils.h" + +extern VConfigManager *g_config; + +extern VWebUtils *g_webUtils; + +VExporter::VExporter(QWidget *p_parent) + : QObject(p_parent), + m_webViewer(NULL), + m_state(ExportState::Idle), + m_askedToStop(false) +{ +} + +static QString marginToStrMM(qreal p_margin) +{ + return QString("%1mm").arg(p_margin); +} + +void VExporter::prepareExport(const ExportOption &p_opt) +{ + bool isPdf = p_opt.m_format == ExportFormat::PDF + || p_opt.m_format == ExportFormat::OnePDF + || (p_opt.m_format == ExportFormat::Custom + && p_opt.m_customOpt.m_pdfLike); + bool extraToc = isPdf + && !p_opt.m_pdfOpt.m_wkhtmltopdf + && p_opt.m_pdfOpt.m_enableTableOfContents; + + m_htmlTemplate = VUtils::generateHtmlTemplate(p_opt.m_renderer, + p_opt.m_renderBg, + p_opt.m_renderStyle, + p_opt.m_renderCodeBlockStyle, + WebSocketPort::ExportWebViewPort, + isPdf, + isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf, + extraToc); + + bool outline = p_opt.m_htmlOpt.m_outlinePanel + && !isPdf + && (p_opt.m_format == ExportFormat::HTML + || p_opt.m_format == ExportFormat::Custom); + m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg, + isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf, + outline); + + m_pageLayout = *(p_opt.m_pdfOpt.m_layout); + + prepareWKArguments(p_opt.m_pdfOpt); +} + +void VExporter::prepareWKArguments(const ExportPDFOption &p_opt) +{ + m_wkArgs.clear(); + m_wkArgs << "--page-size" << m_pageLayout.pageSize().key(); + m_wkArgs << "--orientation" + << (m_pageLayout.orientation() == QPageLayout::Portrait ? "Portrait" : "Landscape"); + + QMarginsF marginsMM = m_pageLayout.margins(QPageLayout::Millimeter); + m_wkArgs << "--margin-bottom" << marginToStrMM(marginsMM.bottom()); + m_wkArgs << "--margin-left" << marginToStrMM(marginsMM.left()); + m_wkArgs << "--margin-right" << marginToStrMM(marginsMM.right()); + m_wkArgs << "--margin-top" << marginToStrMM(marginsMM.top()); + + QString footer; + switch (p_opt.m_wkPageNumber) { + case ExportPageNumber::Left: + footer = "--footer-left"; + break; + + case ExportPageNumber::Center: + footer = "--footer-center"; + break; + + case ExportPageNumber::Right: + footer = "--footer-right"; + break; + + default: + break; + } + + if (!footer.isEmpty()) { + m_wkArgs << footer << "[page]" + << "--footer-spacing" << QString::number(marginsMM.bottom() / 3, 'f', 2); + } + + // Title. + if (!p_opt.m_wkTitle.isEmpty()) { + m_wkArgs << "--title" << p_opt.m_wkTitle; + } + + m_wkArgs << "--encoding" << "utf-8"; + m_wkArgs << (p_opt.m_wkEnableBackground ? "--background" : "--no-background"); + + // Delay for MathJax. + if (p_opt.m_wkhtmltopdf) { + m_wkArgs << "--javascript-delay" << "10000"; + } + + // Append additional global option. + if (!p_opt.m_wkExtraArgs.isEmpty()) { + m_wkArgs.append(VUtils::parseCombinedArgString(p_opt.m_wkExtraArgs)); + } + + // TOC option. + if (p_opt.m_enableTableOfContents) { + m_wkArgs << "toc" << "--toc-text-size-shrink" << "1.0"; + } +} + +bool VExporter::exportPDF(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg); +} + +bool VExporter::exportHTML(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg); +} + +static void replaceArgument(QString &p_cmd, const QString &p_arg, const QString &p_val) +{ + if (p_val.startsWith("\"")) { + // Check if the arg has been already surrounded by ". + int pos = 0; + while (pos < p_cmd.size()) { + int idx = p_cmd.indexOf(p_arg, pos); + if (idx == -1) { + break; + } + + int len = p_arg.size(); + int nidx = idx; + if (idx > 0 && p_cmd[idx - 1] == '"') { + --nidx; + len += 1; + } + + if (idx + p_arg.size() < p_cmd.size() + && p_cmd[idx + p_arg.size()] == '"') { + len += 1; + } + + p_cmd.replace(nidx, len, p_val); + pos = nidx + p_val.size() - len; + } + } else { + p_cmd.replace(p_arg, p_val); + } +} + +static QString evaluateCommand(const ExportCustomOption &p_opt, + const QString &p_input, + const QString &p_inputFolder, + const QString &p_output) +{ + QString cmd(p_opt.m_cmd); + replaceArgument(cmd, "%0", p_input); + replaceArgument(cmd, "%1", p_output); + replaceArgument(cmd, "%2", QDir::toNativeSeparators(p_opt.m_cssUrl)); + replaceArgument(cmd, "%3", p_inputFolder); + replaceArgument(cmd, "%4", QDir::toNativeSeparators(p_opt.m_codeBlockCssUrl)); + + return cmd; +} + +bool VExporter::exportCustom(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + const ExportCustomOption &customOpt = p_opt.m_customOpt; + if (customOpt.m_srcFormat == ExportCustomOption::Markdown) { + // Use Markdown file as input. + QList files; + files.append(QDir::toNativeSeparators(p_file->fetchPath())); + return convertFilesViaCustom(files, p_outputFile, customOpt, p_errMsg); + } else { + return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg); + } +} + +void VExporter::initWebViewer(VFile *p_file, const ExportOption &p_opt) +{ + Q_ASSERT(!m_webViewer); + + m_webViewer = new VWebView(p_file, static_cast(parent())); + m_webViewer->hide(); + + VPreviewPage *page = new VPreviewPage(m_webViewer); + m_webViewer->setPage(page); + connect(page, &VPreviewPage::loadFinished, + this, &VExporter::handleLoadFinished); + /* + connect(page->profile(), &QWebEngineProfile::downloadRequested, + this, &VExporter::handleDownloadRequested); + */ + + m_webDocument = new VDocument(p_file, m_webViewer); + connect(m_webDocument, &VDocument::logicsFinished, + this, &VExporter::handleLogicsFinished); + + quint16 port = WebSocketPort::ExportWebViewPort; + m_webViewer->bindToChannel(port, QStringLiteral("content"), m_webDocument); + + // Need to generate HTML using Hoedown. + if (p_opt.m_renderer == MarkdownConverterType::Hoedown) { + VMarkdownConverter mdConverter; + QString toc; + QString html = mdConverter.generateHtml(p_file->getContent(), + g_config->getMarkdownExtensions(), + toc); + bool isPdf = p_opt.m_format == ExportFormat::PDF + || p_opt.m_format == ExportFormat::OnePDF; + bool extraToc = isPdf + && !p_opt.m_pdfOpt.m_wkhtmltopdf + && p_opt.m_pdfOpt.m_enableTableOfContents; + if (extraToc && !toc.isEmpty()) { + // Add toc to html. + QString div = "
" + toc + "
\n"; + html = div + html; + } + + m_webDocument->setHtml(html); + } + + m_baseUrl = p_file->getBaseUrl(); + m_webViewer->setHtml(m_htmlTemplate, m_baseUrl); +} + +void VExporter::handleLogicsFinished() +{ + Q_ASSERT(!(m_noteState & NoteState::WebLogicsReady)); + m_noteState = NoteState(m_noteState | NoteState::WebLogicsReady); +} + +void VExporter::handleLoadFinished(bool p_ok) +{ + Q_ASSERT(!(m_noteState & NoteState::WebLoadFinished)); + m_noteState = NoteState(m_noteState | NoteState::WebLoadFinished); + + if (!p_ok) { + m_noteState = NoteState(m_noteState | NoteState::Failed); + } +} + +void VExporter::clearWebViewer() +{ + // m_webDocument will be freeed by QObject. + delete m_webViewer; + m_webViewer = NULL; + m_webDocument = NULL; + m_baseUrl.clear(); +} + +bool VExporter::exportToPDF(VWebView *p_webViewer, + const QString &p_filePath, + const QPageLayout &p_layout) +{ +/* + int pdfPrinted = 0; + p_webViewer->page()->printToPdf([&, this](const QByteArray &p_result) { + if (p_result.isEmpty() || this->m_state == ExportState::Cancelled) { + pdfPrinted = -1; + return; + } + + V_ASSERT(!p_filePath.isEmpty()); + + if (!VUtils::writeFileToDisk(p_filePath, p_result)) { + pdfPrinted = -1; + return; + } + + pdfPrinted = 1; + }, p_layout); + + while (pdfPrinted == 0) { + VUtils::sleepWait(100); + + if (m_state == ExportState::Cancelled) { + break; + } + } + + return pdfPrinted == 1; +*/ + return false; +} + +bool VExporter::exportToPDFViaWK(VDocument *p_webDocument, + const ExportPDFOption &p_opt, + const QString &p_filePath, + QString *p_errMsg) +{ + int pdfExported = 0; + + connect(p_webDocument, &VDocument::htmlContentFinished, + this, [&, this](const QString &p_headContent, + const QString &p_styleContent, + const QString &p_bodyContent) { + if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) { + pdfExported = -1; + return; + } + + Q_ASSERT(!p_filePath.isEmpty()); + + // Save HTML to a temp dir. + QTemporaryDir tmpDir; + if (!tmpDir.isValid()) { + pdfExported = -1; + return; + } + + QDir dir(tmpDir.path()); + QString htmlPath = dir.filePath("vnote_tmp.html"); + QString title = p_webDocument->getFile()->getName(); + title = QFileInfo(title).completeBaseName(); + if (!outputToHTMLFile(htmlPath, + title, + p_headContent, + p_styleContent, + p_bodyContent, + true, + true, + false)) { + pdfExported = -1; + return; + } + + // Convert via wkhtmltopdf. + QList files; + files.append(htmlPath); + if (!htmlsToPDFViaWK(files, p_filePath, p_opt, p_errMsg)) { + pdfExported = -1; + } else { + pdfExported = 1; + } + }); + + p_webDocument->getHtmlContentAsync(); + + while (pdfExported == 0) { + VUtils::sleepWait(100); + + if (m_state == ExportState::Cancelled) { + break; + } + } + + return pdfExported == 1; +} + +bool VExporter::exportToCustom(VDocument *p_webDocument, + const ExportCustomOption &p_opt, + const QString &p_filePath, + QString *p_errMsg) +{ + int exported = 0; + + connect(p_webDocument, &VDocument::htmlContentFinished, + this, [&, this](const QString &p_headContent, + const QString &p_styleContent, + const QString &p_bodyContent) { + if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) { + exported = -1; + return; + } + + Q_ASSERT(!p_filePath.isEmpty()); + + // Save HTML to a temp dir. + QTemporaryDir tmpDir; + if (!tmpDir.isValid()) { + exported = -1; + return; + } + + QDir dir(tmpDir.path()); + QString htmlPath = dir.filePath("vnote_tmp.html"); + QString title = p_webDocument->getFile()->getName(); + title = QFileInfo(title).completeBaseName(); + if (!outputToHTMLFile(htmlPath, + title, + p_headContent, + p_styleContent, + p_bodyContent, + true, + true, + false)) { + exported = -1; + return; + } + + // Convert via custom command. + QList files; + files.append(htmlPath); + if (!convertFilesViaCustom(files, p_filePath, p_opt, p_errMsg)) { + exported = -1; + } else { + exported = 1; + } + }); + + p_webDocument->getHtmlContentAsync(); + + while (exported == 0) { + VUtils::sleepWait(100); + + if (m_state == ExportState::Cancelled) { + break; + } + } + + return exported == 1; +} + +bool VExporter::exportViaWebView(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + Q_UNUSED(p_errMsg); + + bool ret = false; + + bool isOpened = p_file->isOpened(); + if (!isOpened && !p_file->open()) { + goto exit; + } + + Q_ASSERT(m_state == ExportState::Idle); + m_state = ExportState::Busy; + + clearNoteState(); + + initWebViewer(p_file, p_opt); + + while (!isNoteStateReady()) { + VUtils::sleepWait(100); + + if (m_state == ExportState::Cancelled) { + goto exit; + } + + if (isNoteStateFailed()) { + m_state = ExportState::Failed; + goto exit; + } + + if (m_askedToStop) { + m_state = ExportState::Cancelled; + goto exit; + } + } + + // Wait to ensure Web side is really ready. + VUtils::sleepWait(200); + + if (m_state == ExportState::Cancelled) { + goto exit; + } + + { + + bool exportRet = false; + switch (p_opt.m_format) { + case ExportFormat::PDF: + V_FALLTHROUGH; + case ExportFormat::OnePDF: + if (p_opt.m_pdfOpt.m_wkhtmltopdf) { + exportRet = exportToPDFViaWK(m_webDocument, + p_opt.m_pdfOpt, + p_outputFile, + p_errMsg); + } else { + exportRet = exportToPDF(m_webViewer, + p_outputFile, + m_pageLayout); + } + + break; + + case ExportFormat::HTML: + if (p_opt.m_htmlOpt.m_mimeHTML) { + exportRet = exportToMHTML(m_webViewer, + p_opt.m_htmlOpt, + p_outputFile); + } else { + exportRet = exportToHTML(m_webDocument, + p_opt.m_htmlOpt, + p_outputFile); + } + + break; + + case ExportFormat::Custom: + exportRet = exportToCustom(m_webDocument, + p_opt.m_customOpt, + p_outputFile, + p_errMsg); + break; + + default: + break; + } + + clearNoteState(); + + if (!isOpened) { + p_file->close(); + } + + if (exportRet) { + m_state = ExportState::Successful; + } else { + m_state = ExportState::Failed; + } + + } + +exit: + clearWebViewer(); + + if (m_state == ExportState::Successful) { + ret = true; + } + + m_state = ExportState::Idle; + + return ret; +} + +bool VExporter::exportToHTML(VDocument *p_webDocument, + const ExportHTMLOption &p_opt, + const QString &p_filePath) +{ + int htmlExported = 0; + + connect(p_webDocument, &VDocument::htmlContentFinished, + this, [&, this](const QString &p_headContent, + const QString &p_styleContent, + const QString &p_bodyContent) { + if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) { + htmlExported = -1; + return; + } + + Q_ASSERT(!p_filePath.isEmpty()); + + QString title = p_webDocument->getFile()->getName(); + title = QFileInfo(title).completeBaseName(); + if (!outputToHTMLFile(p_filePath, + title, + p_headContent, + p_styleContent, + p_bodyContent, + p_opt.m_embedCssStyle, + p_opt.m_completeHTML, + p_opt.m_embedImages)) { + htmlExported = -1; + return; + } + + htmlExported = 1; + }); + + p_webDocument->getHtmlContentAsync(); + + while (htmlExported == 0) { + VUtils::sleepWait(100); + + if (m_state == ExportState::Cancelled) { + break; + } + } + + return htmlExported == 1; +} + +bool VExporter::fixStyleResources(const QString &p_folder, + QString &p_html) +{ + bool altered = false; + QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);"); + + int pos = 0; + while (pos < p_html.size()) { + int idx = p_html.indexOf(reg, pos); + if (idx == -1) { + break; + } + + QString targetFile = g_webUtils->copyResource(QUrl(reg.cap(1)), p_folder); + if (targetFile.isEmpty()) { + pos = idx + reg.matchedLength(); + } else { + // Replace the url string in html. + QString newUrl = QString("url(\"%1\");").arg(getResourceRelativePath(targetFile)); + p_html.replace(idx, reg.matchedLength(), newUrl); + pos = idx + newUrl.size(); + altered = true; + } + } + + return altered; +} + +bool VExporter::embedStyleResources(QString &p_html) +{ + bool altered = false; + QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);"); + + int pos = 0; + while (pos < p_html.size()) { + int idx = p_html.indexOf(reg, pos); + if (idx == -1) { + break; + } + + QString dataURI = g_webUtils->dataURI(QUrl(reg.cap(1)), false); + if (dataURI.isEmpty()) { + pos = idx + reg.matchedLength(); + } else { + // Replace the url string in html. + QString newUrl = QString("url('%1');").arg(dataURI); + p_html.replace(idx, reg.matchedLength(), newUrl); + pos = idx + newUrl.size(); + altered = true; + } + } + + return altered; +} + +bool VExporter::fixBodyResources(const QUrl &p_baseUrl, + const QString &p_folder, + QString &p_html) +{ + bool altered = false; + if (p_baseUrl.isEmpty()) { + return altered; + } + + QRegExp reg("]*)src=\"([^\"]+)\"([^>]*)>"); + + int pos = 0; + while (pos < p_html.size()) { + int idx = p_html.indexOf(reg, pos); + if (idx == -1) { + break; + } + + if (reg.cap(2).isEmpty()) { + pos = idx + reg.matchedLength(); + continue; + } + + QUrl srcUrl(p_baseUrl.resolved(reg.cap(2))); + QString targetFile = g_webUtils->copyResource(srcUrl, p_folder); + if (targetFile.isEmpty()) { + pos = idx + reg.matchedLength(); + } else { + // Replace the url string in html. + QString newUrl = QString("").arg(reg.cap(1)) + .arg(getResourceRelativePath(targetFile)) + .arg(reg.cap(3)); + p_html.replace(idx, reg.matchedLength(), newUrl); + pos = idx + newUrl.size(); + altered = true; + } + } + + return altered; +} + +bool VExporter::embedBodyResources(const QUrl &p_baseUrl, QString &p_html) +{ + bool altered = false; + if (p_baseUrl.isEmpty()) { + return altered; + } + + QRegExp reg("]*)src=\"([^\"]+)\"([^>]*)>"); + + int pos = 0; + while (pos < p_html.size()) { + int idx = p_html.indexOf(reg, pos); + if (idx == -1) { + break; + } + + if (reg.cap(2).isEmpty()) { + pos = idx + reg.matchedLength(); + continue; + } + + QUrl srcUrl(p_baseUrl.resolved(reg.cap(2))); + QString dataURI = g_webUtils->dataURI(srcUrl); + if (dataURI.isEmpty()) { + pos = idx + reg.matchedLength(); + } else { + // Replace the url string in html. + QString newUrl = QString("").arg(reg.cap(1)) + .arg(dataURI) + .arg(reg.cap(3)); + p_html.replace(idx, reg.matchedLength(), newUrl); + pos = idx + newUrl.size(); + altered = true; + } + } + + return altered; +} + +QString VExporter::getResourceRelativePath(const QString &p_file) +{ + int idx = p_file.lastIndexOf('/'); + int idx2 = p_file.lastIndexOf('/', idx - 1); + Q_ASSERT(idx > 0 && idx2 < idx); + return "." + p_file.mid(idx2); +} + +bool VExporter::exportToMHTML(VWebView *p_webViewer, + const ExportHTMLOption &p_opt, + const QString &p_filePath) +{ +/* + Q_UNUSED(p_opt); + + m_downloadState = QWebEngineDownloadItem::DownloadRequested; + + p_webViewer->page()->save(p_filePath, QWebEngineDownloadItem::MimeHtmlSaveFormat); + + while (m_downloadState == QWebEngineDownloadItem::DownloadRequested + || m_downloadState == QWebEngineDownloadItem::DownloadInProgress) { + VUtils::sleepWait(100); + } + + return m_downloadState == QWebEngineDownloadItem::DownloadCompleted; +*/ + return false; +} + +/* +void VExporter::handleDownloadRequested(QWebEngineDownloadItem *p_item) +{ + if (p_item->savePageFormat() == QWebEngineDownloadItem::MimeHtmlSaveFormat) { + connect(p_item, &QWebEngineDownloadItem::stateChanged, + this, [this](QWebEngineDownloadItem::DownloadState p_state) { + m_downloadState = p_state; + }); + } +} +*/ + +static QString combineArgs(QStringList &p_args) +{ + QString str; + for (const QString &arg : p_args) { + QString tmp; + if (arg.contains(' ')) { + tmp = '"' + arg + '"'; + } else { + tmp = arg; + } + + if (str.isEmpty()) { + str = tmp; + } else { + str = str + ' ' + tmp; + } + } + + return str; +} + +bool VExporter::htmlsToPDFViaWK(const QList &p_htmlFiles, + const QString &p_filePath, + const ExportPDFOption &p_opt, + QString *p_errMsg) +{ + // Note: system's locale settings (Language for non-Unicode programs) is important to wkhtmltopdf. + // Input file could be encoded via QUrl::fromLocalFile(p_htmlFile).toString(QUrl::EncodeUnicode) to + // handle non-ASCII path. + + QStringList args(m_wkArgs); + + for (auto const & it : p_htmlFiles) { + args << QDir::toNativeSeparators(it); + } + + args << QDir::toNativeSeparators(p_filePath); + + QString cmd = p_opt.m_wkPath + " " + combineArgs(args); + emit outputLog(cmd); + qDebug() << "wkhtmltopdf cmd:" << cmd; + int ret = startProcess(p_opt.m_wkPath, args); + qDebug() << "wkhtmltopdf returned" << ret; + if (m_askedToStop) { + return ret == 0; + } + + switch (ret) { + case -2: + VUtils::addErrMsg(p_errMsg, tr("Fail to start wkhtmltopdf (%1).").arg(cmd)); + break; + + case -1: + VUtils::addErrMsg(p_errMsg, tr("wkhtmltopdf crashed (%1).").arg(cmd)); + break; + + default: + break; + } + + return ret == 0; +} + +bool VExporter::convertFilesViaCustom(const QList &p_files, + const QString &p_filePath, + const ExportCustomOption &p_opt, + QString *p_errMsg) +{ + QString input; + QString inputFolder; + for (auto const & it : p_files) { + if (!input.isEmpty()) { + input += " "; + } + + if (!inputFolder.isEmpty()) { + inputFolder += p_opt.m_folderSep; + } + + QString tmp = QDir::toNativeSeparators(it); + input += ("\"" + tmp + "\""); + inputFolder += ("\"" + VUtils::basePathFromPath(tmp) + "\""); + } + + QString output = QDir::toNativeSeparators(p_filePath); + QString cmd = evaluateCommand(p_opt, + input, + inputFolder, + output); + emit outputLog(cmd); + qDebug() << "custom cmd:" << cmd; + int ret = startProcess(cmd); + qDebug() << "custom cmd returned" << ret; + if (m_askedToStop) { + return ret == 0; + } + + switch (ret) { + case -2: + VUtils::addErrMsg(p_errMsg, tr("Fail to start custom command (%1).").arg(cmd)); + break; + + case -1: + VUtils::addErrMsg(p_errMsg, tr("Custom command crashed (%1).").arg(cmd)); + break; + + default: + break; + } + + return ret == 0; +} + +int VExporter::exportPDFInOne(const QList &p_htmlFiles, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + if (!htmlsToPDFViaWK(p_htmlFiles, p_outputFile, p_opt.m_pdfOpt, p_errMsg)) { + return 0; + } + + return p_htmlFiles.size(); +} + +int VExporter::startProcess(const QString &p_program, const QStringList &p_args) +{ + int ret = 0; + QScopedPointer process(new QProcess(this)); + process->start(p_program, p_args); + bool finished = false; + bool started = false; + while (true) { + QProcess::ProcessError err = process->error(); + if (err == QProcess::FailedToStart + || err == QProcess::Crashed) { + emit outputLog(tr("QProcess error %1.").arg(err)); + 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; + } + + QByteArray outBa = process->readAllStandardOutput(); + QByteArray errBa = process->readAllStandardError(); + QString msg; + if (!outBa.isEmpty()) { + msg += QString::fromLocal8Bit(outBa); + } + + if (!errBa.isEmpty()) { + msg += QString::fromLocal8Bit(errBa); + } + + if (!msg.isEmpty()) { + emit outputLog(msg); + } + + if (finished) { + QProcess::ExitStatus sta = process->exitStatus(); + if (sta == QProcess::CrashExit) { + ret = -1; + break; + } + + ret = process->exitCode(); + break; + } + + QCoreApplication::processEvents(); + + if (m_askedToStop) { + process->kill(); + ret = -1; + break; + } + } + + return ret; +} + +int VExporter::startProcess(const QString &p_cmd) +{ + QStringList args = VUtils::parseCombinedArgString(p_cmd); + if (args.isEmpty()) { + return -2; + } + + return startProcess(args.first(), args.mid(1)); +} + +bool VExporter::outputToHTMLFile(const QString &p_file, + const QString &p_title, + const QString &p_headContent, + const QString &p_styleContent, + const QString &p_bodyContent, + bool p_embedCssStyle, + bool p_completeHTML, + bool p_embedImages) +{ + QFile file(p_file); + if (!file.open(QFile::WriteOnly)) { + return false; + } + + QString resFolder = QFileInfo(p_file).completeBaseName() + "_files"; + QString resFolderPath = QDir(VUtils::basePathFromPath(p_file)).filePath(resFolder); + + qDebug() << "HTML files folder" << resFolderPath; + + QString html(m_exportHtmlTemplate); + if (!p_title.isEmpty()) { + html.replace(HtmlHolder::c_headTitleHolder, + "" + VUtils::escapeHtml(p_title) + ""); + } + + if (!p_styleContent.isEmpty() && p_embedCssStyle) { + QString content(p_styleContent); + embedStyleResources(content); + html.replace(HtmlHolder::c_styleHolder, content); + } + + if (!p_headContent.isEmpty()) { + html.replace(HtmlHolder::c_headHolder, p_headContent); + } + + if (p_completeHTML) { + QString content(p_bodyContent); + if (p_embedImages) { + embedBodyResources(m_baseUrl, content); + } else { + fixBodyResources(m_baseUrl, resFolderPath, content); + } + + html.replace(HtmlHolder::c_bodyHolder, content); + } else { + html.replace(HtmlHolder::c_bodyHolder, p_bodyContent); + } + + file.write(html.toUtf8()); + file.close(); + + // Delete empty resource folder. + QDir dir(resFolderPath); + dir.cdUp(); + dir.rmdir(resFolder); + + return true; +} + +int VExporter::exportCustomInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg) +{ + if (!convertFilesViaCustom(p_files, p_outputFile, p_opt.m_customOpt, p_errMsg)) { + return 0; + } + + return p_files.size(); +} diff --git a/src/vexporter.h b/src/vexporter.h new file mode 100644 index 00000000..cfdb9b1b --- /dev/null +++ b/src/vexporter.h @@ -0,0 +1,213 @@ +#ifndef VEXPORTER_H +#define VEXPORTER_H + +#include +#include +#include +// #include +#include + +#include "dialog/vexportdialog.h" + +class QWidget; +class VWebView; +class VDocument; + +class VExporter : public QObject +{ + Q_OBJECT +public: + explicit VExporter(QWidget *p_parent = nullptr); + + void prepareExport(const ExportOption &p_opt); + + bool exportPDF(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + bool exportHTML(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + bool exportCustom(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + int exportPDFInOne(const QList &p_htmlFiles, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + int exportCustomInOne(const QList &p_files, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + void setAskedToStop(bool p_askedToStop); + +signals: + // Request to output log. + void outputLog(const QString &p_log); + +private slots: + void handleLogicsFinished(); + + void handleLoadFinished(bool p_ok); + + // void handleDownloadRequested(QWebEngineDownloadItem *p_item); + +private: + enum class ExportState + { + Idle = 0, + Cancelled, + Busy, + Failed, + Successful + }; + + + enum NoteState + { + NotReady = 0, + WebLogicsReady = 0x1, + WebLoadFinished = 0x2, + Ready = 0x3, + Failed = 0x4 + }; + + + void initWebViewer(VFile *p_file, const ExportOption &p_opt); + + void clearWebViewer(); + + void clearNoteState(); + + bool isNoteStateReady() const; + + bool isNoteStateFailed() const; + + bool exportViaWebView(VFile *p_file, + const ExportOption &p_opt, + const QString &p_outputFile, + QString *p_errMsg = NULL); + + bool exportToPDF(VWebView *p_webViewer, + const QString &p_filePath, + const QPageLayout &p_layout); + + bool exportToPDFViaWK(VDocument *p_webDocument, + const ExportPDFOption &p_opt, + const QString &p_filePath, + QString *p_errMsg = NULL); + + bool exportToCustom(VDocument *p_webDocument, + const ExportCustomOption &p_opt, + const QString &p_filePath, + QString *p_errMsg = NULL); + + bool exportToHTML(VDocument *p_webDocument, + const ExportHTMLOption &p_opt, + const QString &p_filePath); + + bool exportToMHTML(VWebView *p_webViewer, + const ExportHTMLOption &p_opt, + const QString &p_filePath); + + bool htmlsToPDFViaWK(const QList &p_htmlFiles, + const QString &p_filePath, + const ExportPDFOption &p_opt, + QString *p_errMsg = NULL); + + bool convertFilesViaCustom(const QList &p_files, + const QString &p_filePath, + const ExportCustomOption &p_opt, + QString *p_errMsg = NULL); + + void prepareWKArguments(const ExportPDFOption &p_opt); + + int startProcess(const QString &p_program, const QStringList &p_args); + + int startProcess(const QString &p_cmd); + + // @p_embedImages: embed as data URI. + bool outputToHTMLFile(const QString &p_file, + const QString &p_title, + const QString &p_headContent, + const QString &p_styleContent, + const QString &p_bodyContent, + bool p_embedCssStyle, + bool p_completeHTML, + bool p_embedImages); + + // Fix @p_html's resources like url("...") with "file" or "qrc" schema. + // Copy the resource to @p_folder and fix the url string. + static bool fixStyleResources(const QString &p_folder, + QString &p_html); + + // Fix @p_html's resources like url("...") with "file" or "qrc" schema. + // Embed the image data in data URIs. + static bool embedStyleResources(QString &p_html); + + // Fix @p_html's resources like . + // Copy the resource to @p_folder and fix the url string. + static bool fixBodyResources(const QUrl &p_baseUrl, + const QString &p_folder, + QString &p_html); + + // Embed @p_html's resources like . + static bool embedBodyResources(const QUrl &p_baseUrl, QString &p_html); + + static QString getResourceRelativePath(const QString &p_file); + + QPageLayout m_pageLayout; + + // Will be allocated and free for each conversion. + VWebView *m_webViewer; + + VDocument *m_webDocument; + + // Base URL of VWebView. + QUrl m_baseUrl; + + QString m_htmlTemplate; + + // Template to hold the export HTML result. + QString m_exportHtmlTemplate; + + NoteState m_noteState; + + ExportState m_state; + + // Download state used for MIME HTML. + // QWebEngineDownloadItem::DownloadState m_downloadState; + + // Arguments for wkhtmltopdf. + QStringList m_wkArgs; + + bool m_askedToStop; +}; + +inline void VExporter::clearNoteState() +{ + m_noteState = NoteState::NotReady; +} + +inline bool VExporter::isNoteStateReady() const +{ + return m_noteState == NoteState::Ready; +} + +inline bool VExporter::isNoteStateFailed() const +{ + return m_noteState & NoteState::Failed; +} + +inline void VExporter::setAskedToStop(bool p_askedToStop) +{ + m_askedToStop = p_askedToStop; +} +#endif // VEXPORTER_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index fea92a44..f60aff24 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -50,6 +50,8 @@ #include "vlistue.h" #include "vtagexplorer.h" #include "vmdeditor.h" +#include "vexporter.h" +#include "dialog/vexportdialog.h" extern VConfigManager *g_config; @@ -1050,7 +1052,6 @@ void VMainWindow::initFileMenu() fileMenu->addSeparator(); - /* m_exportAct = new QAction(tr("E&xport"), this); m_exportAct->setToolTip(tr("Export notes")); VUtils::fixTextWithCaptainShortcut(m_exportAct, "Export"); @@ -1058,7 +1059,6 @@ void VMainWindow::initFileMenu() this, &VMainWindow::handleExportAct); fileMenu->addAction(m_exportAct); - */ // Print. m_printAct = new QAction(VIconUtils::menuIcon(":/resources/icons/print.svg"), @@ -3309,6 +3309,13 @@ void VMainWindow::updateEditReadAct(const VEditTab *p_tab) void VMainWindow::handleExportAct() { + VExportDialog dialog(m_notebookSelector->currentNotebook(), + m_dirTree->currentDirectory(), + m_curFile, + m_cart, + g_config->getMdConverterType(), + this); + dialog.exec(); } VNotebook *VMainWindow::getCurrentNotebook() const diff --git a/src/vmathjaxpreviewhelper.cpp b/src/vmathjaxpreviewhelper.cpp index fe87898b..620688cd 100644 --- a/src/vmathjaxpreviewhelper.cpp +++ b/src/vmathjaxpreviewhelper.cpp @@ -9,6 +9,7 @@ #include "vconfigmanager.h" #include "websocketclientwrapper.h" #include "websockettransport.h" +#include "vconstants.h" extern VConfigManager *g_config; @@ -78,7 +79,7 @@ void VMathJaxPreviewHelper::doInit() emit diagramPreviewResultReady(p_identifier, p_id, p_timeStamp, p_format, ba); }); - quint16 port = 20001; + quint16 port = WebSocketPort::PreviewHelperPort; bindToChannel(port, "content", m_webDoc); // setHtml() will change focus if it is not disabled. diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index df578da8..40c5e160 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -29,7 +29,6 @@ extern VMainWindow *g_mainWin; extern VConfigManager *g_config; -const quint16 VMdTab::c_basePort = 10999; QSet VMdTab::s_usedPorts; VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea, @@ -1684,7 +1683,7 @@ bool VMdTab::previewExpanded() const quint16 VMdTab::getNextPort() { - auto port = c_basePort; + quint16 port = WebSocketPort::LastSpecialPort; while (s_usedPorts.find(port) != s_usedPorts.end()) { ++port; } diff --git a/src/vmdtab.h b/src/vmdtab.h index ca7b407e..d9931317 100644 --- a/src/vmdtab.h +++ b/src/vmdtab.h @@ -286,8 +286,6 @@ private: int m_documentID; - static const quint16 c_basePort; - static QSet s_usedPorts; };