vexportdialog.cpp 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. #include "vexportdialog.h"
  2. #include <QtWidgets>
  3. #include <QCoreApplication>
  4. #include <QProcess>
  5. #include <QTemporaryDir>
  6. #ifndef QT_NO_PRINTER
  7. #include <QPrinter>
  8. #include <QPageSetupDialog>
  9. #endif
  10. #include "utils/vutils.h"
  11. #include "vlineedit.h"
  12. #include "vnotebook.h"
  13. #include "vfile.h"
  14. #include "vdirectory.h"
  15. #include "vcart.h"
  16. #include "vconfigmanager.h"
  17. #include "vnotefile.h"
  18. #include "vnote.h"
  19. #include "vexporter.h"
  20. #include "vlineedit.h"
  21. extern VConfigManager *g_config;
  22. extern VNote *g_vnote;
  23. QString VExportDialog::s_lastOutputFolder;
  24. ExportOption VExportDialog::s_opt;
  25. #define LOGERR(x) do { QString msg = (x); \
  26. VUtils::addErrMsg(p_errMsg, msg); \
  27. appendLogLine(msg); \
  28. } while (0)
  29. VExportDialog::VExportDialog(VNotebook *p_notebook,
  30. VDirectory *p_directory,
  31. VFile *p_file,
  32. VCart *p_cart,
  33. MarkdownConverterType p_renderer,
  34. QWidget *p_parent)
  35. : QDialog(p_parent),
  36. m_notebook(p_notebook),
  37. m_directory(p_directory),
  38. m_file(p_file),
  39. m_cart(p_cart),
  40. m_pageLayout(QPageLayout(QPageSize(QPageSize::A4),
  41. QPageLayout::Portrait,
  42. QMarginsF(10, 16, 10, 10),
  43. QPageLayout::Millimeter)),
  44. m_inExport(false),
  45. m_askedToStop(false)
  46. {
  47. if (s_lastOutputFolder.isEmpty()) {
  48. s_lastOutputFolder = g_config->getExportFolderPath();
  49. }
  50. setupUI();
  51. m_exporter = new VExporter(this);
  52. connect(m_exporter, &VExporter::outputLog,
  53. this, [this](const QString &p_log) {
  54. appendLogLine(p_log);
  55. });
  56. initUIFields(p_renderer);
  57. handleInputChanged();
  58. }
  59. void VExportDialog::setupUI()
  60. {
  61. // Notes to export.
  62. m_srcCB = VUtils::getComboBox();
  63. m_srcCB->setToolTip(tr("Choose notes to export"));
  64. m_srcCB->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
  65. connect(m_srcCB, SIGNAL(currentIndexChanged(int)),
  66. this, SLOT(handleCurrentSrcChanged(int)));
  67. // Target format.
  68. m_formatCB = VUtils::getComboBox();
  69. m_formatCB->setToolTip(tr("Choose target format to export as"));
  70. m_formatCB->setSizeAdjustPolicy(QComboBox::AdjustToContents);
  71. connect(m_formatCB, SIGNAL(currentIndexChanged(int)),
  72. this, SLOT(handleCurrentFormatChanged(int)));
  73. // Markdown renderer.
  74. m_rendererCB = VUtils::getComboBox();
  75. m_rendererCB->setToolTip(tr("Choose converter to render Markdown"));
  76. m_rendererCB->setSizeAdjustPolicy(QComboBox::AdjustToContents);
  77. // Markdown rendering background.
  78. m_renderBgCB = VUtils::getComboBox();
  79. m_renderBgCB->setToolTip(tr("Choose rendering background color for Markdown"));
  80. // Markdown rendering style.
  81. m_renderStyleCB = VUtils::getComboBox();
  82. m_renderStyleCB->setToolTip(tr("Choose rendering style for Markdown"));
  83. // Markdown rendering code block style.
  84. m_renderCodeBlockStyleCB = VUtils::getComboBox();
  85. m_renderCodeBlockStyleCB->setToolTip(tr("Choose rendering code block style for Markdown"));
  86. // Output directory.
  87. m_outputEdit = new VLineEdit(s_lastOutputFolder);
  88. connect(m_outputEdit, &QLineEdit::textChanged,
  89. this, &VExportDialog::handleInputChanged);
  90. QPushButton *browseBtn = new QPushButton(tr("&Browse"));
  91. connect(browseBtn, &QPushButton::clicked,
  92. this, &VExportDialog::handleOutputBrowseBtnClicked);
  93. QHBoxLayout *outputLayout = new QHBoxLayout();
  94. outputLayout->addWidget(m_outputEdit);
  95. outputLayout->addWidget(browseBtn);
  96. m_basicBox = new QGroupBox(tr("Information"));
  97. m_settingBox = new QGroupBox(tr("Advanced Settings"));
  98. m_consoleEdit = new QPlainTextEdit();
  99. m_consoleEdit->setReadOnly(true);
  100. m_consoleEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
  101. m_consoleEdit->setProperty("LineEdit", true);
  102. m_consoleEdit->setPlaceholderText(tr("Output logs will be shown here"));
  103. // Ok is the default button.
  104. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Close);
  105. m_exportBtn = m_btnBox->addButton(tr("Export"), QDialogButtonBox::ActionRole);
  106. m_openBtn = m_btnBox->addButton(tr("Open Output Directory"), QDialogButtonBox::ActionRole);
  107. connect(m_btnBox, &QDialogButtonBox::rejected,
  108. this, [this]() {
  109. if (m_inExport) {
  110. // Just cancel the export. Do not exit.
  111. m_askedToStop = true;
  112. } else {
  113. QDialog::reject();
  114. }
  115. });
  116. m_exportBtn->setProperty("SpecialBtn", true);
  117. connect(m_exportBtn, &QPushButton::clicked,
  118. this, &VExportDialog::startExport);
  119. connect(m_openBtn, &QPushButton::clicked,
  120. this, [this]() {
  121. QUrl url = QUrl::fromLocalFile(getOutputDirectory());
  122. QDesktopServices::openUrl(url);
  123. });
  124. QFormLayout *basicLayout = new QFormLayout();
  125. basicLayout->addRow(tr("Notes to export:"), m_srcCB);
  126. basicLayout->addRow(tr("Target format:"), m_formatCB);
  127. basicLayout->addRow(tr("Markdown renderer:"), m_rendererCB);
  128. basicLayout->addRow(tr("Markdown rendering background:"), m_renderBgCB);
  129. basicLayout->addRow(tr("Markdown rendering style:"), m_renderStyleCB);
  130. basicLayout->addRow(tr("Markdown rendering code block style:"), m_renderCodeBlockStyleCB);
  131. basicLayout->addRow(tr("Output directory:"), outputLayout);
  132. m_basicBox->setLayout(basicLayout);
  133. // Settings box.
  134. m_generalSettings = setupGeneralAdvancedSettings();
  135. m_htmlSettings = setupHTMLAdvancedSettings();
  136. m_pdfSettings = setupPDFAdvancedSettings();
  137. QVBoxLayout *advLayout = new QVBoxLayout();
  138. advLayout->addWidget(m_generalSettings);
  139. advLayout->addWidget(m_htmlSettings);
  140. advLayout->addWidget(m_pdfSettings);
  141. m_settingBox->setLayout(advLayout);
  142. QVBoxLayout *mainLayout = new QVBoxLayout();
  143. mainLayout->addWidget(m_basicBox);
  144. mainLayout->addWidget(m_settingBox);
  145. mainLayout->addWidget(m_consoleEdit);
  146. mainLayout->addWidget(m_btnBox);
  147. setLayout(mainLayout);
  148. setWindowTitle(tr("Export"));
  149. }
  150. QWidget *VExportDialog::setupPDFAdvancedSettings()
  151. {
  152. // Page layout settings.
  153. m_layoutLabel = new QLabel();
  154. QPushButton *layoutBtn = new QPushButton(tr("Settings"));
  155. #ifndef QT_NO_PRINTER
  156. connect(layoutBtn, &QPushButton::clicked,
  157. this, &VExportDialog::handleLayoutBtnClicked);
  158. #else
  159. layoutBtn->hide();
  160. #endif
  161. updatePageLayoutLabel();
  162. // Use wkhtmltopdf.
  163. m_wkhtmltopdfCB = new QCheckBox(tr("Use wkhtmltopdf"));
  164. m_wkhtmltopdfCB->setToolTip(tr("Use wkhtmltopdf tool to generate PDF (wkhtmltopdf needed to be installed)"));
  165. connect(m_wkhtmltopdfCB, &QCheckBox::stateChanged,
  166. this, [this](int p_state) {
  167. bool checked = p_state == Qt::Checked;
  168. m_wkPathEdit->setEnabled(checked);
  169. m_wkPathBrowseBtn->setEnabled(checked);
  170. m_wkBackgroundCB->setEnabled(checked);
  171. m_wkTableOfContentsCB->setEnabled(checked);
  172. m_wkPageNumberCB->setEnabled(checked);
  173. m_wkExtraArgsEdit->setEnabled(checked);
  174. });
  175. QPushButton *wkBtn = new QPushButton(tr("Download wkhtmltopdf"));
  176. connect(wkBtn, &QPushButton::clicked,
  177. this, [this]() {
  178. QString url("https://wkhtmltopdf.org/downloads.html");
  179. QDesktopServices::openUrl(QUrl(url));
  180. });
  181. // wkhtmltopdf Path.
  182. m_wkPathEdit = new VLineEdit();
  183. m_wkPathEdit->setToolTip(tr("Tell VNote where to find wkhtmltopdf tool"));
  184. m_wkPathEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
  185. m_wkPathBrowseBtn = new QPushButton(tr("&Browse"));
  186. m_wkPathBrowseBtn->setEnabled(m_wkhtmltopdfCB->isChecked());
  187. connect(m_wkPathBrowseBtn, &QPushButton::clicked,
  188. this, &VExportDialog::handleWkPathBrowseBtnClicked);
  189. m_wkTitleEdit = new VLineEdit();
  190. m_wkTitleEdit->setPlaceholderText(tr("Use the name of the first source note"));
  191. m_wkTitleEdit->setToolTip(tr("Title of the generated PDF file"));
  192. m_wkTitleEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
  193. m_wkTargetFileNameEdit = new VLineEdit();
  194. m_wkTargetFileNameEdit->setPlaceholderText(tr("Use the name of the first source note"));
  195. m_wkTargetFileNameEdit->setToolTip(tr("Name of the generated PDF file"));
  196. QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
  197. m_wkTargetFileNameEdit);
  198. m_wkTargetFileNameEdit->setValidator(validator);
  199. m_wkTargetFileNameEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
  200. // wkhtmltopdf enable background.
  201. m_wkBackgroundCB = new QCheckBox(tr("Enable background"));
  202. m_wkBackgroundCB->setToolTip(tr("Enable background when printing"));
  203. m_wkBackgroundCB->setEnabled(m_wkhtmltopdfCB->isChecked());
  204. // wkhtmltopdf enable table of contents.
  205. m_wkTableOfContentsCB = new QCheckBox(tr("Enable Table Of Contents"));
  206. m_wkTableOfContentsCB->setToolTip(tr("Add a table of contents to the document"));
  207. m_wkTableOfContentsCB->setEnabled(m_wkhtmltopdfCB->isChecked());
  208. // wkhtmltopdf page number.
  209. m_wkPageNumberCB = VUtils::getComboBox();
  210. m_wkPageNumberCB->setToolTip(tr("Append page number as footer"));
  211. m_wkPageNumberCB->setEnabled(m_wkhtmltopdfCB->isChecked());
  212. // wkhtmltopdf extra argumnets.
  213. m_wkExtraArgsEdit = new VLineEdit();
  214. m_wkExtraArgsEdit->setToolTip(tr("Additional arguments passed to wkhtmltopdf"));
  215. m_wkExtraArgsEdit->setPlaceholderText(tr("Use \" to enclose arguments containing space"));
  216. m_wkExtraArgsEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
  217. QGridLayout *advLayout = new QGridLayout();
  218. advLayout->addWidget(new QLabel(tr("Page layout:")), 0, 0);
  219. advLayout->addWidget(m_layoutLabel, 0, 1);
  220. advLayout->addWidget(layoutBtn, 0, 2);
  221. advLayout->addWidget(m_wkhtmltopdfCB, 1, 1, 1, 2);
  222. advLayout->addWidget(wkBtn, 1, 4, 1, 2);
  223. advLayout->addWidget(new QLabel(tr("wkhtmltopdf path:")), 2, 0);
  224. advLayout->addWidget(m_wkPathEdit, 2, 1, 1, 4);
  225. advLayout->addWidget(m_wkPathBrowseBtn, 2, 5);
  226. advLayout->addWidget(new QLabel(tr("Title:")), 3, 0);
  227. advLayout->addWidget(m_wkTitleEdit, 3, 1, 1, 2);
  228. advLayout->addWidget(new QLabel(tr("Output file name:")), 3, 3);
  229. advLayout->addWidget(m_wkTargetFileNameEdit, 3, 4, 1, 2);
  230. advLayout->addWidget(m_wkBackgroundCB, 4, 1, 1, 2);
  231. advLayout->addWidget(m_wkTableOfContentsCB, 4, 4, 1, 2);
  232. advLayout->addWidget(new QLabel(tr("Page number:")), 5, 0);
  233. advLayout->addWidget(m_wkPageNumberCB, 5, 1, 1, 2);
  234. advLayout->addWidget(new QLabel(tr("Additional global options:")), 6, 0);
  235. advLayout->addWidget(m_wkExtraArgsEdit, 6, 1, 1, 5);
  236. advLayout->setContentsMargins(0, 0, 0, 0);
  237. QWidget *wid = new QWidget();
  238. wid->setLayout(advLayout);
  239. return wid;
  240. }
  241. QWidget *VExportDialog::setupHTMLAdvancedSettings()
  242. {
  243. // Embed CSS styles.
  244. m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this);
  245. m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file"));
  246. // Complete HTML.
  247. m_completeHTMLCB = new QCheckBox(tr("Complete page"), this);
  248. m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures "
  249. "which may not keep the HTML link structure of "
  250. "the original page"));
  251. // Mime HTML.
  252. m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this);
  253. m_mimeHTMLCB->setToolTip(tr("Export as a complete web page in MIME HTML format"));
  254. connect(m_mimeHTMLCB, &QCheckBox::stateChanged,
  255. this, [this](int p_state) {
  256. bool checked = p_state == Qt::Checked;
  257. m_embedStyleCB->setEnabled(!checked);
  258. m_completeHTMLCB->setEnabled(!checked);
  259. });
  260. QFormLayout *advLayout = new QFormLayout();
  261. advLayout->addRow(m_embedStyleCB);
  262. advLayout->addRow(m_completeHTMLCB);
  263. advLayout->addRow(m_mimeHTMLCB);
  264. advLayout->setContentsMargins(0, 0, 0, 0);
  265. QWidget *wid = new QWidget();
  266. wid->setLayout(advLayout);
  267. return wid;
  268. }
  269. QWidget *VExportDialog::setupGeneralAdvancedSettings()
  270. {
  271. // Include subfolders.
  272. m_subfolderCB = new QCheckBox(tr("Process subfolders"));
  273. m_subfolderCB->setToolTip(tr("Process subfolders recursively"));
  274. QFormLayout *advLayout = new QFormLayout();
  275. advLayout->addRow(m_subfolderCB);
  276. advLayout->setContentsMargins(0, 0, 0, 0);
  277. QWidget *wid = new QWidget();
  278. wid->setLayout(advLayout);
  279. return wid;
  280. }
  281. void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
  282. {
  283. // Notes to export.
  284. if (m_file) {
  285. m_srcCB->addItem(tr("Current Note (%1)").arg(m_file->getName()),
  286. (int)ExportSource::CurrentNote);
  287. }
  288. if (m_directory) {
  289. m_srcCB->addItem(tr("Current Folder (%1)").arg(m_directory->getName()),
  290. (int)ExportSource::CurrentFolder);
  291. }
  292. if (m_notebook) {
  293. m_srcCB->addItem(tr("Current Notebook (%1)").arg(m_notebook->getName()),
  294. (int)ExportSource::CurrentNotebook);
  295. }
  296. if (m_cart && m_cart->count() > 0) {
  297. m_srcCB->addItem(tr("Cart (%1)").arg(m_cart->count()),
  298. (int)ExportSource::Cart);
  299. }
  300. m_subfolderCB->setChecked(s_opt.m_processSubfolders);
  301. // Export format.
  302. m_formatCB->addItem(tr("Markdown"), (int)ExportFormat::Markdown);
  303. m_formatCB->addItem(tr("HTML"), (int)ExportFormat::HTML);
  304. m_formatCB->addItem(tr("PDF"), (int)ExportFormat::PDF);
  305. m_formatCB->addItem(tr("PDF (All In One)"), (int)ExportFormat::OnePDF);
  306. m_formatCB->setCurrentIndex(m_formatCB->findData((int)s_opt.m_format));
  307. // Markdown renderer.
  308. m_rendererCB->addItem(tr("Hoedown"), MarkdownConverterType::Hoedown);
  309. m_rendererCB->addItem(tr("Marked"), MarkdownConverterType::Marked);
  310. m_rendererCB->addItem(tr("Markdown-it"), MarkdownConverterType::MarkdownIt);
  311. m_rendererCB->addItem(tr("Showdown"), MarkdownConverterType::Showdown);
  312. m_rendererCB->setCurrentIndex(m_rendererCB->findData(p_renderer));
  313. // Markdown rendering background.
  314. m_renderBgCB->addItem(tr("System"), "System");
  315. m_renderBgCB->addItem(tr("Transparent"), "Transparent");
  316. const QVector<VColor> &bgColors = g_config->getCustomColors();
  317. for (int i = 0; i < bgColors.size(); ++i) {
  318. m_renderBgCB->addItem(bgColors[i].m_name, bgColors[i].m_name);
  319. }
  320. if (s_opt.m_renderBg.isEmpty()) {
  321. s_opt.m_renderBg = g_config->getCurRenderBackgroundColor();
  322. }
  323. m_renderBgCB->setCurrentIndex(m_renderBgCB->findData(s_opt.m_renderBg));
  324. // Markdown rendering style.
  325. QList<QString> styles = g_config->getCssStyles();
  326. for (auto const &style : styles) {
  327. m_renderStyleCB->addItem(style, style);
  328. }
  329. m_renderStyleCB->setCurrentIndex(
  330. m_renderStyleCB->findData(g_config->getCssStyle()));
  331. // Markdown rendering code block style.
  332. QList<QString> cbStyles = g_config->getCodeBlockCssStyles();
  333. for (auto const &style : cbStyles) {
  334. m_renderCodeBlockStyleCB->addItem(style, style);
  335. }
  336. m_renderCodeBlockStyleCB->setCurrentIndex(
  337. m_renderCodeBlockStyleCB->findData(g_config->getCodeBlockCssStyle()));
  338. m_embedStyleCB->setChecked(s_opt.m_htmlOpt.m_embedCssStyle);
  339. m_completeHTMLCB->setChecked(s_opt.m_htmlOpt.m_completeHTML);
  340. m_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML);
  341. m_wkhtmltopdfCB->setChecked(s_opt.m_pdfOpt.m_wkhtmltopdf);
  342. // wkhtmltopdf path.
  343. m_wkPathEdit->setText(g_config->getWkhtmltopdfPath());
  344. m_wkBackgroundCB->setChecked(s_opt.m_pdfOpt.m_wkEnableBackground);
  345. m_wkTableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_wkEnableTableOfContents);
  346. // wkhtmltopdf page number.
  347. m_wkPageNumberCB->addItem(tr("None"), (int)ExportPageNumber::None);
  348. m_wkPageNumberCB->addItem(tr("Left"), (int)ExportPageNumber::Left);
  349. m_wkPageNumberCB->addItem(tr("Center"), (int)ExportPageNumber::Center);
  350. m_wkPageNumberCB->addItem(tr("Right"), (int)ExportPageNumber::Right);
  351. m_wkPageNumberCB->setCurrentIndex(m_wkPageNumberCB->findData((int)s_opt.m_pdfOpt.m_wkPageNumber));
  352. m_wkExtraArgsEdit->setText(g_config->getWkhtmltopdfArgs());
  353. }
  354. bool VExportDialog::checkWkhtmltopdfExecutable(const QString &p_file)
  355. {
  356. QStringList args;
  357. args << "--version";
  358. int ret = QProcess::execute(p_file, args);
  359. switch (ret) {
  360. case -2:
  361. appendLogLine(tr("Fail to start wkhtmltopdf."));
  362. break;
  363. case -1:
  364. appendLogLine(tr("wkhtmltopdf crashed."));
  365. break;
  366. case 0:
  367. appendLogLine(tr("Use %1.").arg(p_file));
  368. break;
  369. default:
  370. appendLogLine(tr("wkhtmltopdf returned %1.").arg(ret));
  371. break;
  372. }
  373. return ret == 0;
  374. }
  375. void VExportDialog::startExport()
  376. {
  377. if (m_inExport) {
  378. return;
  379. }
  380. m_exportBtn->setEnabled(false);
  381. m_askedToStop = false;
  382. m_inExport = true;
  383. QString outputFolder = QDir::cleanPath(QDir(getOutputDirectory()).absolutePath());
  384. s_opt = ExportOption(currentSource(),
  385. currentFormat(),
  386. (MarkdownConverterType)m_rendererCB->currentData().toInt(),
  387. m_renderBgCB->currentData().toString(),
  388. m_renderStyleCB->currentData().toString(),
  389. m_renderCodeBlockStyleCB->currentData().toString(),
  390. m_subfolderCB->isChecked(),
  391. ExportPDFOption(&m_pageLayout,
  392. m_wkhtmltopdfCB->isChecked(),
  393. QDir::toNativeSeparators(m_wkPathEdit->text()),
  394. m_wkBackgroundCB->isChecked(),
  395. m_wkTableOfContentsCB->isChecked(),
  396. m_wkTitleEdit->text(),
  397. m_wkTargetFileNameEdit->text(),
  398. (ExportPageNumber)m_wkPageNumberCB->currentData().toInt(),
  399. m_wkExtraArgsEdit->text()),
  400. ExportHTMLOption(m_embedStyleCB->isChecked(),
  401. m_completeHTMLCB->isChecked(),
  402. m_mimeHTMLCB->isChecked()));
  403. m_consoleEdit->clear();
  404. appendLogLine(tr("Export to %1.").arg(outputFolder));
  405. if (s_opt.m_format == ExportFormat::PDF
  406. || s_opt.m_format == ExportFormat::OnePDF
  407. || s_opt.m_format == ExportFormat::HTML) {
  408. if (s_opt.m_format != ExportFormat::OnePDF) {
  409. s_opt.m_pdfOpt.m_wkTitle.clear();
  410. s_opt.m_pdfOpt.m_wkTargetFileName.clear();
  411. }
  412. if ((s_opt.m_format == ExportFormat::PDF
  413. && s_opt.m_pdfOpt.m_wkhtmltopdf)
  414. || s_opt.m_format == ExportFormat::OnePDF) {
  415. g_config->setWkhtmltopdfPath(s_opt.m_pdfOpt.m_wkPath);
  416. g_config->setWkhtmltopdfArgs(s_opt.m_pdfOpt.m_wkExtraArgs);
  417. if (!checkWkhtmltopdfExecutable(s_opt.m_pdfOpt.m_wkPath)) {
  418. m_inExport = false;
  419. m_exportBtn->setEnabled(true);
  420. return;
  421. }
  422. }
  423. m_exporter->prepareExport(s_opt);
  424. }
  425. int ret = 0;
  426. QString msg;
  427. if (s_opt.m_format == ExportFormat::OnePDF) {
  428. QList<QString> files;
  429. // Output HTMLs to a tmp folder.
  430. QTemporaryDir tmpDir;
  431. if (!tmpDir.isValid()) {
  432. goto exit;
  433. }
  434. qDebug() << "output HTMLs to temporary dir" << tmpDir.path();
  435. s_opt.m_format = ExportFormat::HTML;
  436. switch (s_opt.m_source) {
  437. case ExportSource::CurrentNote:
  438. ret = doExport(m_file, s_opt, tmpDir.path(), &msg, &files);
  439. break;
  440. case ExportSource::CurrentFolder:
  441. ret = doExport(m_directory, s_opt, tmpDir.path(), &msg, &files);
  442. break;
  443. case ExportSource::CurrentNotebook:
  444. ret = doExport(m_notebook, s_opt, tmpDir.path(), &msg, &files);
  445. break;
  446. case ExportSource::Cart:
  447. ret = doExport(m_cart, s_opt, tmpDir.path(), &msg, &files);
  448. break;
  449. default:
  450. break;
  451. }
  452. s_opt.m_format = ExportFormat::OnePDF;
  453. Q_ASSERT(ret == files.size());
  454. if (!files.isEmpty()) {
  455. ret = doExportPDFAllInOne(files, s_opt, outputFolder, &msg);
  456. }
  457. } else {
  458. switch (s_opt.m_source) {
  459. case ExportSource::CurrentNote:
  460. ret = doExport(m_file, s_opt, outputFolder, &msg);
  461. break;
  462. case ExportSource::CurrentFolder:
  463. ret = doExport(m_directory, s_opt, outputFolder, &msg);
  464. break;
  465. case ExportSource::CurrentNotebook:
  466. ret = doExport(m_notebook, s_opt, outputFolder, &msg);
  467. break;
  468. case ExportSource::Cart:
  469. ret = doExport(m_cart, s_opt, outputFolder, &msg);
  470. break;
  471. default:
  472. break;
  473. }
  474. }
  475. exit:
  476. if (m_askedToStop) {
  477. appendLogLine(tr("User cancelled the export. Aborted!"));
  478. m_askedToStop = false;
  479. }
  480. if (!msg.isEmpty()) {
  481. VUtils::showMessage(QMessageBox::Warning,
  482. tr("Warning"),
  483. tr("Errors found during export."),
  484. msg,
  485. QMessageBox::Ok,
  486. QMessageBox::Ok,
  487. this);
  488. }
  489. appendLogLine(tr("%1 notes exported.").arg(ret));
  490. m_inExport = false;
  491. m_exportBtn->setEnabled(true);
  492. }
  493. QString VExportDialog::getOutputDirectory() const
  494. {
  495. return m_outputEdit->text();
  496. }
  497. void VExportDialog::handleOutputBrowseBtnClicked()
  498. {
  499. QString initPath = getOutputDirectory();
  500. if (!QFileInfo::exists(initPath)) {
  501. initPath = g_config->getDocumentPathOrHomePath();
  502. }
  503. QString dirPath = QFileDialog::getExistingDirectory(this,
  504. tr("Select Output Directory To Export To"),
  505. initPath,
  506. QFileDialog::ShowDirsOnly
  507. | QFileDialog::DontResolveSymlinks);
  508. if (!dirPath.isEmpty()) {
  509. m_outputEdit->setText(dirPath);
  510. s_lastOutputFolder = dirPath;
  511. }
  512. }
  513. void VExportDialog::handleWkPathBrowseBtnClicked()
  514. {
  515. QString initPath = m_wkPathEdit->text();
  516. if (!QFileInfo::exists(initPath)) {
  517. initPath = QCoreApplication::applicationDirPath();
  518. }
  519. #if defined(Q_OS_WIN)
  520. QString filter = tr("Executable (*.exe)");
  521. #else
  522. QString filter;
  523. #endif
  524. QString filePath = QFileDialog::getOpenFileName(this,
  525. tr("Select wkhtmltopdf Executable"),
  526. initPath,
  527. filter);
  528. if (!filePath.isEmpty()) {
  529. m_wkPathEdit->setText(filePath);
  530. }
  531. }
  532. void VExportDialog::handleInputChanged()
  533. {
  534. // Source.
  535. bool sourceOk = true;
  536. if (m_srcCB->count() == 0) {
  537. sourceOk = false;
  538. }
  539. // Output folder.
  540. bool pathOk = true;
  541. QString path = getOutputDirectory();
  542. if (path.isEmpty() || !VUtils::checkPathLegal(path)) {
  543. pathOk = false;
  544. }
  545. m_exportBtn->setEnabled(sourceOk && pathOk);
  546. m_openBtn->setEnabled(pathOk);
  547. }
  548. void VExportDialog::appendLogLine(const QString &p_text)
  549. {
  550. m_consoleEdit->appendPlainText(p_text);
  551. QCoreApplication::sendPostedEvents();
  552. }
  553. int VExportDialog::doExport(VFile *p_file,
  554. const ExportOption &p_opt,
  555. const QString &p_outputFolder,
  556. QString *p_errMsg,
  557. QList<QString> *p_outputFiles)
  558. {
  559. Q_ASSERT(p_file);
  560. appendLogLine(tr("Exporting note %1.").arg(p_file->fetchPath()));
  561. int ret = 0;
  562. switch (p_opt.m_format) {
  563. case ExportFormat::Markdown:
  564. ret = doExportMarkdown(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles);
  565. break;
  566. case ExportFormat::PDF:
  567. V_FALLTHROUGH;
  568. case ExportFormat::OnePDF:
  569. ret = doExportPDF(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles);
  570. break;
  571. case ExportFormat::HTML:
  572. ret = doExportHTML(p_file, p_opt, p_outputFolder, p_errMsg, p_outputFiles);
  573. break;
  574. default:
  575. break;
  576. }
  577. return ret;
  578. }
  579. int VExportDialog::doExport(VDirectory *p_directory,
  580. const ExportOption &p_opt,
  581. const QString &p_outputFolder,
  582. QString *p_errMsg,
  583. QList<QString> *p_outputFiles)
  584. {
  585. Q_ASSERT(p_directory);
  586. bool opened = p_directory->isOpened();
  587. if (!opened && !p_directory->open()) {
  588. LOGERR(tr("Fail to open folder %1.").arg(p_directory->fetchRelativePath()));
  589. return 0;
  590. }
  591. int ret = 0;
  592. QString folderName = VUtils::getDirNameWithSequence(p_outputFolder,
  593. p_directory->getName());
  594. QString outputPath = QDir(p_outputFolder).filePath(folderName);
  595. if (!VUtils::makePath(outputPath)) {
  596. LOGERR(tr("Fail to create directory %1.").arg(outputPath));
  597. goto exit;
  598. }
  599. // Export child notes.
  600. for (auto const & file : p_directory->getFiles()) {
  601. if (!checkUserAction()) {
  602. goto exit;
  603. }
  604. ret += doExport(file, p_opt, outputPath, p_errMsg, p_outputFiles);
  605. }
  606. // Export subfolders.
  607. if (p_opt.m_processSubfolders) {
  608. for (auto const & dir : p_directory->getSubDirs()) {
  609. if (!checkUserAction()) {
  610. goto exit;
  611. }
  612. ret += doExport(dir, p_opt, outputPath, p_errMsg, p_outputFiles);
  613. }
  614. }
  615. exit:
  616. if (!opened) {
  617. p_directory->close();
  618. }
  619. return ret;
  620. }
  621. int VExportDialog::doExport(VNotebook *p_notebook,
  622. const ExportOption &p_opt,
  623. const QString &p_outputFolder,
  624. QString *p_errMsg,
  625. QList<QString> *p_outputFiles)
  626. {
  627. Q_ASSERT(p_notebook);
  628. bool opened = p_notebook->isOpened();
  629. if (!opened && !p_notebook->open()) {
  630. LOGERR(tr("Fail to open notebook %1.").arg(p_notebook->getName()));
  631. return 0;
  632. }
  633. int ret = 0;
  634. QString folderName = VUtils::getDirNameWithSequence(p_outputFolder,
  635. p_notebook->getName());
  636. QString outputPath = QDir(p_outputFolder).filePath(folderName);
  637. if (!VUtils::makePath(outputPath)) {
  638. LOGERR(tr("Fail to create directory %1.").arg(outputPath));
  639. goto exit;
  640. }
  641. // Export subfolder.
  642. for (auto const & dir : p_notebook->getRootDir()->getSubDirs()) {
  643. if (!checkUserAction()) {
  644. goto exit;
  645. }
  646. ret += doExport(dir, p_opt, outputPath, p_errMsg, p_outputFiles);
  647. }
  648. exit:
  649. if (!opened) {
  650. p_notebook->close();
  651. }
  652. return ret;
  653. }
  654. int VExportDialog::doExport(VCart *p_cart,
  655. const ExportOption &p_opt,
  656. const QString &p_outputFolder,
  657. QString *p_errMsg,
  658. QList<QString> *p_outputFiles)
  659. {
  660. Q_ASSERT(p_cart);
  661. int ret = 0;
  662. QVector<QString> files = m_cart->getFiles();
  663. for (auto const & it : files) {
  664. VFile *file = g_vnote->getFile(it);
  665. if (!file) {
  666. LOGERR(tr("Fail to open file %1.").arg(it));
  667. continue;
  668. }
  669. ret += doExport(file, p_opt, p_outputFolder, p_errMsg, p_outputFiles);
  670. }
  671. return ret;
  672. }
  673. int VExportDialog::doExportMarkdown(VFile *p_file,
  674. const ExportOption &p_opt,
  675. const QString &p_outputFolder,
  676. QString *p_errMsg,
  677. QList<QString> *p_outputFiles)
  678. {
  679. Q_UNUSED(p_opt);
  680. QString srcFilePath(p_file->fetchPath());
  681. if (p_file->getDocType() != DocType::Markdown) {
  682. LOGERR(tr("Skip exporting non-Markdown file %1 as Markdown.").arg(srcFilePath));
  683. return 0;
  684. }
  685. // Export it to a folder with the same name.
  686. QString name = VUtils::getDirNameWithSequence(p_outputFolder, p_file->getName());
  687. QString outputPath = QDir(p_outputFolder).filePath(name);
  688. if (!VUtils::makePath(outputPath)) {
  689. LOGERR(tr("Fail to create directory %1.").arg(outputPath));
  690. return 0;
  691. }
  692. // Copy the note file.
  693. QString destPath = QDir(outputPath).filePath(p_file->getName());
  694. if (!VUtils::copyFile(srcFilePath, destPath, false)) {
  695. LOGERR(tr("Fail to copy the note file %1.").arg(srcFilePath));
  696. return 0;
  697. }
  698. // Copy images.
  699. int ret = 1;
  700. int nrImageCopied = 0;
  701. QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(p_file,
  702. ImageLink::LocalRelativeInternal);
  703. if (!VNoteFile::copyInternalImages(images,
  704. outputPath,
  705. false,
  706. &nrImageCopied,
  707. p_errMsg)) {
  708. ret = 0;
  709. appendLogLine(tr("Fail to copy images of note %1.").arg(srcFilePath));
  710. }
  711. // Copy attachments.
  712. if (p_file->getType() == FileType::Note) {
  713. VNoteFile *noteFile = static_cast<VNoteFile *>(p_file);
  714. QString attaFolder = noteFile->getAttachmentFolder();
  715. if (!attaFolder.isEmpty()) {
  716. QString attaFolderPath;
  717. attaFolderPath = noteFile->fetchAttachmentFolderPath();
  718. attaFolder = VUtils::getDirNameWithSequence(outputPath, attaFolder);
  719. QString folderPath = QDir(outputPath).filePath(attaFolder);
  720. // Copy attaFolder to folderPath.
  721. if (!VUtils::copyDirectory(attaFolderPath, folderPath, false)) {
  722. LOGERR(tr("Fail to copy attachments folder %1 to %2.")
  723. .arg(attaFolderPath).arg(folderPath));
  724. ret = 0;
  725. }
  726. }
  727. }
  728. if (ret) {
  729. if (p_outputFiles) {
  730. p_outputFiles->append(destPath);
  731. }
  732. appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath));
  733. }
  734. return ret;
  735. }
  736. int VExportDialog::doExportPDF(VFile *p_file,
  737. const ExportOption &p_opt,
  738. const QString &p_outputFolder,
  739. QString *p_errMsg,
  740. QList<QString> *p_outputFiles)
  741. {
  742. Q_UNUSED(p_opt);
  743. QString srcFilePath(p_file->fetchPath());
  744. if (p_file->getDocType() != DocType::Markdown) {
  745. LOGERR(tr("Skip exporting non-Markdown file %1 as PDF.").arg(srcFilePath));
  746. return 0;
  747. }
  748. if (!VUtils::makePath(p_outputFolder)) {
  749. LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder));
  750. return 0;
  751. }
  752. // Get output file.
  753. QString suffix = ".pdf";
  754. QString name = VUtils::getFileNameWithSequence(p_outputFolder,
  755. QFileInfo(p_file->getName()).completeBaseName() + suffix);
  756. QString outputPath = QDir(p_outputFolder).filePath(name);
  757. if (m_exporter->exportPDF(p_file, p_opt, outputPath, p_errMsg)) {
  758. if (p_outputFiles) {
  759. p_outputFiles->append(outputPath);
  760. }
  761. appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath));
  762. return 1;
  763. } else {
  764. appendLogLine(tr("Fail to export note %1.").arg(srcFilePath));
  765. return 0;
  766. }
  767. }
  768. int VExportDialog::doExportHTML(VFile *p_file,
  769. const ExportOption &p_opt,
  770. const QString &p_outputFolder,
  771. QString *p_errMsg,
  772. QList<QString> *p_outputFiles)
  773. {
  774. Q_UNUSED(p_opt);
  775. QString srcFilePath(p_file->fetchPath());
  776. if (p_file->getDocType() != DocType::Markdown) {
  777. LOGERR(tr("Skip exporting non-Markdown file %1 as HTML.").arg(srcFilePath));
  778. return 0;
  779. }
  780. if (!VUtils::makePath(p_outputFolder)) {
  781. LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder));
  782. return 0;
  783. }
  784. // Get output file.
  785. QString suffix = p_opt.m_htmlOpt.m_mimeHTML ? ".mht" : ".html";
  786. QString name = VUtils::getFileNameWithSequence(p_outputFolder,
  787. QFileInfo(p_file->getName()).completeBaseName() + suffix);
  788. QString outputPath = QDir(p_outputFolder).filePath(name);
  789. if (m_exporter->exportHTML(p_file, p_opt, outputPath, p_errMsg)) {
  790. if (p_outputFiles) {
  791. p_outputFiles->append(outputPath);
  792. }
  793. appendLogLine(tr("Note %1 exported to %2.").arg(srcFilePath).arg(outputPath));
  794. return 1;
  795. } else {
  796. appendLogLine(tr("Fail to export note %1.").arg(srcFilePath));
  797. return 0;
  798. }
  799. }
  800. bool VExportDialog::checkUserAction()
  801. {
  802. if (m_askedToStop) {
  803. return false;
  804. }
  805. QCoreApplication::processEvents();
  806. return true;
  807. }
  808. void VExportDialog::handleLayoutBtnClicked()
  809. {
  810. #ifndef QT_NO_PRINTER
  811. QPrinter printer;
  812. printer.setPageLayout(m_pageLayout);
  813. QPageSetupDialog dlg(&printer, this);
  814. if (dlg.exec() != QDialog::Accepted) {
  815. return;
  816. }
  817. m_pageLayout.setUnits(QPageLayout::Millimeter);
  818. m_pageLayout.setPageSize(printer.pageLayout().pageSize());
  819. m_pageLayout.setMargins(printer.pageLayout().margins(QPageLayout::Millimeter));
  820. m_pageLayout.setOrientation(printer.pageLayout().orientation());
  821. updatePageLayoutLabel();
  822. #endif
  823. }
  824. void VExportDialog::updatePageLayoutLabel()
  825. {
  826. m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name())
  827. .arg(m_pageLayout.orientation() == QPageLayout::Portrait ?
  828. tr("Portrait") : tr("Landscape")));
  829. }
  830. void VExportDialog::handleCurrentFormatChanged(int p_index)
  831. {
  832. bool pdfEnabled = false;
  833. bool htmlEnabled = false;
  834. bool pdfTitleNameEnabled = false;
  835. if (p_index >= 0) {
  836. switch (currentFormat()) {
  837. case ExportFormat::PDF:
  838. pdfEnabled = true;
  839. m_wkhtmltopdfCB->setEnabled(true);
  840. break;
  841. case ExportFormat::HTML:
  842. htmlEnabled = true;
  843. break;
  844. case ExportFormat::OnePDF:
  845. pdfEnabled = true;
  846. pdfTitleNameEnabled = true;
  847. m_wkhtmltopdfCB->setChecked(true);
  848. m_wkhtmltopdfCB->setEnabled(false);
  849. break;
  850. default:
  851. break;
  852. }
  853. }
  854. m_pdfSettings->setVisible(pdfEnabled);
  855. m_htmlSettings->setVisible(htmlEnabled);
  856. m_wkTitleEdit->setEnabled(pdfTitleNameEnabled);
  857. m_wkTargetFileNameEdit->setEnabled(pdfTitleNameEnabled);
  858. }
  859. void VExportDialog::handleCurrentSrcChanged(int p_index)
  860. {
  861. bool subfolderEnabled = false;
  862. if (p_index >= 0) {
  863. switch (currentSource()) {
  864. case ExportSource::CurrentFolder:
  865. subfolderEnabled = true;
  866. break;
  867. default:
  868. break;
  869. }
  870. }
  871. m_subfolderCB->setVisible(subfolderEnabled);
  872. }
  873. int VExportDialog::doExportPDFAllInOne(const QList<QString> &p_files,
  874. const ExportOption &p_opt,
  875. const QString &p_outputFolder,
  876. QString *p_errMsg)
  877. {
  878. if (p_files.isEmpty()) {
  879. return 0;
  880. }
  881. if (!VUtils::makePath(p_outputFolder)) {
  882. LOGERR(tr("Fail to create directory %1.").arg(p_outputFolder));
  883. return 0;
  884. }
  885. // Get output file.
  886. const QString suffix = ".pdf";
  887. QString name = p_opt.m_pdfOpt.m_wkTargetFileName;
  888. if (name.isEmpty()) {
  889. name = VUtils::getFileNameWithSequence(p_outputFolder,
  890. QFileInfo(p_files.first()).completeBaseName() + suffix);
  891. } else if (!name.endsWith(suffix)) {
  892. name += suffix;
  893. }
  894. QString outputPath = QDir(p_outputFolder).filePath(name);
  895. qDebug() << "output" << p_files.size() << "HTML files as PDF to" << outputPath;
  896. int ret = m_exporter->exportPDFInOne(p_files, p_opt, outputPath, p_errMsg);
  897. if (ret > 0) {
  898. appendLogLine(tr("%1 notes exported to %2.").arg(ret).arg(outputPath));
  899. } else {
  900. appendLogLine(tr("Fail to export %1 notes in one PDF.").arg(p_files.size()));
  901. }
  902. return ret;
  903. }