diff --git a/examples/codeeditor.cpp b/examples/codeeditor.cpp index 8781583..88f3154 100644 --- a/examples/codeeditor.cpp +++ b/examples/codeeditor.cpp @@ -1,352 +1,358 @@ /* Copyright (C) 2016 Volker Krause - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU Library General Public License as published by - the Free Software Foundation; either version 2 of the License, or (at your - option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public - License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "codeeditor.h" #include #include #include #include #include #include #include #include #include #include #include #include class CodeEditorSidebar : public QWidget { Q_OBJECT public: explicit CodeEditorSidebar(CodeEditor *editor); QSize sizeHint() const Q_DECL_OVERRIDE; protected: void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; private: CodeEditor *m_codeEditor; }; CodeEditorSidebar::CodeEditorSidebar(CodeEditor *editor) : QWidget(editor), m_codeEditor(editor) { } QSize CodeEditorSidebar::sizeHint() const { return QSize(m_codeEditor->sidebarWidth(), 0); } void CodeEditorSidebar::paintEvent(QPaintEvent *event) { m_codeEditor->sidebarPaintEvent(event); } void CodeEditorSidebar::mouseReleaseEvent(QMouseEvent *event) { if (event->x() >= width() - m_codeEditor->fontMetrics().lineSpacing()) { auto block = m_codeEditor->blockAtPosition(event->y()); if (!block.isValid() || !m_codeEditor->isFoldable(block)) return; m_codeEditor->toggleFold(block); } QWidget::mouseReleaseEvent(event); } CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), m_highlighter(new KSyntaxHighlighting::SyntaxHighlighter(document())), m_sideBar(new CodeEditorSidebar(this)) { setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); setTheme((palette().color(QPalette::Base).lightness() < 128) ? m_repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) : m_repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme)); connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::updateSidebarGeometry); connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::updateSidebarArea); connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine); updateSidebarGeometry(); highlightCurrentLine(); } CodeEditor::~CodeEditor() { } void CodeEditor::openFile(const QString& fileName) { QFile f(fileName); if (!f.open(QFile::ReadOnly)) { qWarning() << "Failed to open" << fileName << ":" << f.errorString(); return; } clear(); const auto def = m_repository.definitionForFileName(fileName); m_highlighter->setDefinition(def); setWindowTitle(fileName); setPlainText(QString::fromUtf8(f.readAll())); } void CodeEditor::contextMenuEvent(QContextMenuEvent *event) { auto menu = createStandardContextMenu(event->pos()); menu->addSeparator(); auto openAction = menu->addAction(QStringLiteral("Open File...")); connect(openAction, &QAction::triggered, this, [this]() { const auto fileName = QFileDialog::getOpenFileName(this, QStringLiteral("Open File")); if (!fileName.isEmpty()) openFile(fileName); }); // syntax selection auto hlActionGroup = new QActionGroup(menu); hlActionGroup->setExclusive(true); auto hlGroupMenu = menu->addMenu(QStringLiteral("Syntax")); QMenu *hlSubMenu = hlGroupMenu; QString currentGroup; foreach (const auto &def, m_repository.definitions()) { if (def.isHidden()) continue; if (currentGroup != def.section()) { currentGroup = def.section(); hlSubMenu = hlGroupMenu->addMenu(def.translatedSection()); } Q_ASSERT(hlSubMenu); auto action = hlSubMenu->addAction(def.translatedName()); action->setCheckable(true); action->setData(def.name()); hlActionGroup->addAction(action); if (def.name() == m_highlighter->definition().name()) action->setChecked(true); } connect(hlActionGroup, &QActionGroup::triggered, this, [this](QAction *action) { const auto defName = action->data().toString(); const auto def = m_repository.definitionForName(defName); m_highlighter->setDefinition(def); }); // theme selection auto themeGroup = new QActionGroup(menu); themeGroup->setExclusive(true); auto themeMenu = menu->addMenu(QStringLiteral("Theme")); foreach (const auto &theme, m_repository.themes()) { auto action = themeMenu->addAction(theme.translatedName()); action->setCheckable(true); action->setData(theme.name()); themeGroup->addAction(action); if (theme.name() == m_highlighter->theme().name()) action->setChecked(true); } connect(themeGroup, &QActionGroup::triggered, this, [this](QAction *action) { const auto themeName = action->data().toString(); const auto theme = m_repository.theme(themeName); setTheme(theme); }); menu->exec(event->globalPos()); delete menu; } void CodeEditor::resizeEvent(QResizeEvent *event) { QPlainTextEdit::resizeEvent(event); updateSidebarGeometry(); } void CodeEditor::setTheme(const KSyntaxHighlighting::Theme &theme) { auto pal = qApp->palette(); if (theme.isValid()) { pal.setColor(QPalette::Base, theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor)); pal.setColor(QPalette::Text, theme.textColor(KSyntaxHighlighting::Theme::Normal)); pal.setColor(QPalette::Highlight, theme.editorColor(KSyntaxHighlighting::Theme::TextSelection)); } setPalette(pal); m_highlighter->setTheme(theme); m_highlighter->rehighlight(); highlightCurrentLine(); } int CodeEditor::sidebarWidth() const { int digits = 1; auto count = blockCount(); while (count >= 10) { ++digits; count /= 10; } return 4 + fontMetrics().width(QLatin1Char('9')) * digits + fontMetrics().lineSpacing(); } void CodeEditor::sidebarPaintEvent(QPaintEvent *event) { QPainter painter(m_sideBar); painter.fillRect(event->rect(), m_highlighter->theme().editorColor(KSyntaxHighlighting::Theme::IconBorder)); auto block = firstVisibleBlock(); auto blockNumber = block.blockNumber(); int top = blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + blockBoundingRect(block).height(); const int currentBlockNumber = textCursor().blockNumber(); const auto foldingMarkerSize = fontMetrics().lineSpacing(); while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { const auto number = QString::number(blockNumber + 1); painter.setPen(m_highlighter->theme().editorColor( (blockNumber == currentBlockNumber) ? KSyntaxHighlighting::Theme::CurrentLineNumber : KSyntaxHighlighting::Theme::LineNumbers)); painter.drawText(0, top, m_sideBar->width() - 2 - foldingMarkerSize, fontMetrics().height(), Qt::AlignRight, number); } // folding marker if (block.isVisible() && isFoldable(block)) { QPolygonF polygon; if (isFolded(block)) { polygon << QPointF(foldingMarkerSize * 0.4, foldingMarkerSize * 0.25); polygon << QPointF(foldingMarkerSize * 0.4, foldingMarkerSize * 0.75); polygon << QPointF(foldingMarkerSize * 0.8, foldingMarkerSize * 0.5); } else { polygon << QPointF(foldingMarkerSize * 0.25, foldingMarkerSize * 0.4); polygon << QPointF(foldingMarkerSize * 0.75, foldingMarkerSize * 0.4); polygon << QPointF(foldingMarkerSize * 0.5, foldingMarkerSize * 0.8); } painter.save(); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); painter.setBrush(QColor(m_highlighter->theme().editorColor(KSyntaxHighlighting::Theme::CodeFolding))); painter.translate(m_sideBar->width() - foldingMarkerSize, top); painter.drawPolygon(polygon); painter.restore(); } block = block.next(); top = bottom; bottom = top + blockBoundingRect(block).height(); ++blockNumber; } } void CodeEditor::updateSidebarGeometry() { setViewportMargins(sidebarWidth(), 0, 0, 0); const auto r = contentsRect(); m_sideBar->setGeometry(QRect(r.left(), r.top(), sidebarWidth(), r.height())); } void CodeEditor::updateSidebarArea(const QRect& rect, int dy) { if (dy) m_sideBar->scroll(0, dy); else m_sideBar->update(0, rect.y(), m_sideBar->width(), rect.height()); } void CodeEditor::highlightCurrentLine() { QTextEdit::ExtraSelection selection; selection.format.setBackground(QColor(m_highlighter->theme().editorColor(KSyntaxHighlighting::Theme::CurrentLine))); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); QList extraSelections; extraSelections.append(selection); setExtraSelections(extraSelections); } QTextBlock CodeEditor::blockAtPosition(int y) const { auto block = firstVisibleBlock(); if (!block.isValid()) return QTextBlock(); int top = blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + blockBoundingRect(block).height(); do { if (top <= y && y <= bottom) return block; block = block.next(); top = bottom; bottom = top + blockBoundingRect(block).height(); } while (block.isValid()); return QTextBlock(); } bool CodeEditor::isFoldable(const QTextBlock &block) const { return m_highlighter->startsFoldingRegion(block); } bool CodeEditor::isFolded(const QTextBlock &block) const { if (!block.isValid()) return false; const auto nextBlock = block.next(); if (!nextBlock.isValid()) return false; return !nextBlock.isVisible(); } void CodeEditor::toggleFold(const QTextBlock &startBlock) { // we also want to fold the last line of the region, therefore the ".next()" const auto endBlock = m_highlighter->findFoldingRegionEnd(startBlock).next(); if (isFolded(startBlock)) { // unfold auto block = startBlock.next(); while (block.isValid() && !block.isVisible()) { block.setVisible(true); block.setLineCount(block.layout()->lineCount()); block = block.next(); } } else { // fold auto block = startBlock.next(); while (block.isValid() && block != endBlock) { block.setVisible(false); block.setLineCount(0); block = block.next(); } } // redraw document document()->markContentsDirty(startBlock.position(), endBlock.position() - startBlock.position() + 1); // update scrollbars emit document()->documentLayout()->documentSizeChanged(document()->documentLayout()->documentSize()); } #include "codeeditor.moc" diff --git a/examples/codeeditor.h b/examples/codeeditor.h index 46a9719..1823b43 100644 --- a/examples/codeeditor.h +++ b/examples/codeeditor.h @@ -1,63 +1,69 @@ /* Copyright (C) 2016 Volker Krause - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU Library General Public License as published by - the Free Software Foundation; either version 2 of the License, or (at your - option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public - License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CODEEDITOR_H #define CODEEDITOR_H #include #include namespace KSyntaxHighlighting { class SyntaxHighlighter; } class CodeEditorSidebar; class CodeEditor : public QPlainTextEdit { Q_OBJECT public: explicit CodeEditor(QWidget *parent = nullptr); ~CodeEditor(); void openFile(const QString &fileName); protected: void contextMenuEvent(QContextMenuEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: friend class CodeEditorSidebar; void setTheme(const KSyntaxHighlighting::Theme &theme); int sidebarWidth() const; void sidebarPaintEvent(QPaintEvent *event); void updateSidebarGeometry(); void updateSidebarArea(const QRect &rect, int dy); void highlightCurrentLine(); QTextBlock blockAtPosition(int y) const; bool isFoldable(const QTextBlock &block) const; bool isFolded(const QTextBlock &block) const; void toggleFold(const QTextBlock &block); KSyntaxHighlighting::Repository m_repository; KSyntaxHighlighting::SyntaxHighlighter *m_highlighter; CodeEditorSidebar *m_sideBar; }; #endif // CODEEDITOR_H diff --git a/examples/main.cpp b/examples/main.cpp index 65128a7..3fb5429 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -1,40 +1,46 @@ /* Copyright (C) 2016 Volker Krause - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU Library General Public License as published by - the Free Software Foundation; either version 2 of the License, or (at your - option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public - License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "codeeditor.h" #include #include #include #include int main(int argc, char **argv) { QApplication app(argc, argv); QCommandLineParser parser; parser.addHelpOption(); parser.addPositionalArgument(QStringLiteral("source"), QStringLiteral("The source file to highlight.")); parser.process(app); CodeEditor edit; edit.resize(1024, 1024); edit.show(); if (parser.positionalArguments().size() == 1) edit.openFile(parser.positionalArguments().at(0)); return app.exec(); }