vexporter.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #include "vexporter.h"
  2. #include <QtWidgets>
  3. #include <QFileInfo>
  4. #include <QDir>
  5. #include <QWebChannel>
  6. #include <QDebug>
  7. #include <QVBoxLayout>
  8. #include <QShowEvent>
  9. #ifndef QT_NO_PRINTER
  10. #include <QPrinter>
  11. #include <QPageSetupDialog>
  12. #endif
  13. #include "vconfigmanager.h"
  14. #include "utils/vutils.h"
  15. #include "vfile.h"
  16. #include "vwebview.h"
  17. #include "vpreviewpage.h"
  18. #include "vconstants.h"
  19. #include "vnote.h"
  20. #include "vmarkdownconverter.h"
  21. #include "vdocument.h"
  22. #include "vlineedit.h"
  23. extern VConfigManager *g_config;
  24. QString VExporter::s_defaultPathDir = QDir::homePath();
  25. VExporter::VExporter(MarkdownConverterType p_mdType, QWidget *p_parent)
  26. : QDialog(p_parent), m_webViewer(NULL), m_mdType(p_mdType),
  27. m_file(NULL), m_type(ExportType::PDF), m_source(ExportSource::Invalid),
  28. m_noteState(NoteState::NotReady), m_state(ExportState::Idle),
  29. m_pageLayout(QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0))),
  30. m_exported(false)
  31. {
  32. initMarkdownTemplate();
  33. setupUI();
  34. }
  35. void VExporter::initMarkdownTemplate()
  36. {
  37. m_htmlTemplate = VUtils::generateHtmlTemplate(m_mdType, true);
  38. }
  39. void VExporter::setupUI()
  40. {
  41. m_infoLabel = new QLabel();
  42. m_infoLabel->setWordWrap(true);
  43. // Target file path.
  44. QLabel *pathLabel = new QLabel(tr("Target &path:"));
  45. m_pathEdit = new VLineEdit();
  46. pathLabel->setBuddy(m_pathEdit);
  47. m_browseBtn = new QPushButton(tr("&Browse"));
  48. connect(m_browseBtn, &QPushButton::clicked,
  49. this, &VExporter::handleBrowseBtnClicked);
  50. // Page layout.
  51. QLabel *layoutLabel = new QLabel(tr("Page layout:"));
  52. m_layoutLabel = new QLabel();
  53. m_layoutBtn = new QPushButton(tr("&Settings"));
  54. #ifndef QT_NO_PRINTER
  55. connect(m_layoutBtn, &QPushButton::clicked,
  56. this, &VExporter::handleLayoutBtnClicked);
  57. #else
  58. m_layoutBtn->hide();
  59. #endif
  60. // Progress.
  61. m_proLabel = new QLabel(this);
  62. m_proBar = new QProgressBar(this);
  63. // Ok is the default button.
  64. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  65. m_openBtn = m_btnBox->addButton(tr("Open File Location"), QDialogButtonBox::ActionRole);
  66. connect(m_btnBox, &QDialogButtonBox::accepted, this, &VExporter::startExport);
  67. connect(m_btnBox, &QDialogButtonBox::rejected, this, &VExporter::cancelExport);
  68. connect(m_openBtn, &QPushButton::clicked, this, &VExporter::openTargetPath);
  69. QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
  70. okBtn->setProperty("SpecialBtn", true);
  71. m_pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
  72. QGridLayout *mainLayout = new QGridLayout();
  73. mainLayout->addWidget(m_infoLabel, 0, 0, 1, 3);
  74. mainLayout->addWidget(pathLabel, 1, 0);
  75. mainLayout->addWidget(m_pathEdit, 1, 1);
  76. mainLayout->addWidget(m_browseBtn, 1, 2);
  77. mainLayout->addWidget(layoutLabel, 2, 0);
  78. mainLayout->addWidget(m_layoutLabel, 2, 1);
  79. mainLayout->addWidget(m_layoutBtn, 2, 2);
  80. mainLayout->addWidget(m_proLabel, 3, 1, 1, 2);
  81. mainLayout->addWidget(m_proBar, 4, 1, 1, 2);
  82. mainLayout->addWidget(m_btnBox, 5, 1, 1, 2);
  83. m_proLabel->hide();
  84. m_proBar->hide();
  85. setLayout(mainLayout);
  86. mainLayout->setSizeConstraint(QLayout::SetFixedSize);
  87. setWindowTitle(tr("Export Note"));
  88. m_openBtn->hide();
  89. updatePageLayoutLabel();
  90. }
  91. static QString exportTypeStr(ExportType p_type)
  92. {
  93. if (p_type == ExportType::PDF) {
  94. return "PDF";
  95. } else {
  96. return "HTML";
  97. }
  98. }
  99. void VExporter::handleBrowseBtnClicked()
  100. {
  101. QFileInfo fi(getFilePath());
  102. QString fileType = m_type == ExportType::PDF ?
  103. tr("Portable Document Format (*.pdf)") :
  104. tr("WebPage, Complete (*.html)");
  105. QString path = QFileDialog::getSaveFileName(this, tr("Export As"),
  106. fi.absoluteFilePath(),
  107. fileType);
  108. if (path.isEmpty()) {
  109. return;
  110. }
  111. setFilePath(path);
  112. s_defaultPathDir = VUtils::basePathFromPath(path);
  113. m_openBtn->hide();
  114. }
  115. void VExporter::handleLayoutBtnClicked()
  116. {
  117. #ifndef QT_NO_PRINTER
  118. QPrinter printer;
  119. printer.setPageLayout(m_pageLayout);
  120. QPageSetupDialog dlg(&printer, this);
  121. if (dlg.exec() != QDialog::Accepted) {
  122. return;
  123. }
  124. m_pageLayout.setPageSize(printer.pageLayout().pageSize());
  125. m_pageLayout.setOrientation(printer.pageLayout().orientation());
  126. updatePageLayoutLabel();
  127. #endif
  128. }
  129. void VExporter::updatePageLayoutLabel()
  130. {
  131. m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name())
  132. .arg(m_pageLayout.orientation() == QPageLayout::Portrait ?
  133. tr("Portrait") : tr("Landscape")));
  134. }
  135. QString VExporter::getFilePath() const
  136. {
  137. return QDir::cleanPath(m_pathEdit->text());
  138. }
  139. void VExporter::setFilePath(const QString &p_path)
  140. {
  141. m_pathEdit->setText(QDir::toNativeSeparators(p_path));
  142. }
  143. void VExporter::exportNote(VFile *p_file, ExportType p_type)
  144. {
  145. m_file = p_file;
  146. m_type = p_type;
  147. m_source = ExportSource::Note;
  148. if (!m_file || m_file->getDocType() != DocType::Markdown) {
  149. // Do not support non-Markdown note now.
  150. m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  151. return;
  152. }
  153. m_infoLabel->setText(tr("Export note <span style=\"%1\">%2</span> as %3.")
  154. .arg(g_config->c_dataTextStyle)
  155. .arg(m_file->getName())
  156. .arg(exportTypeStr(p_type)));
  157. setWindowTitle(tr("Export As %1").arg(exportTypeStr(p_type)));
  158. setFilePath(QDir(s_defaultPathDir).filePath(QFileInfo(p_file->fetchPath()).baseName() +
  159. "." + exportTypeStr(p_type).toLower()));
  160. }
  161. void VExporter::initWebViewer(VFile *p_file)
  162. {
  163. V_ASSERT(!m_webViewer);
  164. m_webViewer = new VWebView(p_file, this);
  165. m_webViewer->hide();
  166. VPreviewPage *page = new VPreviewPage(m_webViewer);
  167. m_webViewer->setPage(page);
  168. connect(page, &VPreviewPage::loadFinished,
  169. this, &VExporter::handleLoadFinished);
  170. VDocument *document = new VDocument(p_file, m_webViewer);
  171. connect(document, &VDocument::logicsFinished,
  172. this, &VExporter::handleLogicsFinished);
  173. QWebChannel *channel = new QWebChannel(m_webViewer);
  174. channel->registerObject(QStringLiteral("content"), document);
  175. page->setWebChannel(channel);
  176. // Need to generate HTML using Hoedown.
  177. if (m_mdType == MarkdownConverterType::Hoedown) {
  178. VMarkdownConverter mdConverter;
  179. QString toc;
  180. QString html = mdConverter.generateHtml(p_file->getContent(),
  181. g_config->getMarkdownExtensions(),
  182. toc);
  183. document->setHtml(html);
  184. }
  185. m_webViewer->setHtml(m_htmlTemplate, p_file->getBaseUrl());
  186. }
  187. void VExporter::clearWebViewer()
  188. {
  189. if (m_webViewer) {
  190. delete m_webViewer;
  191. m_webViewer = NULL;
  192. }
  193. }
  194. void VExporter::handleLogicsFinished()
  195. {
  196. Q_ASSERT(!(m_noteState & NoteState::WebLogicsReady));
  197. m_noteState = NoteState(m_noteState | NoteState::WebLogicsReady);
  198. }
  199. void VExporter::handleLoadFinished(bool p_ok)
  200. {
  201. Q_ASSERT(!(m_noteState & NoteState::WebLoadFinished));
  202. m_noteState = NoteState(m_noteState | NoteState::WebLoadFinished);
  203. if (!p_ok) {
  204. m_noteState = NoteState(m_noteState | NoteState::Failed);
  205. }
  206. }
  207. void VExporter::clearNoteState()
  208. {
  209. m_noteState = NoteState::NotReady;
  210. }
  211. bool VExporter::isNoteStateReady() const
  212. {
  213. return m_noteState == NoteState::Ready;
  214. }
  215. bool VExporter::isNoteStateFailed() const
  216. {
  217. return m_noteState & NoteState::Failed;
  218. }
  219. void VExporter::startExport()
  220. {
  221. QPushButton *cancelBtn = m_btnBox->button(QDialogButtonBox::Cancel);
  222. if (m_exported) {
  223. cancelBtn->show();
  224. m_exported = false;
  225. accept();
  226. }
  227. int exportedNum = 0;
  228. enableUserInput(false);
  229. V_ASSERT(m_state == ExportState::Idle);
  230. m_state = ExportState::Busy;
  231. m_openBtn->hide();
  232. if (m_source == ExportSource::Note) {
  233. V_ASSERT(m_file);
  234. bool isOpened = m_file->isOpened();
  235. if (!isOpened && !m_file->open()) {
  236. goto exit;
  237. }
  238. clearNoteState();
  239. initWebViewer(m_file);
  240. // Update progress info.
  241. m_proLabel->setText(tr("Exporting %1").arg(m_file->getName()));
  242. m_proBar->setEnabled(true);
  243. m_proBar->setMinimum(0);
  244. m_proBar->setMaximum(100);
  245. m_proBar->reset();
  246. m_proLabel->show();
  247. m_proBar->show();
  248. while (!isNoteStateReady()) {
  249. VUtils::sleepWait(100);
  250. if (m_proBar->value() < 70) {
  251. m_proBar->setValue(m_proBar->value() + 1);
  252. }
  253. if (m_state == ExportState::Cancelled) {
  254. goto exit;
  255. }
  256. if (isNoteStateFailed()) {
  257. m_state = ExportState::Failed;
  258. goto exit;
  259. }
  260. }
  261. // Wait to ensure Web side is really ready.
  262. VUtils::sleepWait(200);
  263. if (m_state == ExportState::Cancelled) {
  264. goto exit;
  265. }
  266. m_proBar->setValue(80);
  267. bool exportRet = exportToPDF(m_webViewer, getFilePath(), m_pageLayout);
  268. clearNoteState();
  269. if (!isOpened) {
  270. m_file->close();
  271. }
  272. if (exportRet) {
  273. m_proBar->setValue(100);
  274. m_state = ExportState::Successful;
  275. exportedNum++;
  276. } else {
  277. m_proBar->setEnabled(false);
  278. m_state = ExportState::Failed;
  279. }
  280. }
  281. exit:
  282. clearWebViewer();
  283. m_proLabel->setText("");
  284. m_proLabel->hide();
  285. enableUserInput(true);
  286. if (m_state == ExportState::Cancelled) {
  287. reject();
  288. }
  289. if (exportedNum) {
  290. m_exported = true;
  291. m_openBtn->show();
  292. cancelBtn->hide();
  293. }
  294. m_state = ExportState::Idle;
  295. }
  296. void VExporter::cancelExport()
  297. {
  298. if (m_state == ExportState::Idle) {
  299. reject();
  300. } else {
  301. m_state = ExportState::Cancelled;
  302. }
  303. }
  304. bool VExporter::exportToPDF(VWebView *p_webViewer, const QString &p_filePath,
  305. const QPageLayout &p_layout)
  306. {
  307. int pdfPrinted = 0;
  308. p_webViewer->page()->printToPdf([&, this](const QByteArray &p_result) {
  309. if (p_result.isEmpty() || this->m_state == ExportState::Cancelled) {
  310. pdfPrinted = -1;
  311. return;
  312. }
  313. V_ASSERT(!p_filePath.isEmpty());
  314. QFile file(p_filePath);
  315. if (!file.open(QFile::WriteOnly)) {
  316. pdfPrinted = -1;
  317. return;
  318. }
  319. file.write(p_result.data(), p_result.size());
  320. file.close();
  321. pdfPrinted = 1;
  322. }, p_layout);
  323. while (pdfPrinted == 0) {
  324. VUtils::sleepWait(100);
  325. if (m_state == ExportState::Cancelled) {
  326. break;
  327. }
  328. }
  329. return pdfPrinted == 1;
  330. }
  331. void VExporter::enableUserInput(bool p_enabled)
  332. {
  333. m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(p_enabled);
  334. m_pathEdit->setEnabled(p_enabled);
  335. m_browseBtn->setEnabled(p_enabled);
  336. m_layoutBtn->setEnabled(p_enabled);
  337. }
  338. void VExporter::openTargetPath() const
  339. {
  340. QUrl url = QUrl::fromLocalFile(VUtils::basePathFromPath(getFilePath()));
  341. QDesktopServices::openUrl(url);
  342. }