vtextedit.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. #include "vtextedit.h"
  2. #include <QDebug>
  3. #include <QScrollBar>
  4. #include <QPainter>
  5. #include <QResizeEvent>
  6. #include "vimageresourcemanager2.h"
  7. #define VIRTUAL_CURSOR_BLOCK_WIDTH 8
  8. enum class BlockState
  9. {
  10. Normal = 0,
  11. CodeBlockStart,
  12. CodeBlock,
  13. CodeBlockEnd,
  14. Comment
  15. };
  16. VTextEdit::VTextEdit(QWidget *p_parent)
  17. : QTextEdit(p_parent),
  18. m_imageMgr(nullptr)
  19. {
  20. init();
  21. }
  22. VTextEdit::VTextEdit(const QString &p_text, QWidget *p_parent)
  23. : QTextEdit(p_text, p_parent),
  24. m_imageMgr(nullptr)
  25. {
  26. init();
  27. }
  28. VTextEdit::~VTextEdit()
  29. {
  30. delete m_imageMgr;
  31. }
  32. void VTextEdit::init()
  33. {
  34. setAcceptRichText(false);
  35. m_defaultCursorWidth = 1;
  36. m_lineNumberType = LineNumberType::None;
  37. m_blockImageEnabled = false;
  38. m_cursorBlockMode = CursorBlock::None;
  39. m_highlightCursorLineBlock = false;
  40. m_enableExtraBuffer = false;
  41. m_imageMgr = new VImageResourceManager2();
  42. QTextDocument *doc = document();
  43. VTextDocumentLayout *docLayout = new VTextDocumentLayout(doc, m_imageMgr);
  44. docLayout->setBlockImageEnabled(m_blockImageEnabled);
  45. doc->setDocumentLayout(docLayout);
  46. docLayout->setVirtualCursorBlockWidth(VIRTUAL_CURSOR_BLOCK_WIDTH);
  47. docLayout->setCursorWidth(m_defaultCursorWidth);
  48. connect(docLayout, &VTextDocumentLayout::cursorBlockWidthUpdated,
  49. this, [this](int p_width) {
  50. if (p_width != cursorWidth()
  51. && p_width > VIRTUAL_CURSOR_BLOCK_WIDTH) {
  52. setCursorWidth(p_width);
  53. }
  54. });
  55. m_lineNumberArea = new VLineNumberArea(this,
  56. document(),
  57. fontMetrics().width(QLatin1Char('8')),
  58. this);
  59. connect(doc, &QTextDocument::blockCountChanged,
  60. this, &VTextEdit::updateLineNumberAreaMargin);
  61. connect(this, &QTextEdit::textChanged,
  62. this, &VTextEdit::updateLineNumberArea);
  63. connect(verticalScrollBar(), &QScrollBar::valueChanged,
  64. this, &VTextEdit::updateLineNumberArea);
  65. connect(this, &QTextEdit::cursorPositionChanged,
  66. this, [this]() {
  67. if (m_highlightCursorLineBlock) {
  68. QTextCursor cursor = textCursor();
  69. getLayout()->setCursorLineBlockNumber(cursor.block().blockNumber());
  70. }
  71. updateLineNumberArea();
  72. });
  73. }
  74. VTextDocumentLayout *VTextEdit::getLayout() const
  75. {
  76. return qobject_cast<VTextDocumentLayout *>(document()->documentLayout());
  77. }
  78. void VTextEdit::setLineLeading(qreal p_leading)
  79. {
  80. getLayout()->setLineLeading(p_leading);
  81. }
  82. void VTextEdit::resizeEvent(QResizeEvent *p_event)
  83. {
  84. QTextEdit::resizeEvent(p_event);
  85. QRect rect = contentsRect();
  86. if (m_lineNumberType != LineNumberType::None) {
  87. m_lineNumberArea->setGeometry(QRect(rect.left(),
  88. rect.top(),
  89. m_lineNumberArea->calculateWidth(),
  90. rect.height()));
  91. }
  92. if (m_enableExtraBuffer) {
  93. getLayout()->setExtraBufferHeight(rect.height() / 2);
  94. }
  95. }
  96. void VTextEdit::paintLineNumberArea(QPaintEvent *p_event)
  97. {
  98. if (m_lineNumberType == LineNumberType::None) {
  99. updateLineNumberAreaMargin();
  100. m_lineNumberArea->hide();
  101. return;
  102. }
  103. QPainter painter(m_lineNumberArea);
  104. painter.fillRect(p_event->rect(), m_lineNumberArea->getBackgroundColor());
  105. QTextBlock block = firstVisibleBlock();
  106. if (!block.isValid()) {
  107. return;
  108. }
  109. VTextDocumentLayout *layout = getLayout();
  110. Q_ASSERT(layout);
  111. int blockNumber = block.blockNumber();
  112. QRectF rect = layout->blockBoundingRect(block);
  113. int top = contentOffsetY() + (int)rect.y();
  114. int bottom = top + (int)rect.height();
  115. int eventTop = p_event->rect().top();
  116. int eventBtm = p_event->rect().bottom();
  117. const int digitHeight = painter.fontMetrics().height();
  118. const int curBlockNumber = textCursor().block().blockNumber();
  119. painter.setPen(m_lineNumberArea->getForegroundColor());
  120. const int leading = (int)layout->getLineLeading();
  121. // Display line number only in code block.
  122. if (m_lineNumberType == LineNumberType::CodeBlock) {
  123. int number = 0;
  124. while (block.isValid() && top <= eventBtm) {
  125. int blockState = block.userState();
  126. switch (blockState) {
  127. case (int)BlockState::CodeBlockStart:
  128. Q_ASSERT(number == 0);
  129. number = 1;
  130. break;
  131. case (int)BlockState::CodeBlockEnd:
  132. number = 0;
  133. break;
  134. case (int)BlockState::CodeBlock:
  135. if (number == 0) {
  136. // Need to find current line number in code block.
  137. QTextBlock startBlock = block.previous();
  138. while (startBlock.isValid()) {
  139. if (startBlock.userState() == (int)BlockState::CodeBlockStart) {
  140. number = block.blockNumber() - startBlock.blockNumber();
  141. break;
  142. }
  143. startBlock = startBlock.previous();
  144. }
  145. }
  146. break;
  147. default:
  148. break;
  149. }
  150. if (blockState == (int)BlockState::CodeBlock) {
  151. if (block.isVisible() && bottom >= eventTop) {
  152. QString numberStr = QString::number(number);
  153. painter.drawText(0,
  154. top + leading,
  155. m_lineNumberArea->width(),
  156. digitHeight,
  157. Qt::AlignRight | Qt::AlignTop,
  158. numberStr);
  159. }
  160. ++number;
  161. }
  162. block = block.next();
  163. top = bottom;
  164. bottom = top + (int)layout->blockBoundingRect(block).height();
  165. }
  166. return;
  167. }
  168. // Handle m_lineNumberType 1 and 2.
  169. Q_ASSERT(m_lineNumberType == LineNumberType::Absolute
  170. || m_lineNumberType == LineNumberType::Relative);
  171. while (block.isValid() && top <= eventBtm) {
  172. if (block.isVisible() && bottom >= eventTop) {
  173. bool currentLine = false;
  174. int number = blockNumber + 1;
  175. if (m_lineNumberType == LineNumberType::Relative) {
  176. number = blockNumber - curBlockNumber;
  177. if (number == 0) {
  178. currentLine = true;
  179. number = blockNumber + 1;
  180. } else if (number < 0) {
  181. number = -number;
  182. }
  183. } else if (blockNumber == curBlockNumber) {
  184. currentLine = true;
  185. }
  186. QString numberStr = QString::number(number);
  187. if (currentLine) {
  188. QFont font = painter.font();
  189. font.setBold(true);
  190. painter.setFont(font);
  191. }
  192. painter.drawText(0,
  193. top + leading,
  194. m_lineNumberArea->width(),
  195. digitHeight,
  196. Qt::AlignRight | Qt::AlignTop,
  197. numberStr);
  198. if (currentLine) {
  199. QFont font = painter.font();
  200. font.setBold(false);
  201. painter.setFont(font);
  202. }
  203. }
  204. block = block.next();
  205. top = bottom;
  206. bottom = top + (int)layout->blockBoundingRect(block).height();
  207. ++blockNumber;
  208. }
  209. }
  210. void VTextEdit::updateLineNumberAreaMargin()
  211. {
  212. int width = 0;
  213. if (m_lineNumberType != LineNumberType::None) {
  214. width = m_lineNumberArea->calculateWidth();
  215. }
  216. if (width != viewportMargins().left()) {
  217. setViewportMargins(width, 0, 0, 0);
  218. }
  219. }
  220. void VTextEdit::updateLineNumberArea()
  221. {
  222. if (m_lineNumberType != LineNumberType::None) {
  223. if (!m_lineNumberArea->isVisible()) {
  224. updateLineNumberAreaMargin();
  225. m_lineNumberArea->show();
  226. }
  227. m_lineNumberArea->update();
  228. } else if (m_lineNumberArea->isVisible()) {
  229. updateLineNumberAreaMargin();
  230. m_lineNumberArea->hide();
  231. }
  232. }
  233. int VTextEdit::firstVisibleBlockNumber() const
  234. {
  235. VTextDocumentLayout *layout = getLayout();
  236. Q_ASSERT(layout);
  237. return layout->findBlockByPosition(QPointF(0, -contentOffsetY()));
  238. }
  239. QTextBlock VTextEdit::firstVisibleBlock() const
  240. {
  241. VTextDocumentLayout *layout = getLayout();
  242. Q_ASSERT(layout);
  243. int blockNumber = layout->findBlockByPosition(QPointF(0, -contentOffsetY()));
  244. return document()->findBlockByNumber(blockNumber);
  245. }
  246. QTextBlock VTextEdit::lastVisibleBlock() const
  247. {
  248. VTextDocumentLayout *layout = getLayout();
  249. Q_ASSERT(layout);
  250. int blockNumber = layout->findBlockByPosition(QPointF(0, -contentOffsetY() + contentsRect().height()));
  251. return document()->findBlockByNumber(blockNumber);
  252. }
  253. void VTextEdit::visibleBlockRange(int &p_first, int &p_last) const
  254. {
  255. VTextDocumentLayout *layout = getLayout();
  256. Q_ASSERT(layout);
  257. p_first = layout->findBlockByPosition(QPointF(0, -contentOffsetY()));
  258. p_last = layout->findBlockByPosition(QPointF(0, -contentOffsetY() + contentsRect().height()));
  259. }
  260. int VTextEdit::contentOffsetY() const
  261. {
  262. QScrollBar *sb = verticalScrollBar();
  263. return -(sb->value());
  264. }
  265. void VTextEdit::clearBlockImages()
  266. {
  267. m_imageMgr->clear();
  268. getLayout()->relayout();
  269. }
  270. void VTextEdit::relayout(const OrderedIntSet &p_blocks)
  271. {
  272. if (p_blocks.isEmpty()) {
  273. return;
  274. }
  275. getLayout()->relayout(p_blocks);
  276. updateLineNumberArea();
  277. }
  278. void VTextEdit::relayoutVisibleBlocks()
  279. {
  280. int first, last;
  281. visibleBlockRange(first, last);
  282. OrderedIntSet blocks;
  283. for (int i = first; i <= last; ++i) {
  284. blocks.insert(i, QMapDummyValue());
  285. }
  286. getLayout()->relayout(blocks);
  287. updateLineNumberArea();
  288. }
  289. bool VTextEdit::containsImage(const QString &p_imageName) const
  290. {
  291. return m_imageMgr->contains(p_imageName);
  292. }
  293. QSize VTextEdit::imageSize(const QString &p_imageName) const
  294. {
  295. const QPixmap *img = m_imageMgr->findImage(p_imageName);
  296. if (img) {
  297. return img->size();
  298. }
  299. return QSize();
  300. }
  301. const QPixmap *VTextEdit::findImage(const QString &p_name) const
  302. {
  303. return m_imageMgr->findImage(p_name);
  304. }
  305. void VTextEdit::addImage(const QString &p_imageName, const QPixmap &p_image)
  306. {
  307. if (m_blockImageEnabled) {
  308. m_imageMgr->addImage(p_imageName, p_image);
  309. }
  310. }
  311. void VTextEdit::removeImage(const QString &p_imageName)
  312. {
  313. m_imageMgr->removeImage(p_imageName);
  314. }
  315. void VTextEdit::setBlockImageEnabled(bool p_enabled)
  316. {
  317. if (m_blockImageEnabled == p_enabled) {
  318. return;
  319. }
  320. m_blockImageEnabled = p_enabled;
  321. getLayout()->setBlockImageEnabled(m_blockImageEnabled);
  322. if (!m_blockImageEnabled) {
  323. clearBlockImages();
  324. }
  325. }
  326. void VTextEdit::setImageWidthConstrainted(bool p_enabled)
  327. {
  328. getLayout()->setImageWidthConstrainted(p_enabled);
  329. }
  330. void VTextEdit::setImageLineColor(const QColor &p_color)
  331. {
  332. getLayout()->setImageLineColor(p_color);
  333. }
  334. void VTextEdit::setCursorBlockMode(CursorBlock p_mode)
  335. {
  336. VTextDocumentLayout *layout = getLayout();
  337. if (p_mode != m_cursorBlockMode) {
  338. m_cursorBlockMode = p_mode;
  339. layout->setCursorBlockMode(m_cursorBlockMode);
  340. layout->clearLastCursorBlockWidth();
  341. setCursorWidth(m_cursorBlockMode != CursorBlock::None ? VIRTUAL_CURSOR_BLOCK_WIDTH
  342. : m_defaultCursorWidth);
  343. layout->updateBlockByNumber(textCursor().blockNumber());
  344. }
  345. }
  346. void VTextEdit::setHighlightCursorLineBlockEnabled(bool p_enabled)
  347. {
  348. if (m_highlightCursorLineBlock != p_enabled) {
  349. auto layout = getLayout();
  350. m_highlightCursorLineBlock = p_enabled;
  351. layout->setHighlightCursorLineBlockEnabled(p_enabled);
  352. if (m_highlightCursorLineBlock) {
  353. QTextCursor cursor = textCursor();
  354. layout->setCursorLineBlockNumber(cursor.block().blockNumber());
  355. }
  356. }
  357. }
  358. void VTextEdit::setCursorLineBlockBg(const QColor &p_bg)
  359. {
  360. getLayout()->setCursorLineBlockBg(p_bg);
  361. }
  362. void VTextEdit::relayout()
  363. {
  364. getLayout()->relayout();
  365. updateLineNumberArea();
  366. }
  367. void VTextEdit::setDisplayScaleFactor(qreal p_factor)
  368. {
  369. m_defaultCursorWidth = p_factor + 0.5;
  370. setCursorWidth(m_cursorBlockMode != CursorBlock::None ? VIRTUAL_CURSOR_BLOCK_WIDTH
  371. : m_defaultCursorWidth);
  372. getLayout()->setCursorWidth(m_defaultCursorWidth);
  373. }
  374. void VTextEdit::updateLineNumberAreaWidth(const QFontMetrics &p_metrics)
  375. {
  376. m_lineNumberArea->setDigitWidth(p_metrics.width(QLatin1Char('8')));
  377. updateLineNumberAreaMargin();
  378. }
  379. void VTextEdit::dragMoveEvent(QDragMoveEvent *p_event)
  380. {
  381. QTextEdit::dragMoveEvent(p_event);
  382. // We need to update the cursor rect to show the cursor while dragging text.
  383. // This is a work-around. We do not know why VTextEdit won't update the cursor
  384. // rect to show the cursor.
  385. // TODO: find out the rect of current cursor to update that rect only.
  386. update();
  387. }
  388. int VTextEdit::lineNumberAreaWidth() const
  389. {
  390. return m_lineNumberArea->width();
  391. }