hgmarkdownhighlighter.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 "hgmarkdownhighlighter.h"
  11. WorkerThread::~WorkerThread()
  12. {
  13. if (result != NULL)
  14. pmh_free_elements(result);
  15. free(content);
  16. }
  17. void WorkerThread::run()
  18. {
  19. if (content == NULL)
  20. return;
  21. pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
  22. }
  23. HGMarkdownHighlighter::HGMarkdownHighlighter(QTextDocument *parent,
  24. int aWaitInterval) : QObject(parent)
  25. {
  26. highlightingStyles = NULL;
  27. workerThread = NULL;
  28. cached_elements = NULL;
  29. waitInterval = aWaitInterval;
  30. timer = new QTimer(this);
  31. timer->setSingleShot(true);
  32. timer->setInterval(aWaitInterval);
  33. connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
  34. document = parent;
  35. connect(document, SIGNAL(contentsChange(int,int,int)),
  36. this, SLOT(handleContentsChange(int,int,int)));
  37. this->parse();
  38. }
  39. void HGMarkdownHighlighter::setStyles(QVector<HighlightingStyle> &styles)
  40. {
  41. this->highlightingStyles = &styles;
  42. }
  43. #define STY(type, format) styles->append((HighlightingStyle){type, format})
  44. void HGMarkdownHighlighter::setDefaultStyles()
  45. {
  46. QVector<HighlightingStyle> *styles = new QVector<HighlightingStyle>();
  47. QTextCharFormat headers; headers.setForeground(QBrush(Qt::darkBlue));
  48. headers.setBackground(QBrush(QColor(230,230,240)));
  49. STY(pmh_H1, headers);
  50. STY(pmh_H2, headers);
  51. STY(pmh_H3, headers);
  52. STY(pmh_H4, headers);
  53. STY(pmh_H5, headers);
  54. STY(pmh_H6, headers);
  55. QTextCharFormat hrule; hrule.setForeground(QBrush(Qt::darkGray));
  56. hrule.setBackground(QBrush(Qt::lightGray));
  57. STY(pmh_HRULE, hrule);
  58. QTextCharFormat list; list.setForeground(QBrush(Qt::magenta));
  59. STY(pmh_LIST_BULLET, list);
  60. STY(pmh_LIST_ENUMERATOR, list);
  61. QTextCharFormat link; link.setForeground(QBrush(Qt::darkCyan));
  62. link.setBackground(QBrush(QColor(205,240,240)));
  63. STY(pmh_LINK, link);
  64. STY(pmh_AUTO_LINK_URL, link);
  65. STY(pmh_AUTO_LINK_EMAIL, link);
  66. QTextCharFormat image; image.setForeground(QBrush(Qt::darkCyan));
  67. image.setBackground(QBrush(Qt::cyan));
  68. STY(pmh_IMAGE, image);
  69. QTextCharFormat ref; ref.setForeground(QBrush(QColor(213,178,178)));
  70. STY(pmh_REFERENCE, ref);
  71. QTextCharFormat code; code.setForeground(QBrush(Qt::darkGreen));
  72. code.setBackground(QBrush(QColor(217,231,217)));
  73. STY(pmh_CODE, code);
  74. STY(pmh_VERBATIM, code);
  75. QTextCharFormat emph; emph.setForeground(QBrush(Qt::darkYellow));
  76. emph.setFontItalic(true);
  77. STY(pmh_EMPH, emph);
  78. QTextCharFormat strong; strong.setForeground(QBrush(Qt::magenta));
  79. strong.setFontWeight(QFont::Bold);
  80. STY(pmh_STRONG, strong);
  81. QTextCharFormat comment; comment.setForeground(QBrush(Qt::gray));
  82. STY(pmh_COMMENT, comment);
  83. QTextCharFormat blockquote; blockquote.setForeground(QBrush(Qt::darkRed));
  84. STY(pmh_BLOCKQUOTE, blockquote);
  85. this->setStyles(*styles);
  86. }
  87. void HGMarkdownHighlighter::clearFormatting()
  88. {
  89. QTextBlock block = document->firstBlock();
  90. while (block.isValid()) {
  91. block.layout()->clearAdditionalFormats();
  92. block = block.next();
  93. }
  94. }
  95. void HGMarkdownHighlighter::highlight()
  96. {
  97. if (cached_elements == NULL) {
  98. qDebug() << "cached_elements is NULL";
  99. return;
  100. }
  101. if (highlightingStyles == NULL)
  102. this->setDefaultStyles();
  103. this->clearFormatting();
  104. for (int i = 0; i < highlightingStyles->size(); i++)
  105. {
  106. HighlightingStyle style = highlightingStyles->at(i);
  107. pmh_element *elem_cursor = cached_elements[style.type];
  108. while (elem_cursor != NULL)
  109. {
  110. if (elem_cursor->end <= elem_cursor->pos) {
  111. elem_cursor = elem_cursor->next;
  112. continue;
  113. }
  114. // "The QTextLayout object can only be modified from the
  115. // documentChanged implementation of a QAbstractTextDocumentLayout
  116. // subclass. Any changes applied from the outside cause undefined
  117. // behavior." -- we are breaking this rule here. There might be
  118. // a better (more correct) way to do this.
  119. int startBlockNum = document->findBlock(elem_cursor->pos).blockNumber();
  120. int endBlockNum = document->findBlock(elem_cursor->end).blockNumber();
  121. for (int j = startBlockNum; j <= endBlockNum; j++)
  122. {
  123. QTextBlock block = document->findBlockByNumber(j);
  124. QTextLayout *layout = block.layout();
  125. QList<QTextLayout::FormatRange> list = layout->additionalFormats();
  126. int blockpos = block.position();
  127. QTextLayout::FormatRange r;
  128. r.format = style.format;
  129. if (j == startBlockNum) {
  130. r.start = elem_cursor->pos - blockpos;
  131. r.length = (startBlockNum == endBlockNum)
  132. ? elem_cursor->end - elem_cursor->pos
  133. : block.length() - r.start;
  134. } else if (j == endBlockNum) {
  135. r.start = 0;
  136. r.length = elem_cursor->end - blockpos;
  137. } else {
  138. r.start = 0;
  139. r.length = block.length();
  140. }
  141. list.append(r);
  142. layout->setAdditionalFormats(list);
  143. }
  144. elem_cursor = elem_cursor->next;
  145. }
  146. }
  147. document->markContentsDirty(0, document->characterCount());
  148. }
  149. void HGMarkdownHighlighter::parse()
  150. {
  151. if (workerThread != NULL && workerThread->isRunning()) {
  152. parsePending = true;
  153. return;
  154. }
  155. QString content = document->toPlainText();
  156. QByteArray ba = content.toUtf8();
  157. char *content_cstring = strdup((char *)ba.data());
  158. if (workerThread != NULL)
  159. delete workerThread;
  160. workerThread = new WorkerThread();
  161. workerThread->content = content_cstring;
  162. connect(workerThread, SIGNAL(finished()), this, SLOT(threadFinished()));
  163. parsePending = false;
  164. workerThread->start();
  165. }
  166. void HGMarkdownHighlighter::threadFinished()
  167. {
  168. if (parsePending) {
  169. this->parse();
  170. return;
  171. }
  172. if (cached_elements != NULL)
  173. pmh_free_elements(cached_elements);
  174. cached_elements = workerThread->result;
  175. workerThread->result = NULL;
  176. this->highlight();
  177. }
  178. void HGMarkdownHighlighter::handleContentsChange(int position, int charsRemoved,
  179. int charsAdded)
  180. {
  181. if (charsRemoved == 0 && charsAdded == 0)
  182. return;
  183. timer->stop();
  184. timer->start();
  185. }
  186. void HGMarkdownHighlighter::timerTimeout()
  187. {
  188. this->parse();
  189. }