fb2code.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. #include "fb2code.hpp"
  2. #include "fb2dlgs.hpp"
  3. #ifdef FB2_USE_SCINTILLA
  4. /////////////////////////////////////////////////////////////////////////////
  5. //
  6. // http://qtcoder.blogspot.com/2010/10/qscintills.html
  7. // http://www.riverbankcomputing.co.uk/static/Docs/QScintilla2/classQsciScintilla.html
  8. //
  9. /////////////////////////////////////////////////////////////////////////////
  10. #include <Qsci/qscilexerxml.h>
  11. #include <QtDebug>
  12. Fb2CodeEdit::Fb2CodeEdit(QWidget *parent) :
  13. QsciScintilla(parent)
  14. {
  15. zoomTo(1);
  16. setUtf8(true);
  17. setCaretLineVisible(true);
  18. setCaretLineBackgroundColor(QColor("gainsboro"));
  19. setWrapMode(QsciScintilla::WrapWord);
  20. /*
  21. //setup autocompletion
  22. setAutoCompletionSource(QsciScintilla::AcsAll);
  23. setAutoCompletionCaseSensitivity(true);
  24. setAutoCompletionReplaceWord(true);
  25. setAutoCompletionShowSingle(true);
  26. setAutoCompletionThreshold(2);
  27. */
  28. //setup margins
  29. setMarginsBackgroundColor(QColor("gainsboro"));
  30. setMarginLineNumbers(0, true);
  31. setFolding(QsciScintilla::BoxedFoldStyle, 1);
  32. //setup brace matching
  33. setBraceMatching(QsciScintilla::SloppyBraceMatch);
  34. setMatchedBraceBackgroundColor(Qt::yellow);
  35. setUnmatchedBraceForegroundColor(Qt::blue);
  36. //setup end-of-line mode
  37. #if defined Q_WS_X11
  38. setEolMode(QsciScintilla::EolUnix);
  39. #elif defined Q_WS_WIN
  40. setEolMode(QsciScintilla::EolWindows);
  41. #elif defined Q_WS_MAC
  42. setEolMode(QsciScintilla::EolMac);
  43. #endif
  44. //setup auto-indentation
  45. setAutoIndent(true);
  46. setIndentationGuides(true);
  47. setIndentationsUseTabs(false);
  48. setIndentationWidth(2);
  49. QsciLexerXML * lexer = new QsciLexerXML;
  50. lexer->setFoldPreprocessor(true);
  51. #ifdef Q_WS_WIN
  52. lexer->setFont(QFont("Courier New", 8));
  53. #else
  54. lexer->setFont(QFont("Monospace", 8));
  55. #endif
  56. setLexer(lexer);
  57. connect(this, SIGNAL(linesChanged()), SLOT(linesChanged()));
  58. }
  59. void Fb2CodeEdit::linesChanged()
  60. {
  61. QString width = QString().setNum(lines() * 10);
  62. setMarginWidth(0, width);
  63. }
  64. void Fb2CodeEdit::load(const QByteArray &array, const QList<int> &folds)
  65. {
  66. SendScintilla(SCI_SETTEXT, array.constData());
  67. SendScintilla(SCI_EMPTYUNDOBUFFER);
  68. foldAll(false);
  69. foldLine(1);
  70. for (QList<int>::const_iterator it = folds.constBegin(); it != folds.constEnd(); it++) {
  71. foldLine(*it);
  72. }
  73. }
  74. void Fb2CodeEdit::zoomReset()
  75. {
  76. zoomTo(1);
  77. }
  78. #endif // FB2_USE_SCINTILLA
  79. #ifdef FB2_USE_PLAINTEXT
  80. #include <QtGui>
  81. static const QColor DEFAULT_SYNTAX_CHAR = Qt::blue;
  82. static const QColor DEFAULT_ELEMENT_NAME = Qt::darkRed;
  83. static const QColor DEFAULT_COMMENT = Qt::darkGreen;
  84. static const QColor DEFAULT_ATTRIBUTE_NAME = Qt::red;
  85. static const QColor DEFAULT_ATTRIBUTE_VALUE = Qt::darkGreen;
  86. static const QColor DEFAULT_ERROR = Qt::darkMagenta;
  87. static const QColor DEFAULT_OTHER = Qt::black;
  88. // Regular expressions for parsing XML borrowed from:
  89. // http://www.cs.sfu.ca/~cameron/REX.html
  90. static const QString EXPR_COMMENT = "<!--[^-]*-([^-][^-]*-)*->";
  91. static const QString EXPR_COMMENT_BEGIN = "<!--";
  92. static const QString EXPR_COMMENT_END = "[^-]*-([^-][^-]*-)*->";
  93. static const QString EXPR_ATTRIBUTE_VALUE = "\"[^<\"]*\"|'[^<']*'";
  94. static const QString EXPR_NAME = "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";
  95. Fb2Highlighter::Fb2Highlighter(QObject* parent)
  96. : QSyntaxHighlighter(parent)
  97. {
  98. init();
  99. }
  100. Fb2Highlighter::Fb2Highlighter(QTextDocument* parent)
  101. : QSyntaxHighlighter(parent)
  102. {
  103. init();
  104. }
  105. Fb2Highlighter::Fb2Highlighter(QTextEdit* parent)
  106. : QSyntaxHighlighter(parent)
  107. {
  108. init();
  109. }
  110. Fb2Highlighter::~Fb2Highlighter()
  111. {
  112. }
  113. void Fb2Highlighter::init()
  114. {
  115. fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
  116. fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
  117. fmtComment.setForeground(DEFAULT_COMMENT);
  118. fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
  119. fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
  120. fmtError.setForeground(DEFAULT_ERROR);
  121. fmtOther.setForeground(DEFAULT_OTHER);
  122. }
  123. void Fb2Highlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
  124. {
  125. QTextCharFormat format;
  126. if (foreground)
  127. format.setForeground(color);
  128. else
  129. format.setBackground(color);
  130. setHighlightFormat(type, format);
  131. }
  132. void Fb2Highlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
  133. {
  134. switch (type)
  135. {
  136. case SyntaxChar:
  137. fmtSyntaxChar = format;
  138. break;
  139. case ElementName:
  140. fmtElementName = format;
  141. break;
  142. case Comment:
  143. fmtComment = format;
  144. break;
  145. case AttributeName:
  146. fmtAttributeName = format;
  147. break;
  148. case AttributeValue:
  149. fmtAttributeValue = format;
  150. break;
  151. case Error:
  152. fmtError = format;
  153. break;
  154. case Other:
  155. fmtOther = format;
  156. break;
  157. }
  158. rehighlight();
  159. }
  160. void Fb2Highlighter::highlightBlock(const QString& text)
  161. {
  162. int i = 0;
  163. int pos = 0;
  164. int brackets = 0;
  165. state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
  166. if (previousBlockState() == InComment)
  167. {
  168. // search for the end of the comment
  169. QRegExp expression(EXPR_COMMENT_END);
  170. pos = expression.indexIn(text, i);
  171. if (pos >= 0)
  172. {
  173. // end comment found
  174. const int iLength = expression.matchedLength();
  175. setFormat(0, iLength - 3, fmtComment);
  176. setFormat(iLength - 3, 3, fmtSyntaxChar);
  177. i += iLength; // skip comment
  178. }
  179. else
  180. {
  181. // in comment
  182. setFormat(0, text.length(), fmtComment);
  183. setCurrentBlockState(InComment);
  184. return;
  185. }
  186. }
  187. for (; i < text.length(); i++)
  188. {
  189. switch (text.at(i).toAscii())
  190. {
  191. case '<':
  192. brackets++;
  193. if (brackets == 1)
  194. {
  195. setFormat(i, 1, fmtSyntaxChar);
  196. state = ExpectElementNameOrSlash;
  197. }
  198. else
  199. {
  200. // wrong bracket nesting
  201. setFormat(i, 1, fmtError);
  202. }
  203. break;
  204. case '>':
  205. brackets--;
  206. if (brackets == 0)
  207. {
  208. setFormat(i, 1, fmtSyntaxChar);
  209. }
  210. else
  211. {
  212. // wrong bracket nesting
  213. setFormat( i, 1, fmtError);
  214. }
  215. state = NoState;
  216. break;
  217. case '/':
  218. if (state == ExpectElementNameOrSlash)
  219. {
  220. state = ExpectElementName;
  221. setFormat(i, 1, fmtSyntaxChar);
  222. }
  223. else
  224. {
  225. if (state == ExpectAttributeOrEndOfElement)
  226. {
  227. setFormat(i, 1, fmtSyntaxChar);
  228. }
  229. else
  230. {
  231. processDefaultText(i, text);
  232. }
  233. }
  234. break;
  235. case '=':
  236. if (state == ExpectEqual)
  237. {
  238. state = ExpectAttributeValue;
  239. setFormat(i, 1, fmtOther);
  240. }
  241. else
  242. {
  243. processDefaultText(i, text);
  244. }
  245. break;
  246. case '\'':
  247. case '\"':
  248. if (state == ExpectAttributeValue)
  249. {
  250. // search attribute value
  251. QRegExp expression(EXPR_ATTRIBUTE_VALUE);
  252. pos = expression.indexIn(text, i);
  253. if (pos == i) // attribute value found ?
  254. {
  255. const int iLength = expression.matchedLength();
  256. setFormat(i, 1, fmtOther);
  257. setFormat(i + 1, iLength - 2, fmtAttributeValue);
  258. setFormat(i + iLength - 1, 1, fmtOther);
  259. i += iLength - 1; // skip attribute value
  260. state = ExpectAttributeOrEndOfElement;
  261. }
  262. else
  263. {
  264. processDefaultText(i, text);
  265. }
  266. }
  267. else
  268. {
  269. processDefaultText(i, text);
  270. }
  271. break;
  272. case '!':
  273. if (state == ExpectElementNameOrSlash)
  274. {
  275. // search comment
  276. QRegExp expression(EXPR_COMMENT);
  277. pos = expression.indexIn(text, i - 1);
  278. if (pos == i - 1) // comment found ?
  279. {
  280. const int iLength = expression.matchedLength();
  281. setFormat(pos, 4, fmtSyntaxChar);
  282. setFormat(pos + 4, iLength - 7, fmtComment);
  283. setFormat(iLength - 3, 3, fmtSyntaxChar);
  284. i += iLength - 2; // skip comment
  285. state = NoState;
  286. brackets--;
  287. }
  288. else
  289. {
  290. // Try find multiline comment
  291. QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
  292. pos = expression.indexIn(text, i - 1);
  293. //if (pos == i - 1) // comment found ?
  294. if (pos >= i - 1)
  295. {
  296. setFormat(i, 3, fmtSyntaxChar);
  297. setFormat(i + 3, text.length() - i - 3, fmtComment);
  298. setCurrentBlockState(InComment);
  299. return;
  300. }
  301. else
  302. {
  303. processDefaultText(i, text);
  304. }
  305. }
  306. }
  307. else
  308. {
  309. processDefaultText(i, text);
  310. }
  311. break;
  312. default:
  313. const int iLength = processDefaultText(i, text);
  314. if (iLength > 0)
  315. i += iLength - 1;
  316. break;
  317. }
  318. }
  319. if (state == ExpectAttributeOrEndOfElement)
  320. {
  321. setCurrentBlockState(InElement);
  322. }
  323. }
  324. int Fb2Highlighter::processDefaultText(int i, const QString& text)
  325. {
  326. // length of matched text
  327. int iLength = 0;
  328. switch(state)
  329. {
  330. case ExpectElementNameOrSlash:
  331. case ExpectElementName:
  332. {
  333. // search element name
  334. QRegExp expression(EXPR_NAME);
  335. const int pos = expression.indexIn(text, i);
  336. if (pos == i) // found ?
  337. {
  338. iLength = expression.matchedLength();
  339. setFormat(pos, iLength, fmtElementName);
  340. state = ExpectAttributeOrEndOfElement;
  341. }
  342. else
  343. {
  344. setFormat(i, 1, fmtOther);
  345. }
  346. }
  347. break;
  348. case ExpectAttributeOrEndOfElement:
  349. {
  350. // search attribute name
  351. QRegExp expression(EXPR_NAME);
  352. const int pos = expression.indexIn(text, i);
  353. if (pos == i) // found ?
  354. {
  355. iLength = expression.matchedLength();
  356. setFormat(pos, iLength, fmtAttributeName);
  357. state = ExpectEqual;
  358. }
  359. else
  360. {
  361. setFormat(i, 1, fmtOther);
  362. }
  363. }
  364. break;
  365. default:
  366. setFormat(i, 1, fmtOther);
  367. break;
  368. }
  369. return iLength;
  370. }
  371. qreal Fb2CodeEdit::baseFontSize = 10;
  372. qreal Fb2CodeEdit::zoomRatioMin = 0.2;
  373. qreal Fb2CodeEdit::zoomRatioMax = 5.0;
  374. Fb2CodeEdit::Fb2CodeEdit(QWidget *parent) : QPlainTextEdit(parent)
  375. {
  376. lineNumberArea = new LineNumberArea(this);
  377. highlighter = new Fb2Highlighter(this);
  378. highlighter->setDocument( document() );
  379. connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
  380. connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
  381. connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  382. zoomRatio = 1;
  383. #ifdef Q_WS_WIN
  384. setFont(QFont("Courier New", baseFontSize));
  385. #else
  386. setFont(QFont("Monospace", baseFontSize));
  387. #endif
  388. updateLineNumberAreaWidth(0);
  389. highlightCurrentLine();
  390. }
  391. int Fb2CodeEdit::lineNumberAreaWidth()
  392. {
  393. int digits = 1;
  394. int max = qMax(1, blockCount());
  395. while (max >= 10) {
  396. max /= 10;
  397. ++digits;
  398. }
  399. int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
  400. return space;
  401. }
  402. void Fb2CodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
  403. {
  404. setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
  405. }
  406. void Fb2CodeEdit::updateLineNumberArea(const QRect &rect, int dy)
  407. {
  408. if (dy)
  409. lineNumberArea->scroll(0, dy);
  410. else
  411. lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
  412. if (rect.contains(viewport()->rect()))
  413. updateLineNumberAreaWidth(0);
  414. }
  415. void Fb2CodeEdit::resizeEvent(QResizeEvent *e)
  416. {
  417. QPlainTextEdit::resizeEvent(e);
  418. QRect cr = contentsRect();
  419. lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
  420. }
  421. void Fb2CodeEdit::highlightCurrentLine()
  422. {
  423. QList<QTextEdit::ExtraSelection> extraSelections;
  424. if (!isReadOnly()) {
  425. QTextEdit::ExtraSelection selection;
  426. QColor lineColor = QColor(Qt::yellow).lighter(160);
  427. selection.format.setBackground(lineColor);
  428. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  429. selection.cursor = textCursor();
  430. selection.cursor.clearSelection();
  431. extraSelections.append(selection);
  432. }
  433. setExtraSelections(extraSelections);
  434. }
  435. void Fb2CodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
  436. {
  437. QPainter painter(lineNumberArea);
  438. painter.fillRect(event->rect(), Qt::lightGray);
  439. QTextBlock block = firstVisibleBlock();
  440. int blockNumber = block.blockNumber();
  441. int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
  442. int bottom = top + (int) blockBoundingRect(block).height();
  443. while (block.isValid() && top <= event->rect().bottom()) {
  444. if (block.isVisible() && bottom >= event->rect().top()) {
  445. QString number = QString::number(blockNumber + 1);
  446. painter.setPen(Qt::black);
  447. painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
  448. Qt::AlignRight, number);
  449. }
  450. block = block.next();
  451. top = bottom;
  452. bottom = top + (int) blockBoundingRect(block).height();
  453. ++blockNumber;
  454. }
  455. }
  456. bool Fb2CodeEdit::findText(const QString &exp, QTextDocument::FindFlags options)
  457. {
  458. return QPlainTextEdit::find(exp, options);
  459. }
  460. void Fb2CodeEdit::find()
  461. {
  462. Fb2CodeFindDlg dlg(*this);
  463. dlg.exec();
  464. }
  465. void Fb2CodeEdit::zoomIn()
  466. {
  467. qreal ratio = zoomRatio * 1.1;
  468. ratio = qMin(ratio, zoomRatioMax);
  469. setZoomRatio(ratio);
  470. }
  471. void Fb2CodeEdit::zoomOut()
  472. {
  473. qreal ratio = zoomRatio / 1.1;
  474. ratio = qMax(ratio, zoomRatioMin);
  475. setZoomRatio(ratio);
  476. }
  477. void Fb2CodeEdit::zoomReset()
  478. {
  479. setZoomRatio(1.0);
  480. }
  481. void Fb2CodeEdit::setZoomRatio(qreal ratio)
  482. {
  483. if (!qFuzzyCompare(1 + zoomRatio, 1 + ratio)) {
  484. zoomRatio = ratio;
  485. QFont f = font();
  486. f.setPointSizeF(baseFontSize * zoomRatio);
  487. setFont(f);
  488. }
  489. }
  490. #endif // FB2_USE_PLAINTEXT