hgmarkdownhighlighter.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /* PEG Markdown Highlight
  2. * Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
  3. * Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
  4. *
  5. * highlighter.cpp
  6. *
  7. * Qt 4.7 example for highlighting a rich text widget.
  8. */
  9. #include <QtGui>
  10. #include <QtDebug>
  11. #include "hgmarkdownhighlighter.h"
  12. #ifndef QT_NO_DEBUG
  13. #define V_HIGHLIGHT_DEBUG
  14. #endif
  15. const int WorkerThread::initCapacity = 1024;
  16. WorkerThread::WorkerThread()
  17. : QThread(NULL), content(NULL), capacity(0), result(NULL)
  18. {
  19. resizeBuffer(initCapacity);
  20. }
  21. WorkerThread::~WorkerThread()
  22. {
  23. if (result) {
  24. pmh_free_elements(result);
  25. result = NULL;
  26. }
  27. if (content) {
  28. delete [] content;
  29. capacity = 0;
  30. content = NULL;
  31. }
  32. }
  33. void WorkerThread::resizeBuffer(int newCap)
  34. {
  35. if (newCap == capacity) {
  36. return;
  37. }
  38. if (capacity > 0) {
  39. Q_ASSERT(content);
  40. delete [] content;
  41. }
  42. capacity = newCap;
  43. content = new char [capacity];
  44. }
  45. void WorkerThread::prepareAndStart(const char *data)
  46. {
  47. Q_ASSERT(data);
  48. int len = strlen(data);
  49. if (len >= capacity) {
  50. resizeBuffer(qMax(2 * capacity, len + 1));
  51. }
  52. Q_ASSERT(content);
  53. memcpy(content, data, len);
  54. content[len] = '\0';
  55. if (result) {
  56. pmh_free_elements(result);
  57. result = NULL;
  58. }
  59. start();
  60. }
  61. pmh_element** WorkerThread::retriveResult()
  62. {
  63. Q_ASSERT(result);
  64. pmh_element** ret = result;
  65. result = NULL;
  66. return ret;
  67. }
  68. void WorkerThread::run()
  69. {
  70. if (content == NULL)
  71. return;
  72. Q_ASSERT(!result);
  73. pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
  74. }
  75. // Will be freeed by parent automatically
  76. HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
  77. QTextDocument *parent,
  78. int aWaitInterval) : QObject(parent)
  79. {
  80. workerThread = new WorkerThread();
  81. cached_elements = NULL;
  82. waitInterval = aWaitInterval;
  83. setStyles(styles);
  84. timer = new QTimer(this);
  85. timer->setSingleShot(true);
  86. timer->setInterval(aWaitInterval);
  87. connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
  88. document = parent;
  89. connect(document, SIGNAL(contentsChange(int,int,int)),
  90. this, SLOT(handleContentsChange(int,int,int)));
  91. connect(workerThread, SIGNAL(finished()), this, SLOT(threadFinished()));
  92. this->parse();
  93. }
  94. HGMarkdownHighlighter::~HGMarkdownHighlighter()
  95. {
  96. if (workerThread) {
  97. if (workerThread->isRunning()) {
  98. workerThread->wait();
  99. }
  100. delete workerThread;
  101. workerThread = NULL;
  102. }
  103. if (cached_elements) {
  104. pmh_free_elements(cached_elements);
  105. cached_elements = NULL;
  106. }
  107. }
  108. void HGMarkdownHighlighter::setStyles(const QVector<HighlightingStyle> &styles)
  109. {
  110. this->highlightingStyles = styles;
  111. }
  112. void HGMarkdownHighlighter::clearFormatting()
  113. {
  114. QTextBlock block = document->firstBlock();
  115. while (block.isValid()) {
  116. block.layout()->clearAdditionalFormats();
  117. block = block.next();
  118. }
  119. }
  120. void HGMarkdownHighlighter::highlight()
  121. {
  122. if (cached_elements == NULL) {
  123. return;
  124. }
  125. if (highlightingStyles.isEmpty()) {
  126. qWarning() << "error: HighlightingStyles is not set";
  127. return;
  128. }
  129. this->clearFormatting();
  130. // To make sure content is not changed by highlight operations.
  131. // May be resource-consuming. Can be removed if no need.
  132. #ifdef V_HIGHLIGHT_DEBUG
  133. QString oriContent = document->toPlainText();
  134. #endif
  135. for (int i = 0; i < highlightingStyles.size(); i++)
  136. {
  137. const HighlightingStyle &style = highlightingStyles[i];
  138. pmh_element *elem_cursor = cached_elements[style.type];
  139. while (elem_cursor != NULL)
  140. {
  141. if (elem_cursor->end <= elem_cursor->pos) {
  142. elem_cursor = elem_cursor->next;
  143. continue;
  144. }
  145. // "The QTextLayout object can only be modified from the
  146. // documentChanged implementation of a QAbstractTextDocumentLayout
  147. // subclass. Any changes applied from the outside cause undefined
  148. // behavior." -- we are breaking this rule here. There might be
  149. // a better (more correct) way to do this.
  150. int startBlockNum = document->findBlock(elem_cursor->pos).blockNumber();
  151. int endBlockNum = document->findBlock(elem_cursor->end).blockNumber();
  152. for (int j = startBlockNum; j <= endBlockNum; j++)
  153. {
  154. QTextBlock block = document->findBlockByNumber(j);
  155. QTextLayout *layout = block.layout();
  156. QList<QTextLayout::FormatRange> list = layout->additionalFormats();
  157. int blockpos = block.position();
  158. QTextLayout::FormatRange r;
  159. r.format = style.format;
  160. if (j == startBlockNum) {
  161. r.start = elem_cursor->pos - blockpos;
  162. r.length = (startBlockNum == endBlockNum)
  163. ? elem_cursor->end - elem_cursor->pos
  164. : block.length() - r.start;
  165. } else if (j == endBlockNum) {
  166. r.start = 0;
  167. r.length = elem_cursor->end - blockpos;
  168. } else {
  169. r.start = 0;
  170. r.length = block.length();
  171. }
  172. list.append(r);
  173. layout->setAdditionalFormats(list);
  174. }
  175. elem_cursor = elem_cursor->next;
  176. }
  177. }
  178. document->markContentsDirty(0, document->characterCount());
  179. #ifdef V_HIGHLIGHT_DEBUG
  180. if (oriContent != document->toPlainText()) {
  181. qWarning() << "warning: content was changed before and after highlighting";
  182. }
  183. #endif
  184. }
  185. void HGMarkdownHighlighter::parse()
  186. {
  187. if (workerThread->isRunning()) {
  188. parsePending = true;
  189. return;
  190. }
  191. QString content = document->toPlainText();
  192. QByteArray ba = content.toUtf8();
  193. parsePending = false;
  194. workerThread->prepareAndStart((const char *)ba.data());
  195. }
  196. void HGMarkdownHighlighter::threadFinished()
  197. {
  198. if (parsePending) {
  199. this->parse();
  200. return;
  201. }
  202. if (cached_elements != NULL) {
  203. pmh_free_elements(cached_elements);
  204. }
  205. cached_elements = workerThread->retriveResult();
  206. this->highlight();
  207. }
  208. void HGMarkdownHighlighter::handleContentsChange(int position, int charsRemoved,
  209. int charsAdded)
  210. {
  211. if (charsRemoved == 0 && charsAdded == 0)
  212. return;
  213. timer->stop();
  214. timer->start();
  215. }
  216. void HGMarkdownHighlighter::timerTimeout()
  217. {
  218. this->parse();
  219. }