#include #include #include #include #include #include #include "vedittab.h" #include "vedit.h" #include "vdocument.h" #include "vnote.h" #include "utils/vutils.h" #include "vpreviewpage.h" #include "hgmarkdownhighlighter.h" #include "vconfigmanager.h" #include "vmarkdownconverter.h" #include "vnotebook.h" #include "vtoc.h" extern VConfigManager vconfig; VEditTab::VEditTab(const QString &path, bool modifiable, QWidget *parent) : QStackedWidget(parent), mdConverterType(vconfig.getMdConverterType()) { DocType docType = VUtils::isMarkdown(path) ? DocType::Markdown : DocType::Html; QString basePath = QFileInfo(path).path(); QString fileName = QFileInfo(path).fileName(); qDebug() << "VEditTab basePath" << basePath << "file" << fileName; QString fileText = VUtils::readFileFromDisk(path); noteFile = new VNoteFile(basePath, fileName, fileText, docType, modifiable); isEditMode = false; setupUI(); showFileReadMode(); connect(qApp, &QApplication::focusChanged, this, &VEditTab::handleFocusChanged); } VEditTab::~VEditTab() { if (noteFile) { delete noteFile; } } void VEditTab::setupUI() { textEditor = new VEdit(noteFile); connect(textEditor, &VEdit::headersChanged, this, &VEditTab::updateTocFromHeaders); connect(textEditor, SIGNAL(curHeaderChanged(int)), this, SLOT(updateCurHeader(int))); connect(textEditor, &VEdit::textChanged, this, &VEditTab::statusChanged); addWidget(textEditor); switch (noteFile->docType) { case DocType::Markdown: setupMarkdownPreview(); textBrowser = NULL; break; case DocType::Html: textBrowser = new QTextBrowser(); addWidget(textBrowser); textBrowser->setFont(vconfig.getBaseEditFont()); textBrowser->setPalette(vconfig.getBaseEditPalette()); webPreviewer = NULL; break; default: qWarning() << "error: unknown doc type" << int(noteFile->docType); } } void VEditTab::showFileReadMode() { isEditMode = false; switch (noteFile->docType) { case DocType::Html: textBrowser->setHtml(noteFile->content); textBrowser->setFont(vconfig.getBaseEditFont()); textBrowser->setPalette(vconfig.getBaseEditPalette()); setCurrentWidget(textBrowser); break; case DocType::Markdown: if (mdConverterType == MarkdownConverterType::Marked) { document.setText(noteFile->content); } else { previewByConverter(); } setCurrentWidget(webPreviewer); break; default: qWarning() << "error: unknown doc type" << int(noteFile->docType); } } void VEditTab::previewByConverter() { VMarkdownConverter mdConverter; QString content = noteFile->content; QString html = mdConverter.generateHtml(content, vconfig.getMarkdownExtensions()); QRegularExpression tocExp("

\\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption); QString toc = mdConverter.generateToc(content, vconfig.getMarkdownExtensions()); html.replace(tocExp, toc); document.setHtml(html); // Hoedown will add '\n' while Marked does not updateTocFromHtml(toc.replace("\n", "")); } void VEditTab::showFileEditMode() { isEditMode = true; textEditor->beginEdit(); setCurrentWidget(textEditor); textEditor->setFocus(); } bool VEditTab::requestClose() { readFile(); return !isEditMode; } void VEditTab::editFile() { if (isEditMode || !noteFile->modifiable) { return; } showFileEditMode(); } void VEditTab::readFile() { if (!isEditMode) { return; } if (textEditor->isModified()) { // Need to save the changes QMessageBox msgBox(this); msgBox.setText(QString("The note \"%1\" has been modified.").arg(noteFile->fileName)); msgBox.setInformativeText("Do you want to save your changes?"); msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Save); int ret = msgBox.exec(); switch (ret) { case QMessageBox::Save: saveFile(); // Fall through case QMessageBox::Discard: textEditor->reloadFile(); break; case QMessageBox::Cancel: // Nothing to do if user cancel this action return; default: qWarning() << "error: wrong return value from QMessageBox:" << ret; return; } } textEditor->setReadOnly(true); showFileReadMode(); } bool VEditTab::saveFile() { bool ret; if (!isEditMode || !noteFile->modifiable || !textEditor->isModified()) { return true; } // Make sure the file already exists. Temporary deal with cases when user delete or move // a file. QString filePath = QDir(noteFile->basePath).filePath(noteFile->fileName); if (!QFile(filePath).exists()) { qWarning() << "error:" << filePath << "being written has been removed"; QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"), QString("%1 being written has been removed.").arg(filePath), QMessageBox::Ok, this); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.exec(); return false; } textEditor->saveFile(); ret = VUtils::writeFileToDisk(filePath, noteFile->content); if (!ret) { QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"), QString("Fail to write to disk when saving a note. Please try it again."), QMessageBox::Ok, this); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.exec(); textEditor->setModified(true); ret = false; } ret = true; emit statusChanged(); return ret; } void VEditTab::setupMarkdownPreview() { webPreviewer = new QWebEngineView(this); VPreviewPage *page = new VPreviewPage(this); webPreviewer->setPage(page); QWebChannel *channel = new QWebChannel(this); channel->registerObject(QStringLiteral("content"), &document); connect(&document, &VDocument::tocChanged, this, &VEditTab::updateTocFromHtml); connect(&document, SIGNAL(headerChanged(const QString&)), this, SLOT(updateCurHeader(const QString &))); page->setWebChannel(channel); if (mdConverterType == MarkdownConverterType::Marked) { webPreviewer->setHtml(VNote::templateHtml, QUrl::fromLocalFile(noteFile->basePath + QDir::separator())); } else { webPreviewer->setHtml(VNote::preTemplateHtml + VNote::postTemplateHtml, QUrl::fromLocalFile(noteFile->basePath + QDir::separator())); } addWidget(webPreviewer); } void VEditTab::focusTab() { currentWidget()->setFocus(); } void VEditTab::handleFocusChanged(QWidget *old, QWidget *now) { if (isChild(now)) { emit getFocused(); } } void VEditTab::updateTocFromHtml(const QString &tocHtml) { qDebug() << tocHtml; tableOfContent.type = VHeaderType::Anchor; QVector &headers = tableOfContent.headers; headers.clear(); if (!tocHtml.isEmpty()) { QXmlStreamReader xml(tocHtml); if (xml.readNextStartElement()) { if (xml.name() == "ul") { parseTocUl(xml, headers, 1); } else { qWarning() << "error: TOC HTML does not start with