vmdeditor.cpp 26 KB

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