123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- #include "fb2code.hpp"
- #include <QApplication>
- #include <QXmlSchema>
- #include <QAbstractMessageHandler>
- #include <QXmlSchemaValidator>
- #include "fb2dlgs.hpp"
- //---------------------------------------------------------------------------
- // FbHighlighter
- //---------------------------------------------------------------------------
- class FbSchemaHandler : public QAbstractMessageHandler
- {
- public:
- FbSchemaHandler()
- : QAbstractMessageHandler(0)
- {
- }
- QString statusMessage() const
- {
- return m_description;
- }
- int line() const
- {
- return m_sourceLocation.line();
- }
- int column() const
- {
- return m_sourceLocation.column();
- }
- protected:
- virtual void handleMessage(QtMsgType type, const QString &description,
- const QUrl &identifier, const QSourceLocation &sourceLocation)
- {
- Q_UNUSED(type);
- Q_UNUSED(identifier);
- m_messageType = type;
- m_description = description;
- m_sourceLocation = sourceLocation;
- }
- private:
- QtMsgType m_messageType;
- QString m_description;
- QSourceLocation m_sourceLocation;
- };
- //---------------------------------------------------------------------------
- // FbHighlighter
- //---------------------------------------------------------------------------
- #include <QXmlInputSource>
- #include <QtGui>
- #include <QSyntaxHighlighter>
- class FbHighlighter : public QSyntaxHighlighter
- {
- public:
- FbHighlighter(QObject* parent);
- FbHighlighter(QTextDocument* parent);
- FbHighlighter(QTextEdit* parent);
- ~FbHighlighter();
- enum HighlightType
- {
- SyntaxChar,
- ElementName,
- Comment,
- AttributeName,
- AttributeValue,
- Error,
- Other
- };
- void setHighlightColor(HighlightType type, QColor color, bool foreground = true);
- void setHighlightFormat(HighlightType type, QTextCharFormat format);
- protected:
- void highlightBlock(const QString& rstrText);
- int processDefaultText(int i, const QString& rstrText);
- private:
- void init();
- QTextCharFormat fmtSyntaxChar;
- QTextCharFormat fmtElementName;
- QTextCharFormat fmtComment;
- QTextCharFormat fmtAttributeName;
- QTextCharFormat fmtAttributeValue;
- QTextCharFormat fmtError;
- QTextCharFormat fmtOther;
- enum ParsingState
- {
- NoState = 0,
- ExpectElementNameOrSlash,
- ExpectElementName,
- ExpectAttributeOrEndOfElement,
- ExpectEqual,
- ExpectAttributeValue
- };
- enum BlockState
- {
- NoBlock = -1,
- InComment,
- InElement
- };
- ParsingState state;
- };
- static const QColor DEFAULT_SYNTAX_CHAR = Qt::blue;
- static const QColor DEFAULT_ELEMENT_NAME = Qt::darkRed;
- static const QColor DEFAULT_COMMENT = Qt::darkGray;
- static const QColor DEFAULT_ATTRIBUTE_NAME = Qt::red;
- static const QColor DEFAULT_ATTRIBUTE_VALUE = Qt::darkGreen;
- static const QColor DEFAULT_ERROR = Qt::darkMagenta;
- static const QColor DEFAULT_OTHER = Qt::black;
- // Regular expressions for parsing XML borrowed from:
- // http://www.cs.sfu.ca/~cameron/REX.html
- static const QString EXPR_COMMENT = "<!--[^-]*-([^-][^-]*-)*->";
- static const QString EXPR_COMMENT_BEGIN = "<!--";
- static const QString EXPR_COMMENT_END = "[^-]*-([^-][^-]*-)*->";
- static const QString EXPR_ATTRIBUTE_VALUE = "\"[^<\"]*\"|'[^<']*'";
- static const QString EXPR_NAME = "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";
- FbHighlighter::FbHighlighter(QObject* parent)
- : QSyntaxHighlighter(parent)
- {
- init();
- }
- FbHighlighter::FbHighlighter(QTextDocument* parent)
- : QSyntaxHighlighter(parent)
- {
- init();
- }
- FbHighlighter::FbHighlighter(QTextEdit* parent)
- : QSyntaxHighlighter(parent)
- {
- init();
- }
- FbHighlighter::~FbHighlighter()
- {
- }
- void FbHighlighter::init()
- {
- fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
- fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
- fmtComment.setForeground(DEFAULT_COMMENT);
- fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
- fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
- fmtError.setForeground(DEFAULT_ERROR);
- fmtOther.setForeground(DEFAULT_OTHER);
- }
- void FbHighlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
- {
- QTextCharFormat format;
- if (foreground)
- format.setForeground(color);
- else
- format.setBackground(color);
- setHighlightFormat(type, format);
- }
- void FbHighlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
- {
- switch (type)
- {
- case SyntaxChar:
- fmtSyntaxChar = format;
- break;
- case ElementName:
- fmtElementName = format;
- break;
- case Comment:
- fmtComment = format;
- break;
- case AttributeName:
- fmtAttributeName = format;
- break;
- case AttributeValue:
- fmtAttributeValue = format;
- break;
- case Error:
- fmtError = format;
- break;
- case Other:
- fmtOther = format;
- break;
- }
- rehighlight();
- }
- void FbHighlighter::highlightBlock(const QString& text)
- {
- int i = 0;
- int pos = 0;
- int brackets = 0;
- state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
- if (previousBlockState() == InComment)
- {
- // search for the end of the comment
- QRegExp expression(EXPR_COMMENT_END);
- pos = expression.indexIn(text, i);
- if (pos >= 0)
- {
- // end comment found
- const int iLength = expression.matchedLength();
- setFormat(0, iLength - 3, fmtComment);
- setFormat(iLength - 3, 3, fmtSyntaxChar);
- i += iLength; // skip comment
- }
- else
- {
- // in comment
- setFormat(0, text.length(), fmtComment);
- setCurrentBlockState(InComment);
- return;
- }
- }
- const int len = text.length();
- for (; i < len; ++i)
- {
- switch (text.at(i).toLatin1())
- {
- case '<':
- ++brackets;
- if (brackets == 1)
- {
- setFormat(i, 1, fmtSyntaxChar);
- state = ExpectElementNameOrSlash;
- }
- else
- {
- // wrong bracket nesting
- setFormat(i, 1, fmtError);
- }
- break;
- case '>':
- --brackets;
- if (brackets == 0)
- {
- setFormat(i, 1, fmtSyntaxChar);
- }
- else
- {
- // wrong bracket nesting
- setFormat( i, 1, fmtError);
- }
- state = NoState;
- break;
- case '/':
- if (state == ExpectElementNameOrSlash)
- {
- state = ExpectElementName;
- setFormat(i, 1, fmtSyntaxChar);
- }
- else
- {
- if (state == ExpectAttributeOrEndOfElement)
- {
- setFormat(i, 1, fmtSyntaxChar);
- }
- else
- {
- processDefaultText(i, text);
- }
- }
- break;
- case '=':
- if (state == ExpectEqual)
- {
- state = ExpectAttributeValue;
- setFormat(i, 1, fmtOther);
- }
- else
- {
- processDefaultText(i, text);
- }
- break;
- case '\'':
- case '\"':
- if (state == ExpectAttributeValue)
- {
- // search attribute value
- QRegExp expression(EXPR_ATTRIBUTE_VALUE);
- pos = expression.indexIn(text, i);
- if (pos == i) // attribute value found ?
- {
- const int iLength = expression.matchedLength();
- setFormat(i, 1, fmtOther);
- setFormat(i + 1, iLength - 2, fmtAttributeValue);
- setFormat(i + iLength - 1, 1, fmtOther);
- i += iLength - 1; // skip attribute value
- state = ExpectAttributeOrEndOfElement;
- }
- else
- {
- processDefaultText(i, text);
- }
- }
- else
- {
- processDefaultText(i, text);
- }
- break;
- case '!':
- if (state == ExpectElementNameOrSlash)
- {
- // search comment
- QRegExp expression(EXPR_COMMENT);
- pos = expression.indexIn(text, i - 1);
- if (pos == i - 1) // comment found ?
- {
- const int iLength = expression.matchedLength();
- setFormat(pos, 4, fmtSyntaxChar);
- setFormat(pos + 4, iLength - 7, fmtComment);
- setFormat(iLength - 3, 3, fmtSyntaxChar);
- i += iLength - 2; // skip comment
- state = NoState;
- --brackets;
- }
- else
- {
- // Try find multiline comment
- QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
- pos = expression.indexIn(text, i - 1);
- //if (pos == i - 1) // comment found ?
- if (pos >= i - 1)
- {
- setFormat(i, 3, fmtSyntaxChar);
- setFormat(i + 3, text.length() - i - 3, fmtComment);
- setCurrentBlockState(InComment);
- return;
- }
- else
- {
- processDefaultText(i, text);
- }
- }
- }
- else
- {
- processDefaultText(i, text);
- }
- break;
- default:
- const int iLength = processDefaultText(i, text);
- if (iLength > 0)
- i += iLength - 1;
- break;
- }
- }
- if (state == ExpectAttributeOrEndOfElement)
- {
- setCurrentBlockState(InElement);
- }
- }
- int FbHighlighter::processDefaultText(int i, const QString& text)
- {
- // length of matched text
- int iLength = 0;
- switch(state)
- {
- case ExpectElementNameOrSlash:
- case ExpectElementName:
- {
- // search element name
- QRegExp expression(EXPR_NAME);
- const int pos = expression.indexIn(text, i);
- if (pos == i) // found ?
- {
- iLength = expression.matchedLength();
- setFormat(pos, iLength, fmtElementName);
- state = ExpectAttributeOrEndOfElement;
- }
- else
- {
- setFormat(i, 1, fmtOther);
- }
- }
- break;
- case ExpectAttributeOrEndOfElement:
- {
- // search attribute name
- QRegExp expression(EXPR_NAME);
- const int pos = expression.indexIn(text, i);
- if (pos == i) // found ?
- {
- iLength = expression.matchedLength();
- setFormat(pos, iLength, fmtAttributeName);
- state = ExpectEqual;
- }
- else
- {
- setFormat(i, 1, fmtOther);
- }
- }
- break;
- default:
- setFormat(i, 1, fmtOther);
- break;
- }
- return iLength;
- }
- //---------------------------------------------------------------------------
- // FbCodeEdit
- //---------------------------------------------------------------------------
- qreal FbCodeEdit::baseFontSize = 10;
- qreal FbCodeEdit::zoomRatioMin = 0.2;
- qreal FbCodeEdit::zoomRatioMax = 5.0;
- FbCodeEdit::FbCodeEdit(QWidget *parent) : QPlainTextEdit(parent)
- {
- lineNumberArea = new LineNumberArea(this);
- FbHighlighter *highlighter = new FbHighlighter(this);
- highlighter->setDocument( document() );
- connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
- connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
- connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
- zoomRatio = 1;
- QFont f("Monospace", baseFontSize);
- f.setStyleHint(f.TypeWriter, f.PreferDefault);
- setFont(f);
- updateLineNumberAreaWidth(0);
- highlightCurrentLine();
- }
- QAction * FbCodeEdit::act(Fb::Actions index) const
- {
- return m_actions[index];
- }
- void FbCodeEdit::setAction(Fb::Actions index, QAction *action)
- {
- m_actions[index] = action;
- }
- void FbCodeEdit::connectActions(QToolBar *tool)
- {
- act(Fb::EditUndo)->setEnabled(document()->isUndoAvailable());
- act(Fb::EditRedo)->setEnabled(document()->isRedoAvailable());
- act(Fb::EditFind)->setEnabled(true);
- act(Fb::CheckText)->setEnabled(true);
- act(Fb::ZoomIn)->setEnabled(true);
- act(Fb::ZoomOut)->setEnabled(true);
- act(Fb::ZoomReset)->setEnabled(true);
- connect(act(Fb::EditUndo), SIGNAL(triggered()), SLOT(undo()));
- connect(act(Fb::EditRedo), SIGNAL(triggered()), SLOT(redo()));
- connect(this, SIGNAL(undoAvailable(bool)), act(Fb::EditUndo), SLOT(setEnabled(bool)));
- connect(this, SIGNAL(redoAvailable(bool)), act(Fb::EditRedo), SLOT(setEnabled(bool)));
- connect(act(Fb::EditCut), SIGNAL(triggered()), SLOT(cut()));
- connect(act(Fb::EditCopy), SIGNAL(triggered()), SLOT(copy()));
- connect(act(Fb::EditPaste), SIGNAL(triggered()), SLOT(paste()));
- connect(act(Fb::EditFind), SIGNAL(triggered()), SLOT(find()));
- connect(act(Fb::CheckText), SIGNAL(triggered()), SLOT(validate()));
- connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCut), SLOT(setEnabled(bool)));
- connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCopy), SLOT(setEnabled(bool)));
- connect(qApp->clipboard(), SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
- clipboardDataChanged();
- connect(act(Fb::ZoomIn), SIGNAL(triggered()), SLOT(zoomIn()));
- connect(act(Fb::ZoomOut), SIGNAL(triggered()), SLOT(zoomOut()));
- connect(act(Fb::ZoomReset), SIGNAL(triggered()), SLOT(zoomReset()));
- tool->clear();
- tool->addSeparator();
- tool->addAction(act(Fb::EditUndo));
- tool->addAction(act(Fb::EditRedo));
- tool->addSeparator();
- tool->addAction(act(Fb::EditCut));
- tool->addAction(act(Fb::EditCopy));
- tool->addAction(act(Fb::EditPaste));
- tool->addSeparator();
- tool->addAction(act(Fb::ZoomIn));
- tool->addAction(act(Fb::ZoomOut));
- tool->addAction(act(Fb::ZoomReset));
- }
- void FbCodeEdit::disconnectActions()
- {
- m_actions.disconnect();
- }
- void FbCodeEdit::clipboardDataChanged()
- {
- if (const QMimeData *md = QApplication::clipboard()->mimeData()) {
- act(Fb::EditPaste)->setEnabled(md->hasText());
- }
- }
- bool FbCodeEdit::read(QIODevice *device)
- {
- QByteArray data = device->readAll();
- delete device;
- setPlainText(data);
- return true;
- }
- int FbCodeEdit::lineNumberAreaWidth()
- {
- int digits = 1;
- int max = qMax(1, blockCount());
- while (max >= 10) {
- max /= 10;
- ++digits;
- }
- int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
- return space;
- }
- void FbCodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
- {
- setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
- }
- void FbCodeEdit::updateLineNumberArea(const QRect &rect, int dy)
- {
- if (dy)
- lineNumberArea->scroll(0, dy);
- else
- lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
- if (rect.contains(viewport()->rect()))
- updateLineNumberAreaWidth(0);
- }
- void FbCodeEdit::resizeEvent(QResizeEvent *e)
- {
- QPlainTextEdit::resizeEvent(e);
- QRect cr = contentsRect();
- lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
- }
- void FbCodeEdit::highlightCurrentLine()
- {
- QList<QTextEdit::ExtraSelection> extraSelections;
- if (!isReadOnly()) {
- QTextEdit::ExtraSelection selection;
- QColor lineColor = QColor(Qt::yellow).lighter(160);
- selection.format.setBackground(lineColor);
- selection.format.setProperty(QTextFormat::FullWidthSelection, true);
- selection.cursor = textCursor();
- selection.cursor.clearSelection();
- extraSelections.append(selection);
- }
- setExtraSelections(extraSelections);
- }
- void FbCodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
- {
- QPainter painter(lineNumberArea);
- painter.fillRect(event->rect(), Qt::lightGray);
- QTextBlock block = firstVisibleBlock();
- int blockNumber = block.blockNumber();
- int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
- int bottom = top + (int) blockBoundingRect(block).height();
- while (block.isValid() && top <= event->rect().bottom()) {
- if (block.isVisible() && bottom >= event->rect().top()) {
- QString number = QString::number(blockNumber + 1);
- painter.setPen(Qt::black);
- painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
- Qt::AlignRight, number);
- }
- block = block.next();
- top = bottom;
- bottom = top + (int) blockBoundingRect(block).height();
- ++blockNumber;
- }
- }
- bool FbCodeEdit::findText(const QString &exp, QTextDocument::FindFlags options)
- {
- return QPlainTextEdit::find(exp, options);
- }
- void FbCodeEdit::find()
- {
- FbCodeFindDlg dlg(*this);
- dlg.exec();
- }
- void FbCodeEdit::zoomIn()
- {
- qreal ratio = zoomRatio * 1.1;
- ratio = qMin(ratio, zoomRatioMax);
- setZoomRatio(ratio);
- }
- void FbCodeEdit::zoomOut()
- {
- qreal ratio = zoomRatio / 1.1;
- ratio = qMax(ratio, zoomRatioMin);
- setZoomRatio(ratio);
- }
- void FbCodeEdit::zoomReset()
- {
- setZoomRatio(1.0);
- }
- void FbCodeEdit::setZoomRatio(qreal ratio)
- {
- if (!qFuzzyCompare(1 + zoomRatio, 1 + ratio)) {
- zoomRatio = ratio;
- QFont f = font();
- f.setPointSizeF(baseFontSize * zoomRatio);
- setFont(f);
- }
- }
- void FbCodeEdit::validate()
- {
- FbSchemaHandler handler;
- QXmlSchema schema;
- schema.setMessageHandler(&handler);
- QUrl url("qrc:/fb2/FictionBook2.1.xsd");
- schema.load(url);
- if (!schema.isValid()) {
- status(tr("Schema is not valid: ") + handler.statusMessage());
- return;
- }
- const QByteArray data = toPlainText().toUtf8();
- QXmlSchemaValidator validator(schema);
- if (!validator.validate(data)) {
- setCursor(handler.line(), handler.column());
- status(handler.statusMessage());
- } else {
- status(tr("Validation successful"));
- }
- }
- void FbCodeEdit::setCursor(int line, int column)
- {
- moveCursor(QTextCursor::Start);
- for (int i = 1; i < line; ++i)
- moveCursor(QTextCursor::Down);
- for (int i = 1; i < column; ++i)
- moveCursor(QTextCursor::Right);
- QList<QTextEdit::ExtraSelection> extraSelections;
- QTextEdit::ExtraSelection selection;
- const QColor lineColor = QColor(Qt::red).lighter(160);
- selection.format.setBackground(lineColor);
- selection.format.setProperty(QTextFormat::FullWidthSelection, true);
- selection.cursor = textCursor();
- selection.cursor.clearSelection();
- extraSelections.append(selection);
- setExtraSelections(extraSelections);
- setFocus();
- }
|