#include "vexporter.h" #include #include #include #include #include #include #include #ifndef QT_NO_PRINTER #include #include #endif #include "vconfigmanager.h" #include "utils/vutils.h" #include "vfile.h" #include "vwebview.h" #include "vpreviewpage.h" #include "vconstants.h" #include "vnote.h" #include "vmarkdownconverter.h" #include "vdocument.h" #include "vlineedit.h" extern VConfigManager *g_config; QString VExporter::s_defaultPathDir = QDir::homePath(); VExporter::VExporter(MarkdownConverterType p_mdType, QWidget *p_parent) : QDialog(p_parent), m_webViewer(NULL), m_mdType(p_mdType), m_file(NULL), m_type(ExportType::PDF), m_source(ExportSource::Invalid), m_noteState(NoteState::NotReady), m_state(ExportState::Idle), m_pageLayout(QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0))), m_exported(false) { initMarkdownTemplate(); setupUI(); } void VExporter::initMarkdownTemplate() { m_htmlTemplate = VUtils::generateHtmlTemplate(m_mdType, true); } void VExporter::setupUI() { m_infoLabel = new QLabel(); m_infoLabel->setWordWrap(true); // Target file path. QLabel *pathLabel = new QLabel(tr("Target &path:")); m_pathEdit = new VLineEdit(); pathLabel->setBuddy(m_pathEdit); m_browseBtn = new QPushButton(tr("&Browse")); connect(m_browseBtn, &QPushButton::clicked, this, &VExporter::handleBrowseBtnClicked); // Page layout. QLabel *layoutLabel = new QLabel(tr("Page layout:")); m_layoutLabel = new QLabel(); m_layoutBtn = new QPushButton(tr("&Settings")); #ifndef QT_NO_PRINTER connect(m_layoutBtn, &QPushButton::clicked, this, &VExporter::handleLayoutBtnClicked); #else m_layoutBtn->hide(); #endif // Progress. m_proLabel = new QLabel(this); m_proBar = new QProgressBar(this); // Ok is the default button. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_openBtn = m_btnBox->addButton(tr("Open File Location"), QDialogButtonBox::ActionRole); connect(m_btnBox, &QDialogButtonBox::accepted, this, &VExporter::startExport); connect(m_btnBox, &QDialogButtonBox::rejected, this, &VExporter::cancelExport); connect(m_openBtn, &QPushButton::clicked, this, &VExporter::openTargetPath); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); okBtn->setProperty("SpecialBtn", true); m_pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); QGridLayout *mainLayout = new QGridLayout(); mainLayout->addWidget(m_infoLabel, 0, 0, 1, 3); mainLayout->addWidget(pathLabel, 1, 0); mainLayout->addWidget(m_pathEdit, 1, 1); mainLayout->addWidget(m_browseBtn, 1, 2); mainLayout->addWidget(layoutLabel, 2, 0); mainLayout->addWidget(m_layoutLabel, 2, 1); mainLayout->addWidget(m_layoutBtn, 2, 2); mainLayout->addWidget(m_proLabel, 3, 1, 1, 2); mainLayout->addWidget(m_proBar, 4, 1, 1, 2); mainLayout->addWidget(m_btnBox, 5, 1, 1, 2); m_proLabel->hide(); m_proBar->hide(); setLayout(mainLayout); mainLayout->setSizeConstraint(QLayout::SetFixedSize); setWindowTitle(tr("Export Note")); m_openBtn->hide(); updatePageLayoutLabel(); } static QString exportTypeStr(ExportType p_type) { if (p_type == ExportType::PDF) { return "PDF"; } else { return "HTML"; } } void VExporter::handleBrowseBtnClicked() { QFileInfo fi(getFilePath()); QString fileType = m_type == ExportType::PDF ? tr("Portable Document Format (*.pdf)") : tr("WebPage, Complete (*.html)"); QString path = QFileDialog::getSaveFileName(this, tr("Export As"), fi.absoluteFilePath(), fileType); if (path.isEmpty()) { return; } setFilePath(path); s_defaultPathDir = VUtils::basePathFromPath(path); m_openBtn->hide(); } void VExporter::handleLayoutBtnClicked() { #ifndef QT_NO_PRINTER QPrinter printer; printer.setPageLayout(m_pageLayout); QPageSetupDialog dlg(&printer, this); if (dlg.exec() != QDialog::Accepted) { return; } m_pageLayout.setPageSize(printer.pageLayout().pageSize()); m_pageLayout.setOrientation(printer.pageLayout().orientation()); updatePageLayoutLabel(); #endif } void VExporter::updatePageLayoutLabel() { m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name()) .arg(m_pageLayout.orientation() == QPageLayout::Portrait ? tr("Portrait") : tr("Landscape"))); } QString VExporter::getFilePath() const { return QDir::cleanPath(m_pathEdit->text()); } void VExporter::setFilePath(const QString &p_path) { m_pathEdit->setText(QDir::toNativeSeparators(p_path)); } void VExporter::exportNote(VFile *p_file, ExportType p_type) { m_file = p_file; m_type = p_type; m_source = ExportSource::Note; if (!m_file || m_file->getDocType() != DocType::Markdown) { // Do not support non-Markdown note now. m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } m_infoLabel->setText(tr("Export note %2 as %3.") .arg(g_config->c_dataTextStyle) .arg(m_file->getName()) .arg(exportTypeStr(p_type))); setWindowTitle(tr("Export As %1").arg(exportTypeStr(p_type))); setFilePath(QDir(s_defaultPathDir).filePath(QFileInfo(p_file->fetchPath()).baseName() + "." + exportTypeStr(p_type).toLower())); } void VExporter::initWebViewer(VFile *p_file) { V_ASSERT(!m_webViewer); m_webViewer = new VWebView(p_file, this); m_webViewer->hide(); VPreviewPage *page = new VPreviewPage(m_webViewer); m_webViewer->setPage(page); connect(page, &VPreviewPage::loadFinished, this, &VExporter::handleLoadFinished); VDocument *document = new VDocument(p_file, m_webViewer); connect(document, &VDocument::logicsFinished, this, &VExporter::handleLogicsFinished); QWebChannel *channel = new QWebChannel(m_webViewer); channel->registerObject(QStringLiteral("content"), document); page->setWebChannel(channel); // Need to generate HTML using Hoedown. if (m_mdType == MarkdownConverterType::Hoedown) { VMarkdownConverter mdConverter; QString toc; QString html = mdConverter.generateHtml(p_file->getContent(), g_config->getMarkdownExtensions(), toc); document->setHtml(html); } m_webViewer->setHtml(m_htmlTemplate, p_file->getBaseUrl()); } void VExporter::clearWebViewer() { if (m_webViewer) { delete m_webViewer; m_webViewer = NULL; } } 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::clearNoteState() { m_noteState = NoteState::NotReady; } bool VExporter::isNoteStateReady() const { return m_noteState == NoteState::Ready; } bool VExporter::isNoteStateFailed() const { return m_noteState & NoteState::Failed; } void VExporter::startExport() { QPushButton *cancelBtn = m_btnBox->button(QDialogButtonBox::Cancel); if (m_exported) { cancelBtn->show(); m_exported = false; accept(); } int exportedNum = 0; enableUserInput(false); V_ASSERT(m_state == ExportState::Idle); m_state = ExportState::Busy; m_openBtn->hide(); if (m_source == ExportSource::Note) { V_ASSERT(m_file); bool isOpened = m_file->isOpened(); if (!isOpened && !m_file->open()) { goto exit; } clearNoteState(); initWebViewer(m_file); // Update progress info. m_proLabel->setText(tr("Exporting %1").arg(m_file->getName())); m_proBar->setEnabled(true); m_proBar->setMinimum(0); m_proBar->setMaximum(100); m_proBar->reset(); m_proLabel->show(); m_proBar->show(); while (!isNoteStateReady()) { VUtils::sleepWait(100); if (m_proBar->value() < 70) { m_proBar->setValue(m_proBar->value() + 1); } if (m_state == ExportState::Cancelled) { goto exit; } if (isNoteStateFailed()) { m_state = ExportState::Failed; goto exit; } } // Wait to ensure Web side is really ready. VUtils::sleepWait(200); if (m_state == ExportState::Cancelled) { goto exit; } m_proBar->setValue(80); bool exportRet = exportToPDF(m_webViewer, getFilePath(), m_pageLayout); clearNoteState(); if (!isOpened) { m_file->close(); } if (exportRet) { m_proBar->setValue(100); m_state = ExportState::Successful; exportedNum++; } else { m_proBar->setEnabled(false); m_state = ExportState::Failed; } } exit: clearWebViewer(); m_proLabel->setText(""); m_proLabel->hide(); enableUserInput(true); if (m_state == ExportState::Cancelled) { reject(); } if (exportedNum) { m_exported = true; m_openBtn->show(); cancelBtn->hide(); } m_state = ExportState::Idle; } void VExporter::cancelExport() { if (m_state == ExportState::Idle) { reject(); } else { m_state = ExportState::Cancelled; } } 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()); QFile file(p_filePath); if (!file.open(QFile::WriteOnly)) { pdfPrinted = -1; return; } file.write(p_result.data(), p_result.size()); file.close(); pdfPrinted = 1; }, p_layout); while (pdfPrinted == 0) { VUtils::sleepWait(100); if (m_state == ExportState::Cancelled) { break; } } return pdfPrinted == 1; } void VExporter::enableUserInput(bool p_enabled) { m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(p_enabled); m_pathEdit->setEnabled(p_enabled); m_browseBtn->setEnabled(p_enabled); m_layoutBtn->setEnabled(p_enabled); } void VExporter::openTargetPath() const { QUrl url = QUrl::fromLocalFile(VUtils::basePathFromPath(getFilePath())); QDesktopServices::openUrl(url); }