fb2code.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  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 <QXmlInputSource>
  81. #include <QtGui>
  82. static const QColor DEFAULT_SYNTAX_CHAR = Qt::blue;
  83. static const QColor DEFAULT_ELEMENT_NAME = Qt::darkRed;
  84. static const QColor DEFAULT_COMMENT = Qt::darkGray;
  85. static const QColor DEFAULT_ATTRIBUTE_NAME = Qt::red;
  86. static const QColor DEFAULT_ATTRIBUTE_VALUE = Qt::darkGreen;
  87. static const QColor DEFAULT_ERROR = Qt::darkMagenta;
  88. static const QColor DEFAULT_OTHER = Qt::black;
  89. // Regular expressions for parsing XML borrowed from:
  90. // http://www.cs.sfu.ca/~cameron/REX.html
  91. static const QString EXPR_COMMENT = "<!--[^-]*-([^-][^-]*-)*->";
  92. static const QString EXPR_COMMENT_BEGIN = "<!--";
  93. static const QString EXPR_COMMENT_END = "[^-]*-([^-][^-]*-)*->";
  94. static const QString EXPR_ATTRIBUTE_VALUE = "\"[^<\"]*\"|'[^<']*'";
  95. static const QString EXPR_NAME = "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";
  96. Fb2Highlighter::Fb2Highlighter(QObject* parent)
  97. : QSyntaxHighlighter(parent)
  98. {
  99. init();
  100. }
  101. Fb2Highlighter::Fb2Highlighter(QTextDocument* parent)
  102. : QSyntaxHighlighter(parent)
  103. {
  104. init();
  105. }
  106. Fb2Highlighter::Fb2Highlighter(QTextEdit* parent)
  107. : QSyntaxHighlighter(parent)
  108. {
  109. init();
  110. }
  111. Fb2Highlighter::~Fb2Highlighter()
  112. {
  113. }
  114. void Fb2Highlighter::init()
  115. {
  116. fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
  117. fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
  118. fmtComment.setForeground(DEFAULT_COMMENT);
  119. fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
  120. fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
  121. fmtError.setForeground(DEFAULT_ERROR);
  122. fmtOther.setForeground(DEFAULT_OTHER);
  123. }
  124. void Fb2Highlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
  125. {
  126. QTextCharFormat format;
  127. if (foreground)
  128. format.setForeground(color);
  129. else
  130. format.setBackground(color);
  131. setHighlightFormat(type, format);
  132. }
  133. void Fb2Highlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
  134. {
  135. switch (type)
  136. {
  137. case SyntaxChar:
  138. fmtSyntaxChar = format;
  139. break;
  140. case ElementName:
  141. fmtElementName = format;
  142. break;
  143. case Comment:
  144. fmtComment = format;
  145. break;
  146. case AttributeName:
  147. fmtAttributeName = format;
  148. break;
  149. case AttributeValue:
  150. fmtAttributeValue = format;
  151. break;
  152. case Error:
  153. fmtError = format;
  154. break;
  155. case Other:
  156. fmtOther = format;
  157. break;
  158. }
  159. rehighlight();
  160. }
  161. void Fb2Highlighter::highlightBlock(const QString& text)
  162. {
  163. int i = 0;
  164. int pos = 0;
  165. int brackets = 0;
  166. state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
  167. if (previousBlockState() == InComment)
  168. {
  169. // search for the end of the comment
  170. QRegExp expression(EXPR_COMMENT_END);
  171. pos = expression.indexIn(text, i);
  172. if (pos >= 0)
  173. {
  174. // end comment found
  175. const int iLength = expression.matchedLength();
  176. setFormat(0, iLength - 3, fmtComment);
  177. setFormat(iLength - 3, 3, fmtSyntaxChar);
  178. i += iLength; // skip comment
  179. }
  180. else
  181. {
  182. // in comment
  183. setFormat(0, text.length(), fmtComment);
  184. setCurrentBlockState(InComment);
  185. return;
  186. }
  187. }
  188. for (; i < text.length(); i++)
  189. {
  190. switch (text.at(i).toAscii())
  191. {
  192. case '<':
  193. brackets++;
  194. if (brackets == 1)
  195. {
  196. setFormat(i, 1, fmtSyntaxChar);
  197. state = ExpectElementNameOrSlash;
  198. }
  199. else
  200. {
  201. // wrong bracket nesting
  202. setFormat(i, 1, fmtError);
  203. }
  204. break;
  205. case '>':
  206. brackets--;
  207. if (brackets == 0)
  208. {
  209. setFormat(i, 1, fmtSyntaxChar);
  210. }
  211. else
  212. {
  213. // wrong bracket nesting
  214. setFormat( i, 1, fmtError);
  215. }
  216. state = NoState;
  217. break;
  218. case '/':
  219. if (state == ExpectElementNameOrSlash)
  220. {
  221. state = ExpectElementName;
  222. setFormat(i, 1, fmtSyntaxChar);
  223. }
  224. else
  225. {
  226. if (state == ExpectAttributeOrEndOfElement)
  227. {
  228. setFormat(i, 1, fmtSyntaxChar);
  229. }
  230. else
  231. {
  232. processDefaultText(i, text);
  233. }
  234. }
  235. break;
  236. case '=':
  237. if (state == ExpectEqual)
  238. {
  239. state = ExpectAttributeValue;
  240. setFormat(i, 1, fmtOther);
  241. }
  242. else
  243. {
  244. processDefaultText(i, text);
  245. }
  246. break;
  247. case '\'':
  248. case '\"':
  249. if (state == ExpectAttributeValue)
  250. {
  251. // search attribute value
  252. QRegExp expression(EXPR_ATTRIBUTE_VALUE);
  253. pos = expression.indexIn(text, i);
  254. if (pos == i) // attribute value found ?
  255. {
  256. const int iLength = expression.matchedLength();
  257. setFormat(i, 1, fmtOther);
  258. setFormat(i + 1, iLength - 2, fmtAttributeValue);
  259. setFormat(i + iLength - 1, 1, fmtOther);
  260. i += iLength - 1; // skip attribute value
  261. state = ExpectAttributeOrEndOfElement;
  262. }
  263. else
  264. {
  265. processDefaultText(i, text);
  266. }
  267. }
  268. else
  269. {
  270. processDefaultText(i, text);
  271. }
  272. break;
  273. case '!':
  274. if (state == ExpectElementNameOrSlash)
  275. {
  276. // search comment
  277. QRegExp expression(EXPR_COMMENT);
  278. pos = expression.indexIn(text, i - 1);
  279. if (pos == i - 1) // comment found ?
  280. {
  281. const int iLength = expression.matchedLength();
  282. setFormat(pos, 4, fmtSyntaxChar);
  283. setFormat(pos + 4, iLength - 7, fmtComment);
  284. setFormat(iLength - 3, 3, fmtSyntaxChar);
  285. i += iLength - 2; // skip comment
  286. state = NoState;
  287. brackets--;
  288. }
  289. else
  290. {
  291. // Try find multiline comment
  292. QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
  293. pos = expression.indexIn(text, i - 1);
  294. //if (pos == i - 1) // comment found ?
  295. if (pos >= i - 1)
  296. {
  297. setFormat(i, 3, fmtSyntaxChar);
  298. setFormat(i + 3, text.length() - i - 3, fmtComment);
  299. setCurrentBlockState(InComment);
  300. return;
  301. }
  302. else
  303. {
  304. processDefaultText(i, text);
  305. }
  306. }
  307. }
  308. else
  309. {
  310. processDefaultText(i, text);
  311. }
  312. break;
  313. default:
  314. const int iLength = processDefaultText(i, text);
  315. if (iLength > 0)
  316. i += iLength - 1;
  317. break;
  318. }
  319. }
  320. if (state == ExpectAttributeOrEndOfElement)
  321. {
  322. setCurrentBlockState(InElement);
  323. }
  324. }
  325. int Fb2Highlighter::processDefaultText(int i, const QString& text)
  326. {
  327. // length of matched text
  328. int iLength = 0;
  329. switch(state)
  330. {
  331. case ExpectElementNameOrSlash:
  332. case ExpectElementName:
  333. {
  334. // search element name
  335. QRegExp expression(EXPR_NAME);
  336. const int pos = expression.indexIn(text, i);
  337. if (pos == i) // found ?
  338. {
  339. iLength = expression.matchedLength();
  340. setFormat(pos, iLength, fmtElementName);
  341. state = ExpectAttributeOrEndOfElement;
  342. }
  343. else
  344. {
  345. setFormat(i, 1, fmtOther);
  346. }
  347. }
  348. break;
  349. case ExpectAttributeOrEndOfElement:
  350. {
  351. // search attribute name
  352. QRegExp expression(EXPR_NAME);
  353. const int pos = expression.indexIn(text, i);
  354. if (pos == i) // found ?
  355. {
  356. iLength = expression.matchedLength();
  357. setFormat(pos, iLength, fmtAttributeName);
  358. state = ExpectEqual;
  359. }
  360. else
  361. {
  362. setFormat(i, 1, fmtOther);
  363. }
  364. }
  365. break;
  366. default:
  367. setFormat(i, 1, fmtOther);
  368. break;
  369. }
  370. return iLength;
  371. }
  372. qreal Fb2CodeEdit::baseFontSize = 10;
  373. qreal Fb2CodeEdit::zoomRatioMin = 0.2;
  374. qreal Fb2CodeEdit::zoomRatioMax = 5.0;
  375. Fb2CodeEdit::Fb2CodeEdit(QWidget *parent) : QPlainTextEdit(parent)
  376. {
  377. lineNumberArea = new LineNumberArea(this);
  378. highlighter = new Fb2Highlighter(this);
  379. highlighter->setDocument( document() );
  380. connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
  381. connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
  382. connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  383. zoomRatio = 1;
  384. #ifdef Q_WS_WIN
  385. setFont(QFont("Courier New", baseFontSize));
  386. #else
  387. setFont(QFont("Monospace", baseFontSize));
  388. #endif
  389. updateLineNumberAreaWidth(0);
  390. highlightCurrentLine();
  391. }
  392. bool Fb2CodeEdit::read(QIODevice *device)
  393. {
  394. QByteArray data = device->readAll();
  395. QXmlInputSource source;
  396. source.setData(data);
  397. setPlainText(source.data());
  398. return true;
  399. }
  400. int Fb2CodeEdit::lineNumberAreaWidth()
  401. {
  402. int digits = 1;
  403. int max = qMax(1, blockCount());
  404. while (max >= 10) {
  405. max /= 10;
  406. ++digits;
  407. }
  408. int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
  409. return space;
  410. }
  411. void Fb2CodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
  412. {
  413. setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
  414. }
  415. void Fb2CodeEdit::updateLineNumberArea(const QRect &rect, int dy)
  416. {
  417. if (dy)
  418. lineNumberArea->scroll(0, dy);
  419. else
  420. lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
  421. if (rect.contains(viewport()->rect()))
  422. updateLineNumberAreaWidth(0);
  423. }
  424. void Fb2CodeEdit::resizeEvent(QResizeEvent *e)
  425. {
  426. QPlainTextEdit::resizeEvent(e);
  427. QRect cr = contentsRect();
  428. lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
  429. }
  430. void Fb2CodeEdit::highlightCurrentLine()
  431. {
  432. QList<QTextEdit::ExtraSelection> extraSelections;
  433. if (!isReadOnly()) {
  434. QTextEdit::ExtraSelection selection;
  435. QColor lineColor = QColor(Qt::yellow).lighter(160);
  436. selection.format.setBackground(lineColor);
  437. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  438. selection.cursor = textCursor();
  439. selection.cursor.clearSelection();
  440. extraSelections.append(selection);
  441. }
  442. setExtraSelections(extraSelections);
  443. }
  444. void Fb2CodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
  445. {
  446. QPainter painter(lineNumberArea);
  447. painter.fillRect(event->rect(), Qt::lightGray);
  448. QTextBlock block = firstVisibleBlock();
  449. int blockNumber = block.blockNumber();
  450. int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
  451. int bottom = top + (int) blockBoundingRect(block).height();
  452. while (block.isValid() && top <= event->rect().bottom()) {
  453. if (block.isVisible() && bottom >= event->rect().top()) {
  454. QString number = QString::number(blockNumber + 1);
  455. painter.setPen(Qt::black);
  456. painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
  457. Qt::AlignRight, number);
  458. }
  459. block = block.next();
  460. top = bottom;
  461. bottom = top + (int) blockBoundingRect(block).height();
  462. ++blockNumber;
  463. }
  464. }
  465. bool Fb2CodeEdit::findText(const QString &exp, QTextDocument::FindFlags options)
  466. {
  467. return QPlainTextEdit::find(exp, options);
  468. }
  469. void Fb2CodeEdit::find()
  470. {
  471. Fb2CodeFindDlg dlg(*this);
  472. dlg.exec();
  473. }
  474. void Fb2CodeEdit::zoomIn()
  475. {
  476. qreal ratio = zoomRatio * 1.1;
  477. ratio = qMin(ratio, zoomRatioMax);
  478. setZoomRatio(ratio);
  479. }
  480. void Fb2CodeEdit::zoomOut()
  481. {
  482. qreal ratio = zoomRatio / 1.1;
  483. ratio = qMax(ratio, zoomRatioMin);
  484. setZoomRatio(ratio);
  485. }
  486. void Fb2CodeEdit::zoomReset()
  487. {
  488. setZoomRatio(1.0);
  489. }
  490. void Fb2CodeEdit::setZoomRatio(qreal ratio)
  491. {
  492. if (!qFuzzyCompare(1 + zoomRatio, 1 + ratio)) {
  493. zoomRatio = ratio;
  494. QFont f = font();
  495. f.setPointSizeF(baseFontSize * zoomRatio);
  496. setFont(f);
  497. }
  498. }
  499. #endif // FB2_USE_PLAINTEXT