vmdeditor.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. #include "vmdeditor.h"
  2. #include <QtWidgets>
  3. #include <QMenu>
  4. #include <QDebug>
  5. #include "vdocument.h"
  6. #include "utils/veditutils.h"
  7. #include "vedittab.h"
  8. #include "hgmarkdownhighlighter.h"
  9. #include "vcodeblockhighlighthelper.h"
  10. #include "vmdeditoperations.h"
  11. #include "vtableofcontent.h"
  12. #include "utils/veditutils.h"
  13. #include "dialog/vselectdialog.h"
  14. #include "dialog/vconfirmdeletiondialog.h"
  15. #include "vtextblockdata.h"
  16. #include "vorphanfile.h"
  17. #include "vnotefile.h"
  18. #include "vpreviewmanager.h"
  19. #include "utils/viconutils.h"
  20. extern VConfigManager *g_config;
  21. VMdEditor::VMdEditor(VFile *p_file,
  22. VDocument *p_doc,
  23. MarkdownConverterType p_type,
  24. QWidget *p_parent)
  25. : VTextEdit(p_parent),
  26. VEditor(p_file, this),
  27. m_mdHighlighter(NULL),
  28. m_freshEdit(true)
  29. {
  30. Q_ASSERT(p_file->getDocType() == DocType::Markdown);
  31. VEditor::init();
  32. // Hook functions from VEditor.
  33. connect(this, &VTextEdit::cursorPositionChanged,
  34. this, [this]() {
  35. highlightOnCursorPositionChanged();
  36. });
  37. connect(this, &VTextEdit::selectionChanged,
  38. this, [this]() {
  39. highlightSelectedWord();
  40. });
  41. // End.
  42. setReadOnly(true);
  43. m_mdHighlighter = new HGMarkdownHighlighter(g_config->getMdHighlightingStyles(),
  44. g_config->getCodeBlockStyles(),
  45. g_config->getMarkdownHighlightInterval(),
  46. document());
  47. connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
  48. this, &VMdEditor::updateHeaders);
  49. // After highlight, the cursor may trun into non-visible. We should make it visible
  50. // in this case.
  51. connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
  52. this, [this]() {
  53. makeBlockVisible(textCursor().block());
  54. if (m_freshEdit) {
  55. m_freshEdit = false;
  56. emit m_object->ready();
  57. }
  58. });
  59. m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter,
  60. p_doc,
  61. p_type);
  62. m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
  63. connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
  64. m_previewMgr, &VPreviewManager::imageLinksUpdated);
  65. connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
  66. m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);
  67. m_editOps = new VMdEditOperations(this, m_file);
  68. connect(m_editOps, &VEditOperations::statusMessage,
  69. m_object, &VEditorObject::statusMessage);
  70. connect(m_editOps, &VEditOperations::vimStatusUpdated,
  71. m_object, &VEditorObject::vimStatusUpdated);
  72. connect(this, &VTextEdit::cursorPositionChanged,
  73. this, &VMdEditor::updateCurrentHeader);
  74. updateFontAndPalette();
  75. updateConfig();
  76. }
  77. void VMdEditor::updateFontAndPalette()
  78. {
  79. setFont(g_config->getMdEditFont());
  80. setPalette(g_config->getMdEditPalette());
  81. }
  82. void VMdEditor::beginEdit()
  83. {
  84. updateFontAndPalette();
  85. updateConfig();
  86. initInitImages();
  87. setModified(false);
  88. setReadOnlyAndHighlightCurrentLine(false);
  89. emit statusChanged();
  90. if (m_freshEdit) {
  91. m_mdHighlighter->updateHighlight();
  92. } else {
  93. updateHeaders(m_mdHighlighter->getHeaderRegions());
  94. }
  95. }
  96. void VMdEditor::endEdit()
  97. {
  98. setReadOnlyAndHighlightCurrentLine(true);
  99. clearUnusedImages();
  100. }
  101. void VMdEditor::saveFile()
  102. {
  103. Q_ASSERT(m_file->isModifiable());
  104. if (!document()->isModified()) {
  105. return;
  106. }
  107. m_file->setContent(toPlainText());
  108. setModified(false);
  109. }
  110. void VMdEditor::reloadFile()
  111. {
  112. bool readonly = isReadOnly();
  113. setReadOnly(true);
  114. const QString &content = m_file->getContent();
  115. setPlainText(content);
  116. setModified(false);
  117. m_mdHighlighter->updateHighlightFast();
  118. m_freshEdit = true;
  119. setReadOnly(readonly);
  120. }
  121. bool VMdEditor::scrollToBlock(int p_blockNumber)
  122. {
  123. QTextBlock block = document()->findBlockByNumber(p_blockNumber);
  124. if (block.isValid()) {
  125. VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
  126. moveCursor(QTextCursor::EndOfBlock);
  127. return true;
  128. }
  129. return false;
  130. }
  131. // Get the visual offset of a block.
  132. #define GETVISUALOFFSETY (contentOffsetY() + (int)rect.y())
  133. void VMdEditor::makeBlockVisible(const QTextBlock &p_block)
  134. {
  135. if (!p_block.isValid() || !p_block.isVisible()) {
  136. return;
  137. }
  138. QScrollBar *vbar = verticalScrollBar();
  139. if (!vbar || (vbar->minimum() == vbar->maximum())) {
  140. // No vertical scrollbar. No need to scroll.
  141. return;
  142. }
  143. int height = rect().height();
  144. QScrollBar *hbar = horizontalScrollBar();
  145. if (hbar && (hbar->minimum() != hbar->maximum())) {
  146. height -= hbar->height();
  147. }
  148. bool moved = false;
  149. QAbstractTextDocumentLayout *layout = document()->documentLayout();
  150. QRectF rect = layout->blockBoundingRect(p_block);
  151. int y = GETVISUALOFFSETY;
  152. int rectHeight = (int)rect.height();
  153. // Handle the case rectHeight >= height.
  154. if (rectHeight >= height) {
  155. if (y < 0) {
  156. // Need to scroll up.
  157. while (y + rectHeight < height && vbar->value() > vbar->minimum()) {
  158. moved = true;
  159. vbar->setValue(vbar->value() - vbar->singleStep());
  160. rect = layout->blockBoundingRect(p_block);
  161. rectHeight = (int)rect.height();
  162. y = GETVISUALOFFSETY;
  163. }
  164. } else if (y > 0) {
  165. // Need to scroll down.
  166. while (y > 0 && vbar->value() < vbar->maximum()) {
  167. moved = true;
  168. vbar->setValue(vbar->value() + vbar->singleStep());
  169. rect = layout->blockBoundingRect(p_block);
  170. rectHeight = (int)rect.height();
  171. y = GETVISUALOFFSETY;
  172. }
  173. if (y < 0) {
  174. // One step back.
  175. moved = true;
  176. vbar->setValue(vbar->value() - vbar->singleStep());
  177. }
  178. }
  179. if (moved) {
  180. qDebug() << "scroll to make huge block visible";
  181. }
  182. return;
  183. }
  184. while (y < 0 && vbar->value() > vbar->minimum()) {
  185. moved = true;
  186. vbar->setValue(vbar->value() - vbar->singleStep());
  187. rect = layout->blockBoundingRect(p_block);
  188. rectHeight = (int)rect.height();
  189. y = GETVISUALOFFSETY;
  190. }
  191. if (moved) {
  192. qDebug() << "scroll page down to make block visible";
  193. return;
  194. }
  195. while (y + rectHeight > height && vbar->value() < vbar->maximum()) {
  196. moved = true;
  197. vbar->setValue(vbar->value() + vbar->singleStep());
  198. rect = layout->blockBoundingRect(p_block);
  199. rectHeight = (int)rect.height();
  200. y = GETVISUALOFFSETY;
  201. }
  202. if (moved) {
  203. qDebug() << "scroll page up to make block visible";
  204. }
  205. }
  206. void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
  207. {
  208. QMenu *menu = createStandardContextMenu();
  209. menu->setToolTipsVisible(true);
  210. const QList<QAction *> actions = menu->actions();
  211. if (!textCursor().hasSelection()) {
  212. VEditTab *editTab = dynamic_cast<VEditTab *>(parent());
  213. Q_ASSERT(editTab);
  214. if (editTab->isEditMode()) {
  215. QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"),
  216. tr("&Save Changes And Read"),
  217. menu);
  218. saveExitAct->setToolTip(tr("Save changes and exit edit mode"));
  219. connect(saveExitAct, &QAction::triggered,
  220. this, [this]() {
  221. emit m_object->saveAndRead();
  222. });
  223. QAction *discardExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/discard_exit.svg"),
  224. tr("&Discard Changes And Read"),
  225. menu);
  226. discardExitAct->setToolTip(tr("Discard changes and exit edit mode"));
  227. connect(discardExitAct, &QAction::triggered,
  228. this, [this]() {
  229. emit m_object->discardAndRead();
  230. });
  231. menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct);
  232. menu->insertAction(discardExitAct, saveExitAct);
  233. if (!actions.isEmpty()) {
  234. menu->insertSeparator(actions[0]);
  235. }
  236. }
  237. }
  238. menu->exec(p_event->globalPos());
  239. delete menu;
  240. }
  241. void VMdEditor::mousePressEvent(QMouseEvent *p_event)
  242. {
  243. if (handleMousePressEvent(p_event)) {
  244. return;
  245. }
  246. VTextEdit::mousePressEvent(p_event);
  247. emit m_object->mousePressed(p_event);
  248. }
  249. void VMdEditor::mouseReleaseEvent(QMouseEvent *p_event)
  250. {
  251. if (handleMouseReleaseEvent(p_event)) {
  252. return;
  253. }
  254. VTextEdit::mouseReleaseEvent(p_event);
  255. }
  256. void VMdEditor::mouseMoveEvent(QMouseEvent *p_event)
  257. {
  258. if (handleMouseMoveEvent(p_event)) {
  259. return;
  260. }
  261. VTextEdit::mouseMoveEvent(p_event);
  262. emit m_object->mouseMoved(p_event);
  263. }
  264. QVariant VMdEditor::inputMethodQuery(Qt::InputMethodQuery p_query) const
  265. {
  266. QVariant ret;
  267. if (handleInputMethodQuery(p_query, ret)) {
  268. return ret;
  269. }
  270. return VTextEdit::inputMethodQuery(p_query);
  271. }
  272. bool VMdEditor::isBlockVisible(const QTextBlock &p_block)
  273. {
  274. if (!p_block.isValid() || !p_block.isVisible()) {
  275. return false;
  276. }
  277. QScrollBar *vbar = verticalScrollBar();
  278. if (!vbar || !vbar->isVisible()) {
  279. // No vertical scrollbar.
  280. return true;
  281. }
  282. int height = rect().height();
  283. QScrollBar *hbar = horizontalScrollBar();
  284. if (hbar && hbar->isVisible()) {
  285. height -= hbar->height();
  286. }
  287. QAbstractTextDocumentLayout *layout = document()->documentLayout();
  288. QRectF rect = layout->blockBoundingRect(p_block);
  289. int y = GETVISUALOFFSETY;
  290. int rectHeight = (int)rect.height();
  291. return (y >= 0 && y < height) || (y < 0 && y + rectHeight > 0);
  292. }
  293. static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel)
  294. {
  295. Q_ASSERT(p_level >= 1 && p_level < p_sequence.size());
  296. if (p_level < p_baseLevel) {
  297. p_sequence.fill(0);
  298. return;
  299. }
  300. ++p_sequence[p_level];
  301. for (int i = p_level + 1; i < p_sequence.size(); ++i) {
  302. p_sequence[i] = 0;
  303. }
  304. }
  305. static QString headerSequenceStr(const QVector<int> &p_sequence)
  306. {
  307. QString res;
  308. for (int i = 1; i < p_sequence.size(); ++i) {
  309. if (p_sequence[i] != 0) {
  310. res = res + QString::number(p_sequence[i]) + '.';
  311. } else if (res.isEmpty()) {
  312. continue;
  313. } else {
  314. break;
  315. }
  316. }
  317. return res;
  318. }
  319. static void insertSequenceToHeader(QTextBlock p_block,
  320. QRegExp &p_reg,
  321. QRegExp &p_preReg,
  322. const QString &p_seq)
  323. {
  324. if (!p_block.isValid()) {
  325. return;
  326. }
  327. QString text = p_block.text();
  328. bool matched = p_reg.exactMatch(text);
  329. Q_ASSERT(matched);
  330. matched = p_preReg.exactMatch(text);
  331. Q_ASSERT(matched);
  332. int start = p_reg.cap(1).length() + 1;
  333. int end = p_preReg.cap(1).length();
  334. Q_ASSERT(start <= end);
  335. QTextCursor cursor(p_block);
  336. cursor.setPosition(p_block.position() + start);
  337. if (start != end) {
  338. cursor.setPosition(p_block.position() + end, QTextCursor::KeepAnchor);
  339. }
  340. if (p_seq.isEmpty()) {
  341. cursor.removeSelectedText();
  342. } else {
  343. cursor.insertText(p_seq + ' ');
  344. }
  345. }
  346. void VMdEditor::updateHeaders(const QVector<VElementRegion> &p_headerRegions)
  347. {
  348. QTextDocument *doc = document();
  349. QVector<VTableOfContentItem> headers;
  350. QVector<int> headerBlockNumbers;
  351. QVector<QString> headerSequences;
  352. if (!p_headerRegions.isEmpty()) {
  353. headers.reserve(p_headerRegions.size());
  354. headerBlockNumbers.reserve(p_headerRegions.size());
  355. headerSequences.reserve(p_headerRegions.size());
  356. }
  357. // Assume that each block contains only one line
  358. // Only support # syntax for now
  359. QRegExp headerReg(VUtils::c_headerRegExp);
  360. int baseLevel = -1;
  361. for (auto const & reg : p_headerRegions) {
  362. QTextBlock block = doc->findBlock(reg.m_startPos);
  363. if (!block.isValid()) {
  364. continue;
  365. }
  366. if (!block.contains(reg.m_endPos - 1)) {
  367. qWarning() << "header accross multiple blocks, starting from block"
  368. << block.blockNumber()
  369. << block.text();
  370. }
  371. if ((block.userState() == HighlightBlockState::Normal)
  372. && headerReg.exactMatch(block.text())) {
  373. int level = headerReg.cap(1).length();
  374. VTableOfContentItem header(headerReg.cap(2).trimmed(),
  375. level,
  376. block.blockNumber(),
  377. headers.size());
  378. headers.append(header);
  379. headerBlockNumbers.append(block.blockNumber());
  380. headerSequences.append(headerReg.cap(3));
  381. if (baseLevel == -1) {
  382. baseLevel = level;
  383. } else if (baseLevel > level) {
  384. baseLevel = level;
  385. }
  386. }
  387. }
  388. m_headers.clear();
  389. bool autoSequence = m_config.m_enableHeadingSequence
  390. && !isReadOnly()
  391. && m_file->isModifiable();
  392. int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
  393. if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
  394. headingSequenceBaseLevel = 1;
  395. }
  396. QVector<int> seqs(7, 0);
  397. QRegExp preReg(VUtils::c_headerPrefixRegExp);
  398. int curLevel = baseLevel - 1;
  399. for (int i = 0; i < headers.size(); ++i) {
  400. VTableOfContentItem &item = headers[i];
  401. while (item.m_level > curLevel + 1) {
  402. curLevel += 1;
  403. // Insert empty level which is an invalid header.
  404. m_headers.append(VTableOfContentItem(c_emptyHeaderName,
  405. curLevel,
  406. -1,
  407. m_headers.size()));
  408. if (autoSequence) {
  409. addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
  410. }
  411. }
  412. item.m_index = m_headers.size();
  413. m_headers.append(item);
  414. curLevel = item.m_level;
  415. if (autoSequence) {
  416. addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
  417. QString seqStr = headerSequenceStr(seqs);
  418. if (headerSequences[i] != seqStr) {
  419. // Insert correct sequence.
  420. insertSequenceToHeader(doc->findBlockByNumber(headerBlockNumbers[i]),
  421. headerReg,
  422. preReg,
  423. seqStr);
  424. }
  425. }
  426. }
  427. emit headersChanged(m_headers);
  428. updateCurrentHeader();
  429. }
  430. void VMdEditor::updateCurrentHeader()
  431. {
  432. emit currentHeaderChanged(textCursor().block().blockNumber());
  433. }
  434. void VMdEditor::initInitImages()
  435. {
  436. m_initImages = VUtils::fetchImagesFromMarkdownFile(m_file,
  437. ImageLink::LocalRelativeInternal);
  438. }
  439. void VMdEditor::clearUnusedImages()
  440. {
  441. QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
  442. ImageLink::LocalRelativeInternal);
  443. QVector<QString> unusedImages;
  444. if (!m_insertedImages.isEmpty()) {
  445. for (int i = 0; i < m_insertedImages.size(); ++i) {
  446. const ImageLink &link = m_insertedImages[i];
  447. if (link.m_type != ImageLink::LocalRelativeInternal) {
  448. continue;
  449. }
  450. int j;
  451. for (j = 0; j < images.size(); ++j) {
  452. if (VUtils::equalPath(link.m_path, images[j].m_path)) {
  453. break;
  454. }
  455. }
  456. // This inserted image is no longer in the file.
  457. if (j == images.size()) {
  458. unusedImages.push_back(link.m_path);
  459. }
  460. }
  461. m_insertedImages.clear();
  462. }
  463. for (int i = 0; i < m_initImages.size(); ++i) {
  464. const ImageLink &link = m_initImages[i];
  465. V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
  466. int j;
  467. for (j = 0; j < images.size(); ++j) {
  468. if (VUtils::equalPath(link.m_path, images[j].m_path)) {
  469. break;
  470. }
  471. }
  472. // Original local relative image is no longer in the file.
  473. if (j == images.size()) {
  474. unusedImages.push_back(link.m_path);
  475. }
  476. }
  477. if (!unusedImages.isEmpty()) {
  478. if (g_config->getConfirmImagesCleanUp()) {
  479. QVector<ConfirmItemInfo> items;
  480. for (auto const & img : unusedImages) {
  481. items.push_back(ConfirmItemInfo(img,
  482. img,
  483. img,
  484. NULL));
  485. }
  486. QString text = tr("Following images seems not to be used in this note anymore. "
  487. "Please confirm the deletion of these images.");
  488. QString info = tr("Deleted files could be found in the recycle "
  489. "bin of this note.<br>"
  490. "Click \"Cancel\" to leave them untouched.");
  491. VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
  492. text,
  493. info,
  494. items,
  495. true,
  496. true,
  497. true,
  498. this);
  499. unusedImages.clear();
  500. if (dialog.exec()) {
  501. items = dialog.getConfirmedItems();
  502. g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
  503. for (auto const & item : items) {
  504. unusedImages.push_back(item.m_name);
  505. }
  506. }
  507. }
  508. for (int i = 0; i < unusedImages.size(); ++i) {
  509. bool ret = false;
  510. if (m_file->getType() == FileType::Note) {
  511. const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)m_file);
  512. ret = VUtils::deleteFile(tmpFile->getNotebook(), unusedImages[i], false);
  513. } else if (m_file->getType() == FileType::Orphan) {
  514. const VOrphanFile *tmpFile = dynamic_cast<const VOrphanFile *>((VFile *)m_file);
  515. ret = VUtils::deleteFile(tmpFile, unusedImages[i], false);
  516. } else {
  517. Q_ASSERT(false);
  518. }
  519. if (!ret) {
  520. qWarning() << "fail to delete unused original image" << unusedImages[i];
  521. } else {
  522. qDebug() << "delete unused image" << unusedImages[i];
  523. }
  524. }
  525. }
  526. m_initImages.clear();
  527. }
  528. void VMdEditor::keyPressEvent(QKeyEvent *p_event)
  529. {
  530. if (m_editOps && m_editOps->handleKeyPressEvent(p_event)) {
  531. return;
  532. }
  533. VTextEdit::keyPressEvent(p_event);
  534. }
  535. bool VMdEditor::canInsertFromMimeData(const QMimeData *p_source) const
  536. {
  537. return p_source->hasImage()
  538. || p_source->hasUrls()
  539. || VTextEdit::canInsertFromMimeData(p_source);
  540. }
  541. void VMdEditor::insertFromMimeData(const QMimeData *p_source)
  542. {
  543. VSelectDialog dialog(tr("Insert From Clipboard"), this);
  544. dialog.addSelection(tr("Insert As Image"), 0);
  545. dialog.addSelection(tr("Insert As Text"), 1);
  546. if (p_source->hasImage()) {
  547. // Image data in the clipboard
  548. if (p_source->hasText()) {
  549. if (dialog.exec() == QDialog::Accepted) {
  550. if (dialog.getSelection() == 1) {
  551. // Insert as text.
  552. Q_ASSERT(p_source->hasText() && p_source->hasImage());
  553. VTextEdit::insertFromMimeData(p_source);
  554. return;
  555. }
  556. } else {
  557. return;
  558. }
  559. }
  560. m_editOps->insertImageFromMimeData(p_source);
  561. return;
  562. } else if (p_source->hasUrls()) {
  563. QList<QUrl> urls = p_source->urls();
  564. if (urls.size() == 1 && VUtils::isImageURL(urls[0])) {
  565. if (dialog.exec() == QDialog::Accepted) {
  566. // FIXME: After calling dialog.exec(), p_source->hasUrl() returns false.
  567. if (dialog.getSelection() == 0) {
  568. // Insert as image.
  569. m_editOps->insertImageFromURL(urls[0]);
  570. return;
  571. }
  572. QMimeData newSource;
  573. newSource.setUrls(urls);
  574. VTextEdit::insertFromMimeData(&newSource);
  575. return;
  576. } else {
  577. return;
  578. }
  579. }
  580. } else if (p_source->hasText()) {
  581. QString text = p_source->text();
  582. if (VUtils::isImageURLText(text)) {
  583. // The text is a URL to an image.
  584. if (dialog.exec() == QDialog::Accepted) {
  585. if (dialog.getSelection() == 0) {
  586. // Insert as image.
  587. QUrl url(text);
  588. if (url.isValid()) {
  589. m_editOps->insertImageFromURL(QUrl(text));
  590. }
  591. return;
  592. }
  593. } else {
  594. return;
  595. }
  596. }
  597. Q_ASSERT(p_source->hasText());
  598. }
  599. VTextEdit::insertFromMimeData(p_source);
  600. }
  601. void VMdEditor::imageInserted(const QString &p_path)
  602. {
  603. ImageLink link;
  604. link.m_path = p_path;
  605. if (m_file->useRelativeImageFolder()) {
  606. link.m_type = ImageLink::LocalRelativeInternal;
  607. } else {
  608. link.m_type = ImageLink::LocalAbsolute;
  609. }
  610. m_insertedImages.append(link);
  611. }
  612. bool VMdEditor::scrollToHeader(int p_blockNumber)
  613. {
  614. if (p_blockNumber < 0) {
  615. return false;
  616. }
  617. return scrollToBlock(p_blockNumber);
  618. }
  619. int VMdEditor::indexOfCurrentHeader() const
  620. {
  621. if (m_headers.isEmpty()) {
  622. return -1;
  623. }
  624. int blockNumber = textCursor().block().blockNumber();
  625. for (int i = m_headers.size() - 1; i >= 0; --i) {
  626. if (!m_headers[i].isEmpty()
  627. && m_headers[i].m_blockNumber <= blockNumber) {
  628. return i;
  629. }
  630. }
  631. return -1;
  632. }
  633. bool VMdEditor::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
  634. {
  635. if (m_headers.isEmpty()) {
  636. return false;
  637. }
  638. QTextCursor cursor = textCursor();
  639. int cursorLine = cursor.block().blockNumber();
  640. int targetIdx = -1;
  641. // -1: skip level check.
  642. int targetLevel = 0;
  643. int idx = indexOfCurrentHeader();
  644. if (idx == -1) {
  645. // Cursor locates at the beginning, before any headers.
  646. if (p_relativeLevel < 0 || !p_forward) {
  647. return false;
  648. }
  649. }
  650. int delta = 1;
  651. if (!p_forward) {
  652. delta = -1;
  653. }
  654. bool firstHeader = true;
  655. for (targetIdx = idx == -1 ? 0 : idx;
  656. targetIdx >= 0 && targetIdx < m_headers.size();
  657. targetIdx += delta) {
  658. const VTableOfContentItem &header = m_headers[targetIdx];
  659. if (header.isEmpty()) {
  660. continue;
  661. }
  662. if (targetLevel == 0) {
  663. // The target level has not been init yet.
  664. Q_ASSERT(firstHeader);
  665. targetLevel = header.m_level;
  666. if (p_relativeLevel < 0) {
  667. targetLevel += p_relativeLevel;
  668. if (targetLevel < 1) {
  669. // Invalid level.
  670. return false;
  671. }
  672. } else if (p_relativeLevel > 0) {
  673. targetLevel = -1;
  674. }
  675. }
  676. if (targetLevel == -1 || header.m_level == targetLevel) {
  677. if (firstHeader
  678. && (cursorLine == header.m_blockNumber
  679. || p_forward)
  680. && idx != -1) {
  681. // This header is not counted for the repeat.
  682. firstHeader = false;
  683. continue;
  684. }
  685. if (--p_repeat == 0) {
  686. // Found.
  687. break;
  688. }
  689. } else if (header.m_level < targetLevel) {
  690. // Stop by higher level.
  691. return false;
  692. }
  693. firstHeader = false;
  694. }
  695. if (targetIdx < 0 || targetIdx >= m_headers.size()) {
  696. return false;
  697. }
  698. // Jump to target header.
  699. int line = m_headers[targetIdx].m_blockNumber;
  700. if (line > -1) {
  701. QTextBlock block = document()->findBlockByNumber(line);
  702. if (block.isValid()) {
  703. cursor.setPosition(block.position());
  704. setTextCursor(cursor);
  705. return true;
  706. }
  707. }
  708. return false;
  709. }
  710. void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest)
  711. {
  712. VEditUtils::scrollBlockInPage(this, p_blockNum, p_dest);
  713. }
  714. void VMdEditor::updateTextEditConfig()
  715. {
  716. setBlockImageEnabled(g_config->getEnablePreviewImages());
  717. setImageWidthConstrainted(g_config->getEnablePreviewImageConstraint());
  718. setLineLeading(m_config.m_lineDistanceHeight);
  719. setImageLineColor(g_config->getEditorPreviewImageLineFg());
  720. int lineNumber = g_config->getEditorLineNumber();
  721. if (lineNumber < (int)LineNumberType::None || lineNumber >= (int)LineNumberType::Invalid) {
  722. lineNumber = (int)LineNumberType::None;
  723. }
  724. setLineNumberType((LineNumberType)lineNumber);
  725. setLineNumberColor(g_config->getEditorLineNumberFg(),
  726. g_config->getEditorLineNumberBg());
  727. m_previewMgr->setPreviewEnabled(g_config->getEnablePreviewImages());
  728. }
  729. void VMdEditor::updateConfig()
  730. {
  731. updateEditConfig();
  732. updateTextEditConfig();
  733. }
  734. QString VMdEditor::getContent() const
  735. {
  736. return toPlainText();
  737. }
  738. void VMdEditor::setContent(const QString &p_content, bool p_modified)
  739. {
  740. if (p_modified) {
  741. QTextCursor cursor = textCursor();
  742. cursor.select(QTextCursor::Document);
  743. cursor.insertText(p_content);
  744. setTextCursor(cursor);
  745. } else {
  746. setPlainText(p_content);
  747. }
  748. }