| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199 |
- #include "vmdeditor.h"
- #include <QtWidgets>
- #include <QMenu>
- #include <QDebug>
- #include <QScopedPointer>
- #include <QClipboard>
- #include <QMimeDatabase>
- #include <QTemporaryFile>
- #include <QProgressDialog>
- #include "vdocument.h"
- #include "utils/veditutils.h"
- #include "vedittab.h"
- #include "pegmarkdownhighlighter.h"
- #include "vcodeblockhighlighthelper.h"
- #include "vmdeditoperations.h"
- #include "vtableofcontent.h"
- #include "utils/veditutils.h"
- #include "dialog/vselectdialog.h"
- #include "dialog/vconfirmdeletiondialog.h"
- #include "vtextblockdata.h"
- #include "vorphanfile.h"
- #include "vnotefile.h"
- #include "vpreviewmanager.h"
- #include "utils/viconutils.h"
- #include "dialog/vcopytextashtmldialog.h"
- #include "utils/vwebutils.h"
- #include "dialog/vinsertlinkdialog.h"
- #include "utils/vclipboardutils.h"
- #include "vplantumlhelper.h"
- #include "vgraphvizhelper.h"
- #include "vmdtab.h"
- #include "vdownloader.h"
- extern VWebUtils *g_webUtils;
- extern VConfigManager *g_config;
- #define LINE_NUMBER_AREA_FONT_DELTA -2
- VMdEditor::VMdEditor(VFile *p_file,
- VDocument *p_doc,
- MarkdownConverterType p_type,
- const QSharedPointer<VTextEditCompleter> &p_completer,
- QWidget *p_parent)
- : VTextEdit(p_parent),
- VEditor(p_file, this, p_completer),
- m_pegHighlighter(NULL),
- m_freshEdit(true),
- m_textToHtmlDialog(NULL),
- m_zoomDelta(0),
- m_editTab(NULL),
- m_copyTimeStamp(0)
- {
- Q_ASSERT(p_file->getDocType() == DocType::Markdown);
- VEditor::init();
- // Hook functions from VEditor.
- connect(this, &VTextEdit::cursorPositionChanged,
- this, [this]() {
- highlightOnCursorPositionChanged();
- });
- connect(document(), &QTextDocument::contentsChange,
- this, [this](int p_position, int p_charsRemoved, int p_charsAdded) {
- Q_UNUSED(p_position);
- if (p_charsRemoved > 0 || p_charsAdded > 0) {
- updateTimeStamp();
- }
- });
- connect(this, &VTextEdit::selectionChanged,
- this, [this]() {
- highlightSelectedWord();
- });
- // End.
- setReadOnly(true);
- m_pegHighlighter = new PegMarkdownHighlighter(document(), this);
- m_pegHighlighter->init(g_config->getMdHighlightingStyles(),
- g_config->getCodeBlockStyles(),
- g_config->getEnableMathjax(),
- g_config->getMarkdownHighlightInterval());
- connect(m_pegHighlighter, &PegMarkdownHighlighter::headersUpdated,
- this, &VMdEditor::updateHeaders);
- // After highlight, the cursor may trun into non-visible. We should make it visible
- // in this case.
- connect(m_pegHighlighter, &PegMarkdownHighlighter::highlightCompleted,
- this, [this]() {
- makeBlockVisible(textCursor().block());
- if (m_freshEdit) {
- m_freshEdit = false;
- emit m_object->ready();
- }
- });
- m_cbHighlighter = new VCodeBlockHighlightHelper(m_pegHighlighter,
- p_doc,
- p_type);
- m_previewMgr = new VPreviewManager(this, m_pegHighlighter);
- connect(m_pegHighlighter, &PegMarkdownHighlighter::imageLinksUpdated,
- m_previewMgr, &VPreviewManager::updateImageLinks);
- connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
- m_pegHighlighter, &PegMarkdownHighlighter::updateHighlight);
- m_editOps = new VMdEditOperations(this, m_file);
- connect(m_editOps, &VEditOperations::statusMessage,
- m_object, &VEditorObject::statusMessage);
- connect(m_editOps, &VEditOperations::vimStatusUpdated,
- m_object, &VEditorObject::vimStatusUpdated);
- connect(this, &VTextEdit::cursorPositionChanged,
- this, &VMdEditor::updateCurrentHeader);
- connect(this, &VTextEdit::cursorPositionChanged,
- m_object, &VEditorObject::cursorPositionChanged);
- setDisplayScaleFactor(VUtils::calculateScaleFactor());
- updateFontAndPalette();
- updateConfig();
- }
- void VMdEditor::updateFontAndPalette()
- {
- QFont font(g_config->getMdEditFont());
- setFont(font);
- const QPalette &palette = g_config->getMdEditPalette();
- /*
- Do not use this function in conjunction with Qt Style Sheets. When
- using style sheets, the palette of a widget can be customized using
- the "color", "background-color", "selection-color",
- "selection-background-color" and "alternate-background-color".
- */
- // setPalette(palette);
- // setTextColor(palette.color(QPalette::Text));
- // Only this could override the font-family set of QWidget in QSS.
- setFontAndPaletteByStyleSheet(font, palette);
- font.setPointSize(font.pointSize() + LINE_NUMBER_AREA_FONT_DELTA);
- updateLineNumberAreaWidth(QFontMetrics(font));
- }
- void VMdEditor::beginEdit()
- {
- updateConfig();
- initInitImages();
- setModified(false);
- setReadOnlyAndHighlightCurrentLine(false);
- emit statusChanged();
- if (m_freshEdit) {
- m_pegHighlighter->updateHighlight();
- relayout();
- } else {
- updateHeaders(m_pegHighlighter->getHeaderRegions());
- }
- }
- void VMdEditor::endEdit()
- {
- setReadOnlyAndHighlightCurrentLine(true);
- clearUnusedImages();
- }
- void VMdEditor::saveFile()
- {
- Q_ASSERT(m_file->isModifiable());
- if (!document()->isModified()) {
- return;
- }
- m_file->setContent(toPlainText());
- setModified(false);
- clearUnusedImages();
- initInitImages();
- }
- void VMdEditor::reloadFile()
- {
- bool readonly = isReadOnly();
- setReadOnly(true);
- const QString &content = m_file->getContent();
- setPlainText(content);
- setModified(false);
- setReadOnly(readonly);
- if (!m_freshEdit) {
- m_freshEdit = true;
- refreshPreview();
- }
- }
- bool VMdEditor::scrollToBlock(int p_blockNumber)
- {
- QTextBlock block = document()->findBlockByNumber(p_blockNumber);
- if (block.isValid()) {
- VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
- moveCursor(QTextCursor::EndOfBlock);
- return true;
- }
- return false;
- }
- // Get the visual offset of a block.
- #define GETVISUALOFFSETY(x) (contentOffsetY() + (int)(x).y())
- void VMdEditor::makeBlockVisible(const QTextBlock &p_block)
- {
- if (!p_block.isValid() || !p_block.isVisible()) {
- return;
- }
- QScrollBar *vbar = verticalScrollBar();
- if (!vbar || (vbar->minimum() == vbar->maximum())) {
- // No vertical scrollbar. No need to scroll.
- return;
- }
- int height = rect().height();
- QScrollBar *hbar = horizontalScrollBar();
- if (hbar && (hbar->minimum() != hbar->maximum())) {
- height -= hbar->height();
- }
- bool moved = false;
- QAbstractTextDocumentLayout *layout = document()->documentLayout();
- QRectF rt = layout->blockBoundingRect(p_block);
- int y = GETVISUALOFFSETY(rt);
- int rectHeight = (int)rt.height();
- // Handle the case rectHeight >= height.
- if (rectHeight >= height) {
- if (y < 0) {
- // Need to scroll up.
- while (y + rectHeight < height && vbar->value() > vbar->minimum()) {
- moved = true;
- vbar->setValue(vbar->value() - vbar->singleStep());
- rt = layout->blockBoundingRect(p_block);
- rectHeight = (int)rt.height();
- y = GETVISUALOFFSETY(rt);
- }
- } else if (y > 0) {
- // Need to scroll down.
- while (y > 0 && vbar->value() < vbar->maximum()) {
- moved = true;
- vbar->setValue(vbar->value() + vbar->singleStep());
- rt = layout->blockBoundingRect(p_block);
- rectHeight = (int)rt.height();
- y = GETVISUALOFFSETY(rt);
- }
- if (y < 0) {
- // One step back.
- moved = true;
- vbar->setValue(vbar->value() - vbar->singleStep());
- }
- }
- return;
- }
- // There is an extra line leading in the layout, so there will always be a scroll
- // action to scroll the page down.
- while (y < 0 && vbar->value() > vbar->minimum()) {
- moved = true;
- vbar->setValue(vbar->value() - vbar->singleStep());
- rt = layout->blockBoundingRect(p_block);
- y = GETVISUALOFFSETY(rt);
- }
- if (moved) {
- return;
- }
- while (y + rectHeight > height && vbar->value() < vbar->maximum()) {
- vbar->setValue(vbar->value() + vbar->singleStep());
- rt = layout->blockBoundingRect(p_block);
- rectHeight = (int)rt.height();
- y = GETVISUALOFFSETY(rt);
- }
- }
- static QAction *getActionByObjectName(const QList<QAction *> &p_actions,
- const QString &p_objName)
- {
- for (auto act : p_actions) {
- if (act->objectName() == p_objName) {
- return act;
- }
- }
- return NULL;
- }
- // Insert @p_action into @p_menu after action @p_after.
- static void insertActionAfter(QAction *p_after, QAction *p_action, QMenu *p_menu)
- {
- p_menu->insertAction(p_after, p_action);
- if (p_after) {
- p_menu->removeAction(p_after);
- p_menu->insertAction(p_action, p_after);
- }
- }
- static QAction *insertMenuAfter(QAction *p_after, QMenu *p_subMenu, QMenu *p_menu)
- {
- QAction *menuAct = p_menu->insertMenu(p_after, p_subMenu);
- if (p_after) {
- p_menu->removeAction(p_after);
- p_menu->insertAction(menuAct, p_after);
- }
- return menuAct;
- }
- void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
- {
- if (!m_editTab || !m_editTab->isEditMode()) {
- return;
- }
- QScopedPointer<QMenu> menu(createStandardContextMenu());
- menu->setToolTipsVisible(true);
- QAction *copyAct, *pasteAct, *firstAct;
- {
- const QList<QAction *> actions = menu->actions();
- firstAct = actions.isEmpty() ? NULL : actions.first();
- copyAct = getActionByObjectName(actions, "edit-copy");
- pasteAct = getActionByObjectName(actions, "edit-paste");
- }
- if (copyAct && copyAct->isEnabled()) {
- initCopyAsMenu(copyAct, menu.data());
- }
- if (pasteAct && pasteAct->isEnabled()) {
- QClipboard *clipboard = QApplication::clipboard();
- const QMimeData *mimeData = clipboard->mimeData();
- if (mimeData->hasText()) {
- initPasteAsBlockQuoteMenu(pasteAct, menu.data());
- }
- if (mimeData->hasHtml()) {
- initPasteAfterParseMenu(pasteAct, menu.data());
- }
- QAction *pptAct = new QAction(tr("Paste As Plain Text"), menu.data());
- VUtils::fixTextWithShortcut(pptAct, "PastePlainText");
- connect(pptAct, &QAction::triggered,
- this, [this]() {
- pastePlainText();
- });
- insertActionAfter(pasteAct, pptAct, menu.data());
- }
- if (!textCursor().hasSelection()) {
- initLinkAndPreviewMenu(firstAct, menu.data(), p_event->pos());
- QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"),
- tr("&Save Changes And Read"),
- menu.data());
- saveExitAct->setToolTip(tr("Save changes and exit edit mode"));
- connect(saveExitAct, &QAction::triggered,
- this, [this]() {
- emit m_object->saveAndRead();
- });
- QAction *discardExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/discard_exit.svg"),
- tr("&Discard Changes And Read"),
- menu.data());
- discardExitAct->setToolTip(tr("Discard changes and exit edit mode"));
- connect(discardExitAct, &QAction::triggered,
- this, [this]() {
- emit m_object->discardAndRead();
- });
- VMdTab *mdtab = dynamic_cast<VMdTab *>(m_editTab);
- if (mdtab) {
- QAction *toggleLivePreviewAct = new QAction(tr("Live Preview For Graphs"), menu.data());
- toggleLivePreviewAct->setToolTip(tr("Toggle live preview panel for graphs"));
- VUtils::fixTextWithCaptainShortcut(toggleLivePreviewAct, "LivePreview");
- connect(toggleLivePreviewAct, &QAction::triggered,
- this, [this, mdtab]() {
- mdtab->toggleLivePreview();
- });
- menu->insertAction(firstAct, toggleLivePreviewAct);
- menu->insertAction(toggleLivePreviewAct, discardExitAct);
- menu->insertAction(discardExitAct, saveExitAct);
- menu->insertSeparator(toggleLivePreviewAct);
- } else {
- menu->insertAction(firstAct, discardExitAct);
- menu->insertAction(discardExitAct, saveExitAct);
- menu->insertSeparator(discardExitAct);
- }
- if (firstAct) {
- menu->insertSeparator(firstAct);
- }
- initAttachmentMenu(menu.data());
- }
- menu->exec(p_event->globalPos());
- }
- void VMdEditor::mousePressEvent(QMouseEvent *p_event)
- {
- if (handleMousePressEvent(p_event)) {
- return;
- }
- VTextEdit::mousePressEvent(p_event);
- emit m_object->mousePressed(p_event);
- }
- void VMdEditor::mouseDoubleClickEvent(QMouseEvent *p_event)
- {
- VTextEdit::mouseDoubleClickEvent(p_event);
- emit m_object->mouseDoubleClicked(p_event);
- }
- void VMdEditor::mouseReleaseEvent(QMouseEvent *p_event)
- {
- if (handleMouseReleaseEvent(p_event)) {
- return;
- }
- VTextEdit::mouseReleaseEvent(p_event);
- emit m_object->mouseReleased(p_event);
- }
- void VMdEditor::mouseMoveEvent(QMouseEvent *p_event)
- {
- if (handleMouseMoveEvent(p_event)) {
- return;
- }
- VTextEdit::mouseMoveEvent(p_event);
- emit m_object->mouseMoved(p_event);
- }
- QVariant VMdEditor::inputMethodQuery(Qt::InputMethodQuery p_query) const
- {
- QVariant ret;
- if (handleInputMethodQuery(p_query, ret)) {
- return ret;
- }
- return VTextEdit::inputMethodQuery(p_query);
- }
- bool VMdEditor::isBlockVisible(const QTextBlock &p_block)
- {
- if (!p_block.isValid() || !p_block.isVisible()) {
- return false;
- }
- QScrollBar *vbar = verticalScrollBar();
- if (!vbar || !vbar->isVisible()) {
- // No vertical scrollbar.
- return true;
- }
- int height = rect().height();
- QScrollBar *hbar = horizontalScrollBar();
- if (hbar && hbar->isVisible()) {
- height -= hbar->height();
- }
- QAbstractTextDocumentLayout *layout = document()->documentLayout();
- QRectF rt = layout->blockBoundingRect(p_block);
- int y = GETVISUALOFFSETY(rt);
- int rectHeight = (int)rt.height();
- return (y >= 0 && y < height) || (y < 0 && y + rectHeight > 0);
- }
- static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel)
- {
- Q_ASSERT(p_level >= 1 && p_level < p_sequence.size());
- if (p_level < p_baseLevel) {
- p_sequence.fill(0);
- return;
- }
- ++p_sequence[p_level];
- for (int i = p_level + 1; i < p_sequence.size(); ++i) {
- p_sequence[i] = 0;
- }
- }
- static QString headerSequenceStr(const QVector<int> &p_sequence)
- {
- QString res;
- for (int i = 1; i < p_sequence.size(); ++i) {
- if (p_sequence[i] != 0) {
- res = res + QString::number(p_sequence[i]) + '.';
- } else if (res.isEmpty()) {
- continue;
- } else {
- break;
- }
- }
- return res;
- }
- static void insertSequenceToHeader(QTextCursor& p_cursor,
- const QTextBlock &p_block,
- QRegExp &p_reg,
- QRegExp &p_preReg,
- const QString &p_seq)
- {
- if (!p_block.isValid()) {
- return;
- }
- QString text = p_block.text();
- bool matched = p_reg.exactMatch(text);
- Q_ASSERT(matched);
- matched = p_preReg.exactMatch(text);
- Q_ASSERT(matched);
- int start = p_reg.cap(1).length() + 1;
- int end = p_preReg.cap(1).length();
- Q_ASSERT(start <= end);
- p_cursor.setPosition(p_block.position() + start);
- if (start != end) {
- p_cursor.setPosition(p_block.position() + end, QTextCursor::KeepAnchor);
- }
- if (p_seq.isEmpty()) {
- p_cursor.removeSelectedText();
- } else {
- p_cursor.insertText(p_seq + ' ');
- }
- }
- void VMdEditor::updateHeaderSequenceByConfigChange()
- {
- updateHeadersHelper(m_pegHighlighter->getHeaderRegions(), true);
- }
- void VMdEditor::updateHeadersHelper(const QVector<VElementRegion> &p_headerRegions, bool p_configChanged)
- {
- QTextDocument *doc = document();
- QVector<VTableOfContentItem> headers;
- QVector<int> headerBlockNumbers;
- QVector<QString> headerSequences;
- if (!p_headerRegions.isEmpty()) {
- headers.reserve(p_headerRegions.size());
- headerBlockNumbers.reserve(p_headerRegions.size());
- headerSequences.reserve(p_headerRegions.size());
- }
- // Assume that each block contains only one line
- // Only support # syntax for now
- QRegExp headerReg(VUtils::c_headerRegExp);
- int baseLevel = -1;
- for (auto const & reg : p_headerRegions) {
- QTextBlock block = doc->findBlock(reg.m_startPos);
- if (!block.isValid()) {
- continue;
- }
- if (!block.contains(reg.m_endPos - 1)) {
- qWarning() << "header accross multiple blocks, starting from block"
- << block.blockNumber()
- << block.text();
- }
- if (headerReg.exactMatch(block.text())) {
- int level = headerReg.cap(1).length();
- VTableOfContentItem header(headerReg.cap(2).trimmed(),
- level,
- block.blockNumber(),
- headers.size());
- headers.append(header);
- headerBlockNumbers.append(block.blockNumber());
- headerSequences.append(headerReg.cap(3));
- if (baseLevel == -1) {
- baseLevel = level;
- } else if (baseLevel > level) {
- baseLevel = level;
- }
- }
- }
- m_headers.clear();
- bool autoSequence = m_config.m_enableHeadingSequence
- && !isReadOnly()
- && m_file->isModifiable();
- int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
- if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
- headingSequenceBaseLevel = 1;
- }
- QVector<int> seqs(7, 0);
- QRegExp preReg(VUtils::c_headerPrefixRegExp);
- int curLevel = baseLevel - 1;
- QTextCursor cursor(doc);
- if(autoSequence || p_configChanged) {
- cursor.beginEditBlock();
- }
- for (int i = 0; i < headers.size(); ++i) {
- VTableOfContentItem &item = headers[i];
- while (item.m_level > curLevel + 1) {
- curLevel += 1;
- // Insert empty level which is an invalid header.
- m_headers.append(VTableOfContentItem(c_emptyHeaderName,
- curLevel,
- -1,
- m_headers.size()));
- if (autoSequence || p_configChanged) {
- addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
- }
- }
- item.m_index = m_headers.size();
- m_headers.append(item);
- curLevel = item.m_level;
- if (autoSequence || p_configChanged) {
- addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
- QString seqStr = autoSequence ? headerSequenceStr(seqs) : "";
- if (headerSequences[i] != seqStr) {
- // Insert correct sequence.
- insertSequenceToHeader(cursor,
- doc->findBlockByNumber(headerBlockNumbers[i]),
- headerReg,
- preReg,
- seqStr);
- }
- }
- }
- if (autoSequence || p_configChanged) {
- cursor.endEditBlock();
- }
- emit headersChanged(m_headers);
- updateCurrentHeader();
- }
- void VMdEditor::updateHeaders(const QVector<VElementRegion> &p_headerRegions)
- {
- updateHeadersHelper(p_headerRegions, false);
- }
- void VMdEditor::updateCurrentHeader()
- {
- emit currentHeaderChanged(textCursor().block().blockNumber());
- }
- void VMdEditor::initInitImages()
- {
- m_initImages = VUtils::fetchImagesFromMarkdownFile(m_file,
- ImageLink::LocalRelativeInternal);
- }
- void VMdEditor::clearUnusedImages()
- {
- QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
- ImageLink::LocalRelativeInternal);
- QSet<QString> unusedImages;
- if (!m_insertedImages.isEmpty()) {
- for (int i = 0; i < m_insertedImages.size(); ++i) {
- const ImageLink &link = m_insertedImages[i];
- if (link.m_type != ImageLink::LocalRelativeInternal) {
- continue;
- }
- int j;
- for (j = 0; j < images.size(); ++j) {
- if (VUtils::equalPath(link.m_path, images[j].m_path)) {
- break;
- }
- }
- // This inserted image is no longer in the file.
- if (j == images.size()) {
- unusedImages.insert(link.m_path);
- }
- }
- m_insertedImages.clear();
- }
- for (int i = 0; i < m_initImages.size(); ++i) {
- const ImageLink &link = m_initImages[i];
- V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
- int j;
- for (j = 0; j < images.size(); ++j) {
- if (VUtils::equalPath(link.m_path, images[j].m_path)) {
- break;
- }
- }
- // Original local relative image is no longer in the file.
- if (j == images.size()) {
- unusedImages.insert(link.m_path);
- }
- }
- if (!unusedImages.isEmpty()) {
- if (g_config->getConfirmImagesCleanUp()) {
- QVector<ConfirmItemInfo> items;
- for (auto const & img : unusedImages) {
- items.push_back(ConfirmItemInfo(img,
- img,
- img,
- NULL));
- }
- QString text = tr("Following images seems not to be used in this note anymore. "
- "Please confirm the deletion of these images.");
- QString info = tr("Deleted files could be found in the recycle "
- "bin of this note.<br>"
- "Click \"Cancel\" to leave them untouched.");
- VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
- text,
- info,
- items,
- true,
- true,
- true,
- this);
- unusedImages.clear();
- if (dialog.exec()) {
- items = dialog.getConfirmedItems();
- g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
- for (auto const & item : items) {
- unusedImages.insert(item.m_name);
- }
- }
- }
- for (auto const & item : unusedImages) {
- bool ret = false;
- if (m_file->getType() == FileType::Note) {
- const VNoteFile *tmpFile = static_cast<const VNoteFile *>((VFile *)m_file);
- ret = VUtils::deleteFile(tmpFile->getNotebook(), item, false);
- } else if (m_file->getType() == FileType::Orphan) {
- const VOrphanFile *tmpFile = static_cast<const VOrphanFile *>((VFile *)m_file);
- ret = VUtils::deleteFile(tmpFile, item, false);
- } else {
- Q_ASSERT(false);
- }
- if (!ret) {
- qWarning() << "fail to delete unused original image" << item;
- } else {
- qDebug() << "delete unused image" << item;
- }
- }
- }
- m_initImages.clear();
- }
- void VMdEditor::keyPressEvent(QKeyEvent *p_event)
- {
- int key = p_event->key();
- int modifiers = p_event->modifiers();
- switch (key) {
- case Qt::Key_Minus:
- case Qt::Key_Underscore:
- // Zoom out.
- if (modifiers & Qt::ControlModifier) {
- zoomPage(false);
- return;
- }
- break;
- case Qt::Key_Plus:
- case Qt::Key_Equal:
- // Zoom in.
- if (modifiers & Qt::ControlModifier) {
- zoomPage(true);
- return;
- }
- break;
- case Qt::Key_0:
- // Restore zoom.
- if (modifiers & Qt::ControlModifier) {
- if (m_zoomDelta > 0) {
- zoomPage(false, m_zoomDelta);
- } else if (m_zoomDelta < 0) {
- zoomPage(true, -m_zoomDelta);
- }
- return;
- }
- break;
- default:
- break;
- }
- if (m_editOps && m_editOps->handleKeyPressEvent(p_event)) {
- return;
- }
- // Esc to exit edit mode when Vim is disabled.
- if (key == Qt::Key_Escape) {
- emit m_object->discardAndRead();
- return;
- }
- VTextEdit::keyPressEvent(p_event);
- }
- bool VMdEditor::canInsertFromMimeData(const QMimeData *p_source) const
- {
- return p_source->hasImage()
- || p_source->hasUrls()
- || VTextEdit::canInsertFromMimeData(p_source);
- }
- void VMdEditor::insertFromMimeData(const QMimeData *p_source)
- {
- if (processHtmlFromMimeData(p_source)) {
- return;
- }
- if (processImageFromMimeData(p_source)) {
- return;
- }
- if (processUrlFromMimeData(p_source)) {
- return;
- }
- VTextEdit::insertFromMimeData(p_source);
- }
- void VMdEditor::imageInserted(const QString &p_path, const QString &p_url)
- {
- ImageLink link;
- link.m_path = p_path;
- link.m_url = p_url;
- if (m_file->useRelativeImageFolder()) {
- link.m_type = ImageLink::LocalRelativeInternal;
- } else {
- link.m_type = ImageLink::LocalAbsolute;
- }
- m_insertedImages.append(link);
- }
- bool VMdEditor::scrollToHeader(int p_blockNumber)
- {
- if (p_blockNumber < 0) {
- return false;
- }
- return scrollToBlock(p_blockNumber);
- }
- int VMdEditor::indexOfCurrentHeader() const
- {
- if (m_headers.isEmpty()) {
- return -1;
- }
- int blockNumber = textCursor().block().blockNumber();
- for (int i = m_headers.size() - 1; i >= 0; --i) {
- if (!m_headers[i].isEmpty()
- && m_headers[i].m_blockNumber <= blockNumber) {
- return i;
- }
- }
- return -1;
- }
- bool VMdEditor::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
- {
- if (m_headers.isEmpty()) {
- return false;
- }
- QTextCursor cursor = textCursor();
- int cursorLine = cursor.block().blockNumber();
- int targetIdx = -1;
- // -1: skip level check.
- int targetLevel = 0;
- int idx = indexOfCurrentHeader();
- if (idx == -1) {
- // Cursor locates at the beginning, before any headers.
- if (p_relativeLevel < 0 || !p_forward) {
- return false;
- }
- }
- int delta = 1;
- if (!p_forward) {
- delta = -1;
- }
- bool firstHeader = true;
- for (targetIdx = idx == -1 ? 0 : idx;
- targetIdx >= 0 && targetIdx < m_headers.size();
- targetIdx += delta) {
- const VTableOfContentItem &header = m_headers[targetIdx];
- if (header.isEmpty()) {
- continue;
- }
- if (targetLevel == 0) {
- // The target level has not been init yet.
- Q_ASSERT(firstHeader);
- targetLevel = header.m_level;
- if (p_relativeLevel < 0) {
- targetLevel += p_relativeLevel;
- if (targetLevel < 1) {
- // Invalid level.
- return false;
- }
- } else if (p_relativeLevel > 0) {
- targetLevel = -1;
- }
- }
- if (targetLevel == -1 || header.m_level == targetLevel) {
- if (firstHeader
- && (cursorLine == header.m_blockNumber
- || p_forward)
- && idx != -1) {
- // This header is not counted for the repeat.
- firstHeader = false;
- continue;
- }
- if (--p_repeat == 0) {
- // Found.
- break;
- }
- } else if (header.m_level < targetLevel) {
- // Stop by higher level.
- return false;
- }
- firstHeader = false;
- }
- if (targetIdx < 0 || targetIdx >= m_headers.size()) {
- return false;
- }
- // Jump to target header.
- int line = m_headers[targetIdx].m_blockNumber;
- if (line > -1) {
- QTextBlock block = document()->findBlockByNumber(line);
- if (block.isValid()) {
- cursor.setPosition(block.position());
- setTextCursor(cursor);
- return true;
- }
- }
- return false;
- }
- void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest)
- {
- VEditUtils::scrollBlockInPage(this, p_blockNum, p_dest);
- }
- void VMdEditor::updateTextEditConfig()
- {
- setBlockImageEnabled(g_config->getEnablePreviewImages());
- setImageWidthConstrainted(g_config->getEnablePreviewImageConstraint());
- setLineLeading(m_config.m_lineDistanceHeight);
- setImageLineColor(g_config->getEditorPreviewImageLineFg());
- int lineNumber = g_config->getEditorLineNumber();
- if (lineNumber < (int)LineNumberType::None || lineNumber >= (int)LineNumberType::Invalid) {
- lineNumber = (int)LineNumberType::None;
- }
- setLineNumberType((LineNumberType)lineNumber);
- setLineNumberColor(g_config->getEditorLineNumberFg(),
- g_config->getEditorLineNumberBg());
- m_previewMgr->setPreviewEnabled(g_config->getEnablePreviewImages());
- }
- void VMdEditor::updateConfig()
- {
- updateEditConfig();
- updateTextEditConfig();
- }
- QString VMdEditor::getContent() const
- {
- return toPlainText();
- }
- void VMdEditor::setContent(const QString &p_content, bool p_modified)
- {
- if (p_modified) {
- QTextCursor cursor = textCursor();
- cursor.select(QTextCursor::Document);
- cursor.insertText(p_content);
- setTextCursor(cursor);
- } else {
- setPlainText(p_content);
- }
- }
- void VMdEditor::refreshPreview()
- {
- m_previewMgr->refreshPreview();
- }
- void VMdEditor::updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_act)
- {
- if (p_fileChanged && p_act == UpdateAction::InfoChanged) {
- return;
- }
- if (!isModified()) {
- Q_ASSERT(m_insertedImages.isEmpty());
- m_insertedImages.clear();
- if (!m_initImages.isEmpty()) {
- // Re-generate init images.
- initInitImages();
- }
- return;
- }
- // Update init images.
- QVector<ImageLink> tmp = m_initImages;
- initInitImages();
- Q_ASSERT(tmp.size() == m_initImages.size());
- QDir dir(m_file->fetchBasePath());
- // File has been moved.
- if (p_fileChanged) {
- // Since we clear unused images once user save the note, all images
- // in m_initImages now are moved already.
- // Update inserted images.
- // Inserted images should be moved manually here. Then update all the
- // paths.
- for (auto & link : m_insertedImages) {
- if (link.m_type == ImageLink::LocalAbsolute) {
- continue;
- }
- QString newPath = QDir::cleanPath(dir.absoluteFilePath(link.m_url));
- if (VUtils::equalPath(link.m_path, newPath)) {
- continue;
- }
- if (!VUtils::copyFile(link.m_path, newPath, true)) {
- VUtils::showMessage(QMessageBox::Warning,
- tr("Warning"),
- tr("Fail to move unsaved inserted image %1 to %2.")
- .arg(link.m_path)
- .arg(newPath),
- tr("Please check it manually to avoid image loss."),
- QMessageBox::Ok,
- QMessageBox::Ok,
- this);
- continue;
- }
- link.m_path = newPath;
- }
- } else {
- // Directory changed.
- // Update inserted images.
- for (auto & link : m_insertedImages) {
- if (link.m_type == ImageLink::LocalAbsolute) {
- continue;
- }
- QString newPath = QDir::cleanPath(dir.absoluteFilePath(link.m_url));
- link.m_path = newPath;
- }
- }
- }
- void VMdEditor::handleCopyAsAction(QAction *p_act)
- {
- ++m_copyTimeStamp;
- QTextCursor cursor = textCursor();
- Q_ASSERT(cursor.hasSelection());
- QString text = VEditUtils::selectedText(cursor);
- Q_ASSERT(!text.isEmpty());
- Q_ASSERT(!m_textToHtmlDialog);
- m_textToHtmlDialog = new VCopyTextAsHtmlDialog(text, p_act->data().toString(), this);
- // For Hoedown, we use marked.js to convert the text to have a general interface.
- emit requestTextToHtml(text, 0, m_copyTimeStamp);
- m_textToHtmlDialog->exec();
- delete m_textToHtmlDialog;
- m_textToHtmlDialog = NULL;
- }
- void VMdEditor::textToHtmlFinished(int p_id,
- int p_timeStamp,
- const QUrl &p_baseUrl,
- const QString &p_html)
- {
- Q_UNUSED(p_id);
- if (m_textToHtmlDialog && p_timeStamp == m_copyTimeStamp) {
- m_textToHtmlDialog->setConvertedHtml(p_baseUrl, p_html);
- }
- }
- void VMdEditor::htmlToTextFinished(int p_id, int p_timeStamp, const QString &p_text)
- {
- Q_UNUSED(p_id);
- if (m_copyTimeStamp == p_timeStamp && !p_text.isEmpty()) {
- emit m_object->statusMessage(tr("Inserting parsed Markdown text"));
- QString text(p_text);
- if (g_config->getParsePasteLocalImage()) {
- // May take long time.
- replaceTextWithLocalImages(text);
- }
- m_editOps->insertText(text);
- emit m_object->statusMessage(tr("Parsed Markdown text inserted"));
- }
- }
- void VMdEditor::wheelEvent(QWheelEvent *p_event)
- {
- if (handleWheelEvent(p_event)) {
- return;
- }
- VTextEdit::wheelEvent(p_event);
- }
- void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
- {
- const int minSize = 2;
- int delta = p_zoomIn ? p_range : -p_range;
- // zoomIn() and zoomOut() does not work if we set stylesheet of VMdEditor.
- int ptSz = font().pointSize() + delta;
- if (ptSz < minSize) {
- ptSz = minSize;
- }
- setFontPointSizeByStyleSheet(ptSz);
- emit m_object->statusMessage(QObject::tr("Set base font point size %1").arg(ptSz));
- m_zoomDelta += delta;
- QVector<HighlightingStyle> &styles = m_pegHighlighter->getStyles();
- for (auto & it : styles) {
- int size = it.format.fontPointSize();
- if (size == 0) {
- // It contains no font size format.
- continue;
- }
- size += delta;
- if (size < minSize) {
- size = minSize;
- }
- it.format.setFontPointSize(size);
- }
- QHash<QString, QTextCharFormat> &cbStyles = m_pegHighlighter->getCodeBlockStyles();
- for (auto it = cbStyles.begin(); it != cbStyles.end(); ++it) {
- int size = it.value().fontPointSize();
- if (size == 0) {
- // It contains no font size format.
- continue;
- }
- size += delta;
- if (size < minSize) {
- size = minSize;
- }
- it.value().setFontPointSize(size);
- }
- m_pegHighlighter->rehighlight();
- }
- QAction *VMdEditor::initCopyAsMenu(QAction *p_after, QMenu *p_menu)
- {
- QStringList targets = g_webUtils->getCopyTargetsName();
- if (targets.isEmpty()) {
- return NULL;
- }
- QMenu *subMenu = new QMenu(tr("Copy HTML As"), p_menu);
- subMenu->setToolTipsVisible(true);
- for (auto const & target : targets) {
- QAction *act = new QAction(target, subMenu);
- act->setData(target);
- act->setToolTip(tr("Copy selected content as HTML using rules specified by target %1").arg(target));
- subMenu->addAction(act);
- }
- connect(subMenu, &QMenu::triggered,
- this, &VMdEditor::handleCopyAsAction);
- return insertMenuAfter(p_after, subMenu, p_menu);
- }
- QAction *VMdEditor::initPasteAsBlockQuoteMenu(QAction *p_after, QMenu *p_menu)
- {
- QAction *pbqAct = new QAction(tr("Paste As Block &Quote"), p_menu);
- pbqAct->setToolTip(tr("Paste text from clipboard as block quote"));
- connect(pbqAct, &QAction::triggered,
- this, [this]() {
- QClipboard *clipboard = QApplication::clipboard();
- const QMimeData *mimeData = clipboard->mimeData();
- QString text = mimeData->text();
- QTextCursor cursor = textCursor();
- cursor.removeSelectedText();
- QTextBlock block = cursor.block();
- QString indent = VEditUtils::fetchIndentSpaces(block);
- // Insert '> ' in front of each line.
- VEditUtils::insertBeforeEachLine(text, indent + QStringLiteral("> "));
- if (VEditUtils::isSpaceBlock(block)) {
- if (!indent.isEmpty()) {
- // Remove the indent.
- cursor.movePosition(QTextCursor::StartOfBlock);
- cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
- cursor.removeSelectedText();
- }
- } else {
- // Insert a new block.
- VEditUtils::insertBlock(cursor, false);
- }
- cursor.insertText(text);
- setTextCursor(cursor);
- });
- insertActionAfter(p_after, pbqAct, p_menu);
- return pbqAct;
- }
- QAction *VMdEditor::initPasteAfterParseMenu(QAction *p_after, QMenu *p_menu)
- {
- QAction *papAct = new QAction(tr("Paste Parsed &Markdown Text"), p_menu);
- VUtils::fixTextWithCaptainShortcut(papAct, "ParseAndPaste");
- papAct->setToolTip(tr("Parse HTML to Markdown text and paste"));
- connect(papAct, &QAction::triggered,
- this, &VMdEditor::parseAndPaste);
- insertActionAfter(p_after, papAct, p_menu);
- return papAct;
- }
- void VMdEditor::insertImageLink(const QString &p_text, const QString &p_url)
- {
- VInsertLinkDialog dialog(tr("Insert Image Link"),
- "",
- "",
- p_text,
- p_url,
- true,
- this);
- if (dialog.exec() == QDialog::Accepted) {
- QString linkText = dialog.getLinkText();
- QString linkUrl = dialog.getLinkUrl();
- static_cast<VMdEditOperations *>(m_editOps)->insertImageLink(linkText, linkUrl);
- }
- }
- VWordCountInfo VMdEditor::fetchWordCountInfo() const
- {
- VWordCountInfo info;
- QTextDocument *doc = document();
- // Char without spaces.
- int cns = 0;
- int wc = 0;
- // Remove th ending new line.
- int cc = doc->characterCount() - 1;
- // 0 - not in word;
- // 1 - in English word;
- // 2 - in non-English word;
- int state = 0;
- for (int i = 0; i < cc; ++i) {
- QChar ch = doc->characterAt(i);
- if (ch.isSpace()) {
- if (state) {
- state = 0;
- }
- continue;
- } else if (ch.unicode() < 128) {
- if (state != 1) {
- state = 1;
- ++wc;
- }
- } else {
- state = 2;
- ++wc;
- }
- ++cns;
- }
- info.m_mode = VWordCountInfo::Edit;
- info.m_wordCount = wc;
- info.m_charWithoutSpacesCount = cns;
- info.m_charWithSpacesCount = cc;
- return info;
- }
- void VMdEditor::setEditTab(VEditTab *p_editTab)
- {
- m_editTab = p_editTab;
- }
- void VMdEditor::setFontPointSizeByStyleSheet(int p_ptSize)
- {
- QFont ft = font();
- ft.setPointSize(p_ptSize);
- const QPalette &palette = g_config->getMdEditPalette();
- setFontAndPaletteByStyleSheet(ft, palette);
- ensurePolished();
- ft.setPointSize(p_ptSize + LINE_NUMBER_AREA_FONT_DELTA);
- updateLineNumberAreaWidth(QFontMetrics(ft));
- }
- void VMdEditor::setFontAndPaletteByStyleSheet(const QFont &p_font, const QPalette &p_palette)
- {
- QString styles(QString("VMdEditor, VLineNumberArea {"
- "font-family: \"%1\";"
- "font-size: %2pt;"
- "color: %3;"
- "background-color: %4; } "
- "VLineNumberArea {"
- "font-size: %5pt; }")
- .arg(p_font.family())
- .arg(p_font.pointSize())
- .arg(p_palette.color(QPalette::Text).name())
- .arg(p_palette.color(QPalette::Base).name())
- .arg(p_font.pointSize() + LINE_NUMBER_AREA_FONT_DELTA));
- setStyleSheet(styles);
- }
- int VMdEditor::lineNumberAreaWidth() const
- {
- return VTextEdit::lineNumberAreaWidth();
- }
- void VMdEditor::initLinkAndPreviewMenu(QAction *p_before, QMenu *p_menu, const QPoint &p_pos)
- {
- QTextCursor cursor = cursorForPosition(p_pos);
- const int pos = cursor.position();
- QTextBlock block = cursor.block();
- const QString text(block.text());
- // Image.
- QRegExp regExp(VUtils::c_imageLinkRegExp);
- if (regExp.indexIn(text) > -1) {
- const QVector<VElementRegion> &imgRegs = m_pegHighlighter->getImageRegions();
- for (auto const & reg : imgRegs) {
- if (!reg.contains(pos)) {
- continue;
- }
- if (reg.m_endPos > block.position() + text.length()) {
- return;
- }
- QString linkText = text.mid(reg.m_startPos - block.position(),
- reg.m_endPos - reg.m_startPos);
- QString surl = VUtils::fetchImageLinkUrl(linkText);
- if (surl.isEmpty()) {
- return;
- }
- QString imgPath = VUtils::linkUrlToPath(m_file->fetchBasePath(), surl);
- bool isLocalFile = QFileInfo::exists(imgPath);
- QAction *viewImageAct = new QAction(tr("View Image"), p_menu);
- connect(viewImageAct, &QAction::triggered,
- this, [this, imgPath]() {
- QDesktopServices::openUrl(VUtils::pathToUrl(imgPath));
- });
- p_menu->insertAction(p_before, viewImageAct);
- QAction *copyImageLinkAct = new QAction(tr("Copy Image URL"), p_menu);
- connect(copyImageLinkAct, &QAction::triggered,
- this, [this, imgPath]() {
- QClipboard *clipboard = QApplication::clipboard();
- VClipboardUtils::setLinkToClipboard(clipboard,
- imgPath,
- QClipboard::Clipboard);
- });
- p_menu->insertAction(p_before, copyImageLinkAct);
- if (isLocalFile) {
- QAction *copyImagePathAct = new QAction(tr("Copy Image Path"), p_menu);
- connect(copyImagePathAct, &QAction::triggered,
- this, [this, imgPath]() {
- QClipboard *clipboard = QApplication::clipboard();
- QMimeData *data = new QMimeData();
- data->setText(imgPath);
- VClipboardUtils::setMimeDataToClipboard(clipboard,
- data,
- QClipboard::Clipboard);
- });
- p_menu->insertAction(p_before, copyImagePathAct);
- QAction *copyImageAct = new QAction(tr("Copy Image"), p_menu);
- connect(copyImageAct, &QAction::triggered,
- this, [this, imgPath]() {
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->clear();
- QImage img = VUtils::imageFromFile(imgPath);
- if (!img.isNull()) {
- VClipboardUtils::setImageToClipboard(clipboard,
- img,
- QClipboard::Clipboard);
- }
- });
- p_menu->insertAction(p_before, copyImageAct);
- } else {
- // Copy in-place preview.
- initInPlacePreviewMenu(p_before, p_menu, block, pos);
- }
- p_menu->insertSeparator(p_before);
- return;
- }
- }
- // Link.
- QRegExp regExp2(VUtils::c_linkRegExp);
- QString linkText;
- int p = 0;
- const int pib = pos - block.position();
- while (p < text.size()) {
- int idx = text.indexOf(regExp2, p);
- if (idx == -1) {
- break;
- }
- p = idx + regExp2.matchedLength();
- if (pib >= idx && pib < p) {
- linkText = regExp2.cap(2);
- break;
- }
- }
- if (!linkText.isEmpty()) {
- QString linkUrl = VUtils::linkUrlToPath(m_file->fetchBasePath(), linkText);
- bool isLocalFile = QFileInfo::exists(linkUrl);
- QAction *viewLinkAct = new QAction(tr("View Link"), p_menu);
- connect(viewLinkAct, &QAction::triggered,
- this, [this, linkUrl]() {
- QDesktopServices::openUrl(VUtils::pathToUrl(linkUrl));
- });
- p_menu->insertAction(p_before, viewLinkAct);
- QAction *copyLinkAct = new QAction(tr("Copy Link URL"), p_menu);
- connect(copyLinkAct, &QAction::triggered,
- this, [this, linkUrl]() {
- QClipboard *clipboard = QApplication::clipboard();
- VClipboardUtils::setLinkToClipboard(clipboard,
- linkUrl,
- QClipboard::Clipboard);
- });
- p_menu->insertAction(p_before, copyLinkAct);
- if (isLocalFile) {
- QAction *copyLinkPathAct = new QAction(tr("Copy Link Path"), p_menu);
- connect(copyLinkPathAct, &QAction::triggered,
- this, [this, linkUrl]() {
- QClipboard *clipboard = QApplication::clipboard();
- QMimeData *data = new QMimeData();
- data->setText(linkUrl);
- VClipboardUtils::setMimeDataToClipboard(clipboard,
- data,
- QClipboard::Clipboard);
- });
- p_menu->insertAction(p_before, copyLinkPathAct);
- }
- p_menu->insertSeparator(p_before);
- return;
- }
- bool needSeparator = false;
- if (initInPlacePreviewMenu(p_before, p_menu, block, pos)) {
- needSeparator = true;
- }
- if (initExportAndCopyMenu(p_before, p_menu, block, pos)) {
- needSeparator = true;
- }
- if (needSeparator) {
- p_menu->insertSeparator(p_before);
- }
- }
- bool VMdEditor::initInPlacePreviewMenu(QAction *p_before,
- QMenu *p_menu,
- const QTextBlock &p_block,
- int p_pos)
- {
- VTextBlockData *data = VTextBlockData::blockData(p_block);
- if (!data) {
- return false;
- }
- const QVector<VPreviewInfo *> &previews = data->getPreviews();
- if (previews.isEmpty()) {
- return false;
- }
- QPixmap image;
- QString background;
- int pib = p_pos - p_block.position();
- for (auto info : previews) {
- const VPreviewedImageInfo &pii = info->m_imageInfo;
- if (pii.contains(pib)) {
- const QPixmap *img = findImage(pii.m_imageName);
- if (img) {
- image = *img;
- background = pii.m_background;
- }
- break;
- }
- }
- if (image.isNull()) {
- return false;
- }
- QAction *copyImageAct = new QAction(tr("Copy In-Place Preview"), p_menu);
- connect(copyImageAct, &QAction::triggered,
- this, [this, image, background]() {
- QColor co(background);
- if (!co.isValid()) {
- co = palette().color(QPalette::Base);
- }
- QImage img(image.size(), QImage::Format_ARGB32);
- img.fill(co);
- QPainter pter(&img);
- pter.drawPixmap(img.rect(), image);
- QClipboard *clipboard = QApplication::clipboard();
- VClipboardUtils::setImageToClipboard(clipboard,
- img,
- QClipboard::Clipboard);
- });
- p_menu->insertAction(p_before, copyImageAct);
- return true;
- }
- bool VMdEditor::initExportAndCopyMenu(QAction *p_before,
- QMenu *p_menu,
- const QTextBlock &p_block,
- int p_pos)
- {
- Q_UNUSED(p_pos);
- int state = p_block.userState();
- if (state != HighlightBlockState::CodeBlockStart
- && state != HighlightBlockState::CodeBlock
- && state != HighlightBlockState::CodeBlockEnd) {
- return false;
- }
- int blockNum = p_block.blockNumber();
- const QVector<VCodeBlock> &cbs = m_pegHighlighter->getCodeBlocks();
- int idx = 0;
- for (idx = 0; idx < cbs.size(); ++idx) {
- if (cbs[idx].m_startBlock <= blockNum
- && cbs[idx].m_endBlock >= blockNum) {
- break;
- }
- }
- if (idx >= cbs.size()) {
- return false;
- }
- const VCodeBlock &cb = cbs[idx];
- if (cb.m_lang != "puml" && cb.m_lang != "dot") {
- return false;
- }
- QMenu *subMenu = new QMenu(tr("Copy Graph"), p_menu);
- subMenu->setToolTipsVisible(true);
- QAction *pngAct = new QAction(tr("PNG"), subMenu);
- pngAct->setToolTip(tr("Export graph as PNG to a temporary file and copy"));
- connect(pngAct, &QAction::triggered,
- this, [this, lang = cb.m_lang, text = cb.m_text]() {
- exportGraphAndCopy(lang, text, "png");
- });
- subMenu->addAction(pngAct);
- QAction *svgAct = new QAction(tr("SVG"), subMenu);
- svgAct->setToolTip(tr("Export graph as SVG to a temporary file and copy"));
- connect(svgAct, &QAction::triggered,
- this, [this, lang = cb.m_lang, text = cb.m_text]() {
- exportGraphAndCopy(lang, text, "svg");
- });
- subMenu->addAction(svgAct);
- p_menu->insertMenu(p_before, subMenu);
- return true;
- }
- void VMdEditor::exportGraphAndCopy(const QString &p_lang,
- const QString &p_text,
- const QString &p_format)
- {
- m_exportTempFile.reset(VUtils::createTemporaryFile(p_format));
- if (!m_exportTempFile->open()) {
- VUtils::showMessage(QMessageBox::Warning,
- tr("Warning"),
- tr("Fail to open a temporary file for export."),
- "",
- QMessageBox::Ok,
- QMessageBox::Ok,
- this);
- m_exportTempFile.clear();
- return;
- }
- emit m_object->statusMessage(tr("Exporting graph"));
- QString filePath(m_exportTempFile->fileName());
- QByteArray out;
- if (p_lang == "puml") {
- out = VPlantUMLHelper::process(p_format,
- VEditUtils::removeCodeBlockFence(p_text));
- } else if (p_lang == "dot") {
- out = VGraphvizHelper::process(p_format,
- VEditUtils::removeCodeBlockFence(p_text));
- }
- if (out.isEmpty() || m_exportTempFile->write(out) == -1) {
- m_exportTempFile->close();
- VUtils::showMessage(QMessageBox::Warning,
- tr("Warning"),
- tr("Fail to export graph."),
- "",
- QMessageBox::Ok,
- QMessageBox::Ok,
- this);
- } else {
- m_exportTempFile->close();
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->clear();
- QImage img;
- img.loadFromData(out, p_format.toLocal8Bit().data());
- if (!img.isNull()) {
- VClipboardUtils::setImageAndLinkToClipboard(clipboard,
- img,
- filePath,
- QClipboard::Clipboard);
- emit m_object->statusMessage(tr("Graph exported and copied"));
- } else {
- emit m_object->statusMessage(tr("Fail to read exported image: %1").arg(filePath));
- }
- }
- }
- void VMdEditor::parseAndPaste()
- {
- if (!m_editTab
- || !m_editTab->isEditMode()
- || isReadOnly()) {
- return;
- }
- QClipboard *clipboard = QApplication::clipboard();
- const QMimeData *mimeData = clipboard->mimeData();
- QString html(mimeData->html());
- if (!html.isEmpty()) {
- ++m_copyTimeStamp;
- emit requestHtmlToText(html, 0, m_copyTimeStamp);
- }
- }
- bool VMdEditor::processHtmlFromMimeData(const QMimeData *p_source)
- {
- if (!p_source->hasHtml()) {
- return false;
- }
- // Handle <img>.
- QRegExp reg("<img ([^>]*)src=\"([^\"]+)\"([^>]*)>");
- QString html(p_source->html());
- if (reg.indexIn(html) != -1 && VUtils::onlyHasImgInHtml(html)) {
- if (p_source->hasImage()) {
- // Both image data and URL are embedded.
- VSelectDialog dialog(tr("Insert From Clipboard"), this);
- dialog.addSelection(tr("Insert From URL"), 0);
- dialog.addSelection(tr("Insert From Image Data"), 1);
- dialog.addSelection(tr("Insert As Image Link"), 2);
- if (dialog.exec() == QDialog::Accepted) {
- int selection = dialog.getSelection();
- if (selection == 1) {
- // Insert from image data.
- m_editOps->insertImageFromMimeData(p_source);
- return true;
- } else if (selection == 2) {
- // Insert as link.
- insertImageLink("", reg.cap(2));
- return true;
- }
- } else {
- return true;
- }
- }
- m_editOps->insertImageFromURL(QUrl(reg.cap(2)));
- return true;
- }
- return false;
- }
- bool VMdEditor::processImageFromMimeData(const QMimeData *p_source)
- {
- if (!p_source->hasImage()) {
- return false;
- }
- // Image data in the clipboard
- if (p_source->hasText()) {
- VSelectDialog dialog(tr("Insert From Clipboard"), this);
- dialog.addSelection(tr("Insert As Image"), 0);
- dialog.addSelection(tr("Insert As Text"), 1);
- dialog.addSelection(tr("Insert As Image Link"), 2);
- if (dialog.exec() == QDialog::Accepted) {
- int selection = dialog.getSelection();
- if (selection == 1) {
- // Insert as text.
- Q_ASSERT(p_source->hasText() && p_source->hasImage());
- VTextEdit::insertFromMimeData(p_source);
- return true;
- } else if (selection == 2) {
- // Insert as link.
- insertImageLink("", p_source->text());
- return true;
- }
- } else {
- return true;
- }
- }
- m_editOps->insertImageFromMimeData(p_source);
- return true;
- }
- bool VMdEditor::processUrlFromMimeData(const QMimeData *p_source)
- {
- QUrl url;
- if (p_source->hasUrls()) {
- QList<QUrl> urls = p_source->urls();
- if (urls.size() == 1) {
- url = urls[0];
- }
- } else if (p_source->hasText()) {
- // Try to get URL from text.
- QString text = p_source->text();
- if (QFileInfo::exists(text)) {
- url = QUrl::fromLocalFile(text);
- } else {
- url = QUrl(text);
- if (url.scheme() != "https" && url.scheme() != "http") {
- url.clear();
- }
- }
- }
- if (!url.isValid()) {
- return false;
- }
- bool isImage = VUtils::isImageURL(url);
- bool isLocalFile = url.isLocalFile()
- && QFileInfo::exists(url.toLocalFile());
- QString localTextFilePath;
- if (!isImage && isLocalFile) {
- localTextFilePath = url.toLocalFile();
- QMimeDatabase mimeDatabase;
- const QMimeType mimeType = mimeDatabase.mimeTypeForFile(localTextFilePath);
- if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) {
- localTextFilePath.clear();
- }
- }
- VSelectDialog dialog(tr("Insert From Clipboard"), this);
- if (isImage) {
- dialog.addSelection(tr("Insert As Image"), 0);
- dialog.addSelection(tr("Insert As Image Link"), 1);
- }
- dialog.addSelection(tr("Insert As Link"), 2);
- if (isLocalFile) {
- dialog.addSelection(tr("Insert As Relative Link"), 3);
- // Attach as attachment.
- if (m_file->getType() == FileType::Note) {
- VNoteFile *note = static_cast<VNoteFile *>((VFile *)m_file);
- if (-1 == note->findAttachmentByPath(url.toLocalFile(), false)) {
- dialog.addSelection(tr("Attach And Insert Link"), 6);
- }
- }
- }
- dialog.addSelection(tr("Insert As Text"), 4);
- if (!localTextFilePath.isEmpty()) {
- dialog.addSelection(tr("Insert File Content"), 5);
- }
- // FIXME: After calling dialog.exec(), p_source->hasUrl() returns false.
- if (dialog.exec() == QDialog::Accepted) {
- bool relativeLink = false;
- switch (dialog.getSelection()) {
- case 0:
- {
- // Insert As Image.
- m_editOps->insertImageFromURL(url);
- return true;
- }
- case 1:
- {
- // Insert As Image Link.
- insertImageLink("", url.isLocalFile() ? url.toString(QUrl::EncodeSpaces)
- : url.toString());
- return true;
- }
- case 6:
- {
- // Attach And Insert Link.
- QString file = url.toLocalFile();
- Q_ASSERT(m_file->getType() == FileType::Note);
- VNoteFile *note = static_cast<VNoteFile *>((VFile *)m_file);
- QString destFile;
- if (!note->addAttachment(file, &destFile)) {
- VUtils::showMessage(QMessageBox::Warning,
- tr("Warning"),
- tr("Fail to add attachment %1 for note <span style=\"%2\">%3</span>.")
- .arg(file)
- .arg(g_config->c_dataTextStyle)
- .arg(note->getName()),
- "",
- QMessageBox::Ok,
- QMessageBox::Ok,
- this);
- return true;
- }
- emit m_object->statusMessage(tr("1 file added as attachment"));
- // Update url to point to the attachment file.
- Q_ASSERT(!destFile.isEmpty());
- url = QUrl::fromLocalFile(destFile);
- V_FALLTHROUGH;
- }
- case 3:
- // Insert As Relative link.
- relativeLink = true;
- V_FALLTHROUGH;
- case 2:
- {
- // Insert As Link.
- QString initLinkText;
- if (isLocalFile) {
- initLinkText = QFileInfo(url.toLocalFile()).fileName();
- }
- QString ut;
- if (relativeLink) {
- QDir dir(m_file->fetchBasePath());
- ut = dir.relativeFilePath(url.toLocalFile());
- ut = QUrl(ut).toString(QUrl::EncodeSpaces);
- } else {
- ut = url.isLocalFile() ? url.toString(QUrl::EncodeSpaces)
- : url.toString();
- }
- VInsertLinkDialog ld(QObject::tr("Insert Link"),
- "",
- "",
- initLinkText,
- ut,
- false,
- this);
- if (ld.exec() == QDialog::Accepted) {
- QString linkText = ld.getLinkText();
- QString linkUrl = ld.getLinkUrl();
- Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty());
- m_editOps->insertLink(linkText, linkUrl);
- }
- return true;
- }
- case 4:
- {
- // Insert As Text.
- if (p_source->hasText()) {
- m_editOps->insertText(p_source->text());
- } else {
- m_editOps->insertText(url.toString());
- }
- return true;
- }
- case 5:
- {
- // Insert File Content.
- Q_ASSERT(!localTextFilePath.isEmpty());
- m_editOps->insertText(VUtils::readFileFromDisk(localTextFilePath));
- return true;
- }
- default:
- Q_ASSERT(false);
- break;
- }
- }
- return true;
- }
- void VMdEditor::replaceTextWithLocalImages(QString &p_text)
- {
- QVector<VElementRegion> regs = VUtils::fetchImageRegionsUsingParser(p_text);
- if (regs.isEmpty()) {
- return;
- }
- // Sort it in ascending order.
- std::sort(regs.begin(), regs.end());
- QProgressDialog proDlg(tr("Fetching images to local folder..."),
- tr("Abort"),
- 0,
- regs.size(),
- this);
- proDlg.setWindowModality(Qt::WindowModal);
- proDlg.setWindowTitle(tr("Fetching Images To Local Folder"));
- QRegExp zhihuRegExp("^https?://www\\.zhihu\\.com/equation\\?tex=(.+)$");
- QRegExp regExp(VUtils::c_imageLinkRegExp);
- for (int i = regs.size() - 1; i >= 0; --i) {
- proDlg.setValue(regs.size() - 1 - i);
- if (proDlg.wasCanceled()) {
- break;
- }
- const VElementRegion ® = regs[i];
- QString linkText = p_text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos);
- if (regExp.indexIn(linkText) == -1) {
- continue;
- }
- QString imageTitle = VUtils::purifyImageTitle(regExp.cap(1).trimmed());
- QString imageUrl = regExp.cap(2).trimmed();
- const int maxUrlLength = 100;
- QString urlToDisplay(imageUrl);
- if (urlToDisplay.size() > maxUrlLength) {
- urlToDisplay = urlToDisplay.left(maxUrlLength) + "...";
- }
- proDlg.setLabelText(tr("Fetching image: %1").arg(urlToDisplay));
- // Handle equation from zhihu.com like http://www.zhihu.com/equation?tex=P.
- if (zhihuRegExp.indexIn(imageUrl) != -1) {
- QString tex = zhihuRegExp.cap(1).trimmed();
- // Remove the +.
- tex.replace(QChar('+'), " ");
- tex = QUrl::fromPercentEncoding(tex.toUtf8());
- if (tex.isEmpty()) {
- continue;
- }
- tex = "$" + tex + "$";
- p_text.replace(reg.m_startPos,
- reg.m_endPos - reg.m_startPos,
- tex);
- continue;
- }
- QString destImagePath, urlInLink;
- // Only handle absolute file path or network path.
- QString srcImagePath;
- QFileInfo info(VUtils::purifyUrl(imageUrl));
- // For network image.
- QScopedPointer<QTemporaryFile> tmpFile;
- if (info.exists()) {
- if (info.isAbsolute()) {
- // Absolute local path.
- srcImagePath = info.absoluteFilePath();
- }
- } else {
- // Network path.
- QByteArray data = VDownloader::downloadSync(QUrl(imageUrl));
- if (!data.isEmpty()) {
- tmpFile.reset(VUtils::createTemporaryFile(info.suffix()));
- if (tmpFile->open() && tmpFile->write(data) > -1) {
- srcImagePath = tmpFile->fileName();
- }
- // Need to close it explicitly to flush cache of small file.
- tmpFile->close();
- }
- }
- if (srcImagePath.isEmpty()) {
- continue;
- }
- // Insert image without inserting text.
- auto ops = static_cast<VMdEditOperations *>(m_editOps);
- ops->insertImageFromPath(imageTitle,
- m_file->fetchImageFolderPath(),
- m_file->getImageFolderInLink(),
- srcImagePath,
- false,
- destImagePath,
- urlInLink);
- if (urlInLink.isEmpty()) {
- continue;
- }
- // Replace URL in link.
- QString newLink = QString("")
- .arg(imageTitle)
- .arg(urlInLink)
- .arg(regExp.cap(3))
- .arg(regExp.cap(6));
- p_text.replace(reg.m_startPos,
- reg.m_endPos - reg.m_startPos,
- newLink);
- }
- proDlg.setValue(regs.size());
- }
- void VMdEditor::initAttachmentMenu(QMenu *p_menu)
- {
- if (m_file->getType() != FileType::Note) {
- return;
- }
- const VNoteFile *note = static_cast<const VNoteFile *>((VFile *)m_file);
- const QVector<VAttachment> &attas = note->getAttachments();
- if (attas.isEmpty()) {
- return;
- }
- QMenu *subMenu = new QMenu(tr("Link To Attachment"), p_menu);
- for (auto const & att : attas) {
- QAction *act = new QAction(att.m_name, subMenu);
- act->setData(att.m_name);
- subMenu->addAction(act);
- }
- connect(subMenu, &QMenu::triggered,
- this, &VMdEditor::handleLinkToAttachmentAction);
- p_menu->addSeparator();
- p_menu->addMenu(subMenu);
- }
- void VMdEditor::handleLinkToAttachmentAction(QAction *p_act)
- {
- Q_ASSERT(m_file->getType() == FileType::Note);
- VNoteFile *note = static_cast<VNoteFile *>((VFile *)m_file);
- QString name = p_act->data().toString();
- QString folderPath = note->fetchAttachmentFolderPath();
- QString filePath = QDir(folderPath).filePath(name);
- QDir dir(note->fetchBasePath());
- QString ut = dir.relativeFilePath(filePath);
- ut = QUrl(ut).toString(QUrl::EncodeSpaces);
- VInsertLinkDialog ld(QObject::tr("Insert Link"),
- "",
- "",
- name,
- ut,
- false,
- this);
- if (ld.exec() == QDialog::Accepted) {
- QString linkText = ld.getLinkText();
- QString linkUrl = ld.getLinkUrl();
- Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty());
- m_editOps->insertLink(linkText, linkUrl);
- }
- }
|