fb2code.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. #include "fb2code.hpp"
  2. #include <QApplication>
  3. #include <QXmlSchema>
  4. #include <QAbstractMessageHandler>
  5. #include <QXmlSchemaValidator>
  6. #include "fb2dlgs.hpp"
  7. //---------------------------------------------------------------------------
  8. // FbHighlighter
  9. //---------------------------------------------------------------------------
  10. class FbSchemaHandler : public QAbstractMessageHandler
  11. {
  12. public:
  13. FbSchemaHandler()
  14. : QAbstractMessageHandler(0)
  15. {
  16. }
  17. QString statusMessage() const
  18. {
  19. return m_description;
  20. }
  21. int line() const
  22. {
  23. return m_sourceLocation.line();
  24. }
  25. int column() const
  26. {
  27. return m_sourceLocation.column();
  28. }
  29. protected:
  30. virtual void handleMessage(QtMsgType type, const QString &description,
  31. const QUrl &identifier, const QSourceLocation &sourceLocation)
  32. {
  33. Q_UNUSED(type);
  34. Q_UNUSED(identifier);
  35. m_messageType = type;
  36. m_description = description;
  37. m_sourceLocation = sourceLocation;
  38. }
  39. private:
  40. QtMsgType m_messageType;
  41. QString m_description;
  42. QSourceLocation m_sourceLocation;
  43. };
  44. //---------------------------------------------------------------------------
  45. // FbHighlighter
  46. //---------------------------------------------------------------------------
  47. #include <QXmlInputSource>
  48. #include <QtGui>
  49. #include <QSyntaxHighlighter>
  50. class FbHighlighter : public QSyntaxHighlighter
  51. {
  52. public:
  53. FbHighlighter(QObject* parent);
  54. FbHighlighter(QTextDocument* parent);
  55. FbHighlighter(QTextEdit* parent);
  56. ~FbHighlighter();
  57. enum HighlightType
  58. {
  59. SyntaxChar,
  60. ElementName,
  61. Comment,
  62. AttributeName,
  63. AttributeValue,
  64. Error,
  65. Other
  66. };
  67. void setHighlightColor(HighlightType type, QColor color, bool foreground = true);
  68. void setHighlightFormat(HighlightType type, QTextCharFormat format);
  69. protected:
  70. void highlightBlock(const QString& rstrText);
  71. int processDefaultText(int i, const QString& rstrText);
  72. private:
  73. void init();
  74. QTextCharFormat fmtSyntaxChar;
  75. QTextCharFormat fmtElementName;
  76. QTextCharFormat fmtComment;
  77. QTextCharFormat fmtAttributeName;
  78. QTextCharFormat fmtAttributeValue;
  79. QTextCharFormat fmtError;
  80. QTextCharFormat fmtOther;
  81. enum ParsingState
  82. {
  83. NoState = 0,
  84. ExpectElementNameOrSlash,
  85. ExpectElementName,
  86. ExpectAttributeOrEndOfElement,
  87. ExpectEqual,
  88. ExpectAttributeValue
  89. };
  90. enum BlockState
  91. {
  92. NoBlock = -1,
  93. InComment,
  94. InElement
  95. };
  96. ParsingState state;
  97. };
  98. static const QColor DEFAULT_SYNTAX_CHAR = Qt::blue;
  99. static const QColor DEFAULT_ELEMENT_NAME = Qt::darkRed;
  100. static const QColor DEFAULT_COMMENT = Qt::darkGray;
  101. static const QColor DEFAULT_ATTRIBUTE_NAME = Qt::red;
  102. static const QColor DEFAULT_ATTRIBUTE_VALUE = Qt::darkGreen;
  103. static const QColor DEFAULT_ERROR = Qt::darkMagenta;
  104. static const QColor DEFAULT_OTHER = Qt::black;
  105. // Regular expressions for parsing XML borrowed from:
  106. // http://www.cs.sfu.ca/~cameron/REX.html
  107. static const QString EXPR_COMMENT = "<!--[^-]*-([^-][^-]*-)*->";
  108. static const QString EXPR_COMMENT_BEGIN = "<!--";
  109. static const QString EXPR_COMMENT_END = "[^-]*-([^-][^-]*-)*->";
  110. static const QString EXPR_ATTRIBUTE_VALUE = "\"[^<\"]*\"|'[^<']*'";
  111. static const QString EXPR_NAME = "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";
  112. FbHighlighter::FbHighlighter(QObject* parent)
  113. : QSyntaxHighlighter(parent)
  114. {
  115. init();
  116. }
  117. FbHighlighter::FbHighlighter(QTextDocument* parent)
  118. : QSyntaxHighlighter(parent)
  119. {
  120. init();
  121. }
  122. FbHighlighter::FbHighlighter(QTextEdit* parent)
  123. : QSyntaxHighlighter(parent)
  124. {
  125. init();
  126. }
  127. FbHighlighter::~FbHighlighter()
  128. {
  129. }
  130. void FbHighlighter::init()
  131. {
  132. fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
  133. fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
  134. fmtComment.setForeground(DEFAULT_COMMENT);
  135. fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
  136. fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
  137. fmtError.setForeground(DEFAULT_ERROR);
  138. fmtOther.setForeground(DEFAULT_OTHER);
  139. }
  140. void FbHighlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
  141. {
  142. QTextCharFormat format;
  143. if (foreground)
  144. format.setForeground(color);
  145. else
  146. format.setBackground(color);
  147. setHighlightFormat(type, format);
  148. }
  149. void FbHighlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
  150. {
  151. switch (type)
  152. {
  153. case SyntaxChar:
  154. fmtSyntaxChar = format;
  155. break;
  156. case ElementName:
  157. fmtElementName = format;
  158. break;
  159. case Comment:
  160. fmtComment = format;
  161. break;
  162. case AttributeName:
  163. fmtAttributeName = format;
  164. break;
  165. case AttributeValue:
  166. fmtAttributeValue = format;
  167. break;
  168. case Error:
  169. fmtError = format;
  170. break;
  171. case Other:
  172. fmtOther = format;
  173. break;
  174. }
  175. rehighlight();
  176. }
  177. void FbHighlighter::highlightBlock(const QString& text)
  178. {
  179. int i = 0;
  180. int pos = 0;
  181. int brackets = 0;
  182. state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
  183. if (previousBlockState() == InComment)
  184. {
  185. // search for the end of the comment
  186. QRegExp expression(EXPR_COMMENT_END);
  187. pos = expression.indexIn(text, i);
  188. if (pos >= 0)
  189. {
  190. // end comment found
  191. const int iLength = expression.matchedLength();
  192. setFormat(0, iLength - 3, fmtComment);
  193. setFormat(iLength - 3, 3, fmtSyntaxChar);
  194. i += iLength; // skip comment
  195. }
  196. else
  197. {
  198. // in comment
  199. setFormat(0, text.length(), fmtComment);
  200. setCurrentBlockState(InComment);
  201. return;
  202. }
  203. }
  204. const int len = text.length();
  205. for (; i < len; ++i)
  206. {
  207. switch (text.at(i).toLatin1())
  208. {
  209. case '<':
  210. ++brackets;
  211. if (brackets == 1)
  212. {
  213. setFormat(i, 1, fmtSyntaxChar);
  214. state = ExpectElementNameOrSlash;
  215. }
  216. else
  217. {
  218. // wrong bracket nesting
  219. setFormat(i, 1, fmtError);
  220. }
  221. break;
  222. case '>':
  223. --brackets;
  224. if (brackets == 0)
  225. {
  226. setFormat(i, 1, fmtSyntaxChar);
  227. }
  228. else
  229. {
  230. // wrong bracket nesting
  231. setFormat( i, 1, fmtError);
  232. }
  233. state = NoState;
  234. break;
  235. case '/':
  236. if (state == ExpectElementNameOrSlash)
  237. {
  238. state = ExpectElementName;
  239. setFormat(i, 1, fmtSyntaxChar);
  240. }
  241. else
  242. {
  243. if (state == ExpectAttributeOrEndOfElement)
  244. {
  245. setFormat(i, 1, fmtSyntaxChar);
  246. }
  247. else
  248. {
  249. processDefaultText(i, text);
  250. }
  251. }
  252. break;
  253. case '=':
  254. if (state == ExpectEqual)
  255. {
  256. state = ExpectAttributeValue;
  257. setFormat(i, 1, fmtOther);
  258. }
  259. else
  260. {
  261. processDefaultText(i, text);
  262. }
  263. break;
  264. case '\'':
  265. case '\"':
  266. if (state == ExpectAttributeValue)
  267. {
  268. // search attribute value
  269. QRegExp expression(EXPR_ATTRIBUTE_VALUE);
  270. pos = expression.indexIn(text, i);
  271. if (pos == i) // attribute value found ?
  272. {
  273. const int iLength = expression.matchedLength();
  274. setFormat(i, 1, fmtOther);
  275. setFormat(i + 1, iLength - 2, fmtAttributeValue);
  276. setFormat(i + iLength - 1, 1, fmtOther);
  277. i += iLength - 1; // skip attribute value
  278. state = ExpectAttributeOrEndOfElement;
  279. }
  280. else
  281. {
  282. processDefaultText(i, text);
  283. }
  284. }
  285. else
  286. {
  287. processDefaultText(i, text);
  288. }
  289. break;
  290. case '!':
  291. if (state == ExpectElementNameOrSlash)
  292. {
  293. // search comment
  294. QRegExp expression(EXPR_COMMENT);
  295. pos = expression.indexIn(text, i - 1);
  296. if (pos == i - 1) // comment found ?
  297. {
  298. const int iLength = expression.matchedLength();
  299. setFormat(pos, 4, fmtSyntaxChar);
  300. setFormat(pos + 4, iLength - 7, fmtComment);
  301. setFormat(iLength - 3, 3, fmtSyntaxChar);
  302. i += iLength - 2; // skip comment
  303. state = NoState;
  304. --brackets;
  305. }
  306. else
  307. {
  308. // Try find multiline comment
  309. QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
  310. pos = expression.indexIn(text, i - 1);
  311. //if (pos == i - 1) // comment found ?
  312. if (pos >= i - 1)
  313. {
  314. setFormat(i, 3, fmtSyntaxChar);
  315. setFormat(i + 3, text.length() - i - 3, fmtComment);
  316. setCurrentBlockState(InComment);
  317. return;
  318. }
  319. else
  320. {
  321. processDefaultText(i, text);
  322. }
  323. }
  324. }
  325. else
  326. {
  327. processDefaultText(i, text);
  328. }
  329. break;
  330. default:
  331. const int iLength = processDefaultText(i, text);
  332. if (iLength > 0)
  333. i += iLength - 1;
  334. break;
  335. }
  336. }
  337. if (state == ExpectAttributeOrEndOfElement)
  338. {
  339. setCurrentBlockState(InElement);
  340. }
  341. }
  342. int FbHighlighter::processDefaultText(int i, const QString& text)
  343. {
  344. // length of matched text
  345. int iLength = 0;
  346. switch(state)
  347. {
  348. case ExpectElementNameOrSlash:
  349. case ExpectElementName:
  350. {
  351. // search element 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, fmtElementName);
  358. state = ExpectAttributeOrEndOfElement;
  359. }
  360. else
  361. {
  362. setFormat(i, 1, fmtOther);
  363. }
  364. }
  365. break;
  366. case ExpectAttributeOrEndOfElement:
  367. {
  368. // search attribute name
  369. QRegExp expression(EXPR_NAME);
  370. const int pos = expression.indexIn(text, i);
  371. if (pos == i) // found ?
  372. {
  373. iLength = expression.matchedLength();
  374. setFormat(pos, iLength, fmtAttributeName);
  375. state = ExpectEqual;
  376. }
  377. else
  378. {
  379. setFormat(i, 1, fmtOther);
  380. }
  381. }
  382. break;
  383. default:
  384. setFormat(i, 1, fmtOther);
  385. break;
  386. }
  387. return iLength;
  388. }
  389. //---------------------------------------------------------------------------
  390. // FbCodeEdit
  391. //---------------------------------------------------------------------------
  392. qreal FbCodeEdit::baseFontSize = 10;
  393. qreal FbCodeEdit::zoomRatioMin = 0.2;
  394. qreal FbCodeEdit::zoomRatioMax = 5.0;
  395. FbCodeEdit::FbCodeEdit(QWidget *parent) : QPlainTextEdit(parent)
  396. {
  397. lineNumberArea = new LineNumberArea(this);
  398. FbHighlighter *highlighter = new FbHighlighter(this);
  399. highlighter->setDocument( document() );
  400. connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
  401. connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
  402. connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  403. zoomRatio = 1;
  404. QFont f("Monospace", baseFontSize);
  405. f.setStyleHint(f.TypeWriter, f.PreferDefault);
  406. setFont(f);
  407. updateLineNumberAreaWidth(0);
  408. highlightCurrentLine();
  409. }
  410. QAction * FbCodeEdit::act(Fb::Actions index) const
  411. {
  412. return m_actions[index];
  413. }
  414. void FbCodeEdit::setAction(Fb::Actions index, QAction *action)
  415. {
  416. m_actions[index] = action;
  417. }
  418. void FbCodeEdit::connectActions(QToolBar *tool)
  419. {
  420. act(Fb::EditUndo)->setEnabled(document()->isUndoAvailable());
  421. act(Fb::EditRedo)->setEnabled(document()->isRedoAvailable());
  422. act(Fb::EditFind)->setEnabled(true);
  423. act(Fb::CheckText)->setEnabled(true);
  424. act(Fb::ZoomIn)->setEnabled(true);
  425. act(Fb::ZoomOut)->setEnabled(true);
  426. act(Fb::ZoomReset)->setEnabled(true);
  427. connect(act(Fb::EditUndo), SIGNAL(triggered()), SLOT(undo()));
  428. connect(act(Fb::EditRedo), SIGNAL(triggered()), SLOT(redo()));
  429. connect(this, SIGNAL(undoAvailable(bool)), act(Fb::EditUndo), SLOT(setEnabled(bool)));
  430. connect(this, SIGNAL(redoAvailable(bool)), act(Fb::EditRedo), SLOT(setEnabled(bool)));
  431. connect(act(Fb::EditCut), SIGNAL(triggered()), SLOT(cut()));
  432. connect(act(Fb::EditCopy), SIGNAL(triggered()), SLOT(copy()));
  433. connect(act(Fb::EditPaste), SIGNAL(triggered()), SLOT(paste()));
  434. connect(act(Fb::EditFind), SIGNAL(triggered()), SLOT(find()));
  435. connect(act(Fb::CheckText), SIGNAL(triggered()), SLOT(validate()));
  436. connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCut), SLOT(setEnabled(bool)));
  437. connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCopy), SLOT(setEnabled(bool)));
  438. connect(qApp->clipboard(), SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
  439. clipboardDataChanged();
  440. connect(act(Fb::ZoomIn), SIGNAL(triggered()), SLOT(zoomIn()));
  441. connect(act(Fb::ZoomOut), SIGNAL(triggered()), SLOT(zoomOut()));
  442. connect(act(Fb::ZoomReset), SIGNAL(triggered()), SLOT(zoomReset()));
  443. tool->clear();
  444. tool->addSeparator();
  445. tool->addAction(act(Fb::EditUndo));
  446. tool->addAction(act(Fb::EditRedo));
  447. tool->addSeparator();
  448. tool->addAction(act(Fb::EditCut));
  449. tool->addAction(act(Fb::EditCopy));
  450. tool->addAction(act(Fb::EditPaste));
  451. tool->addSeparator();
  452. tool->addAction(act(Fb::ZoomIn));
  453. tool->addAction(act(Fb::ZoomOut));
  454. tool->addAction(act(Fb::ZoomReset));
  455. }
  456. void FbCodeEdit::disconnectActions()
  457. {
  458. m_actions.disconnect();
  459. }
  460. void FbCodeEdit::clipboardDataChanged()
  461. {
  462. if (const QMimeData *md = QApplication::clipboard()->mimeData()) {
  463. act(Fb::EditPaste)->setEnabled(md->hasText());
  464. }
  465. }
  466. bool FbCodeEdit::read(QIODevice *device)
  467. {
  468. QByteArray data = device->readAll();
  469. delete device;
  470. setPlainText(data);
  471. return true;
  472. }
  473. int FbCodeEdit::lineNumberAreaWidth()
  474. {
  475. int digits = 1;
  476. int max = qMax(1, blockCount());
  477. while (max >= 10) {
  478. max /= 10;
  479. ++digits;
  480. }
  481. int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
  482. return space;
  483. }
  484. void FbCodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
  485. {
  486. setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
  487. }
  488. void FbCodeEdit::updateLineNumberArea(const QRect &rect, int dy)
  489. {
  490. if (dy)
  491. lineNumberArea->scroll(0, dy);
  492. else
  493. lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
  494. if (rect.contains(viewport()->rect()))
  495. updateLineNumberAreaWidth(0);
  496. }
  497. void FbCodeEdit::resizeEvent(QResizeEvent *e)
  498. {
  499. QPlainTextEdit::resizeEvent(e);
  500. QRect cr = contentsRect();
  501. lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
  502. }
  503. void FbCodeEdit::highlightCurrentLine()
  504. {
  505. QList<QTextEdit::ExtraSelection> extraSelections;
  506. if (!isReadOnly()) {
  507. QTextEdit::ExtraSelection selection;
  508. QColor lineColor = QColor(Qt::yellow).lighter(160);
  509. selection.format.setBackground(lineColor);
  510. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  511. selection.cursor = textCursor();
  512. selection.cursor.clearSelection();
  513. extraSelections.append(selection);
  514. }
  515. setExtraSelections(extraSelections);
  516. }
  517. void FbCodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
  518. {
  519. QPainter painter(lineNumberArea);
  520. painter.fillRect(event->rect(), Qt::lightGray);
  521. QTextBlock block = firstVisibleBlock();
  522. int blockNumber = block.blockNumber();
  523. int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
  524. int bottom = top + (int) blockBoundingRect(block).height();
  525. while (block.isValid() && top <= event->rect().bottom()) {
  526. if (block.isVisible() && bottom >= event->rect().top()) {
  527. QString number = QString::number(blockNumber + 1);
  528. painter.setPen(Qt::black);
  529. painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
  530. Qt::AlignRight, number);
  531. }
  532. block = block.next();
  533. top = bottom;
  534. bottom = top + (int) blockBoundingRect(block).height();
  535. ++blockNumber;
  536. }
  537. }
  538. bool FbCodeEdit::findText(const QString &exp, QTextDocument::FindFlags options)
  539. {
  540. return QPlainTextEdit::find(exp, options);
  541. }
  542. void FbCodeEdit::find()
  543. {
  544. FbCodeFindDlg dlg(*this);
  545. dlg.exec();
  546. }
  547. void FbCodeEdit::zoomIn()
  548. {
  549. qreal ratio = zoomRatio * 1.1;
  550. ratio = qMin(ratio, zoomRatioMax);
  551. setZoomRatio(ratio);
  552. }
  553. void FbCodeEdit::zoomOut()
  554. {
  555. qreal ratio = zoomRatio / 1.1;
  556. ratio = qMax(ratio, zoomRatioMin);
  557. setZoomRatio(ratio);
  558. }
  559. void FbCodeEdit::zoomReset()
  560. {
  561. setZoomRatio(1.0);
  562. }
  563. void FbCodeEdit::setZoomRatio(qreal ratio)
  564. {
  565. if (!qFuzzyCompare(1 + zoomRatio, 1 + ratio)) {
  566. zoomRatio = ratio;
  567. QFont f = font();
  568. f.setPointSizeF(baseFontSize * zoomRatio);
  569. setFont(f);
  570. }
  571. }
  572. void FbCodeEdit::validate()
  573. {
  574. FbSchemaHandler handler;
  575. QXmlSchema schema;
  576. schema.setMessageHandler(&handler);
  577. QUrl url("qrc:/fb2/FictionBook2.1.xsd");
  578. schema.load(url);
  579. if (!schema.isValid()) {
  580. status(tr("Schema is not valid: ") + handler.statusMessage());
  581. return;
  582. }
  583. const QByteArray data = toPlainText().toUtf8();
  584. QXmlSchemaValidator validator(schema);
  585. if (!validator.validate(data)) {
  586. setCursor(handler.line(), handler.column());
  587. status(handler.statusMessage());
  588. } else {
  589. status(tr("Validation successful"));
  590. }
  591. }
  592. void FbCodeEdit::setCursor(int line, int column)
  593. {
  594. moveCursor(QTextCursor::Start);
  595. for (int i = 1; i < line; ++i)
  596. moveCursor(QTextCursor::Down);
  597. for (int i = 1; i < column; ++i)
  598. moveCursor(QTextCursor::Right);
  599. QList<QTextEdit::ExtraSelection> extraSelections;
  600. QTextEdit::ExtraSelection selection;
  601. const QColor lineColor = QColor(Qt::red).lighter(160);
  602. selection.format.setBackground(lineColor);
  603. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  604. selection.cursor = textCursor();
  605. selection.cursor.clearSelection();
  606. extraSelections.append(selection);
  607. setExtraSelections(extraSelections);
  608. setFocus();
  609. }