From dc64f3207bdf6c0d295859e47791cb8193e67f4e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 25 Sep 2020 15:04:36 +0200 Subject: [PATCH] Editor: Make line spacing adjustable Fixes: QTCREATORBUG-13727 Change-Id: I3dbc3277795b339bced81dc6c5a048c828183cb6 Reviewed-by: Christian Stenger --- src/plugins/fakevim/fakevimplugin.cpp | 2 +- src/plugins/texteditor/fontsettings.cpp | 80 +++++++---- src/plugins/texteditor/fontsettings.h | 7 + src/plugins/texteditor/fontsettingspage.cpp | 16 ++- src/plugins/texteditor/fontsettingspage.ui | 125 ++++++++++++------ src/plugins/texteditor/textdocumentlayout.cpp | 8 ++ src/plugins/texteditor/texteditor.cpp | 45 ++++--- 7 files changed, 197 insertions(+), 86 deletions(-) diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index f81b8146cf6..cd3509b45ed 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -324,7 +324,7 @@ private: { QTextCursor tc = m_editor->textCursor(); m_currentPos = tc.position(); - m_lineSpacing = m_editor->cursorRect(tc).height(); + m_lineSpacing = m_editor->document()->documentLayout()->blockBoundingRect(tc.block()).height(); setFont(m_editor->extraArea()->font()); // Follow geometry of normal line numbers if visible, diff --git a/src/plugins/texteditor/fontsettings.cpp b/src/plugins/texteditor/fontsettings.cpp index d74a13b2cc5..26d3d041942 100644 --- a/src/plugins/texteditor/fontsettings.cpp +++ b/src/plugins/texteditor/fontsettings.cpp @@ -45,6 +45,7 @@ static const char fontFamilyKey[] = "FontFamily"; static const char fontSizeKey[] = "FontSize"; static const char fontZoomKey[] = "FontZoom"; +static const char lineSpacingKey[] = "LineSpacing"; static const char antialiasKey[] = "FontAntialias"; static const char schemeFileNamesKey[] = "ColorSchemes"; @@ -56,11 +57,13 @@ static const bool DEFAULT_ANTIALIAS = true; namespace TextEditor { // -- FontSettings -FontSettings::FontSettings() : - m_family(defaultFixedFontFamily()), - m_fontSize(defaultFontSize()), - m_fontZoom(100), - m_antialias(DEFAULT_ANTIALIAS) +FontSettings::FontSettings() + : m_family(defaultFixedFontFamily()) + , m_fontSize(defaultFontSize()) + , m_fontZoom(100) + , m_lineSpacing(100) + , m_antialias(DEFAULT_ANTIALIAS) + , m_lineSpacingCache(0) { } @@ -69,10 +72,10 @@ void FontSettings::clear() m_family = defaultFixedFontFamily(); m_fontSize = defaultFontSize(); m_fontZoom = 100; + m_lineSpacing = 100; m_antialias = DEFAULT_ANTIALIAS; m_scheme.clear(); - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); } static QString settingsGroup() @@ -89,9 +92,12 @@ void FontSettings::toSettings(QSettings *s) const if (m_fontSize != defaultFontSize() || s->contains(QLatin1String(fontSizeKey))) s->setValue(QLatin1String(fontSizeKey), m_fontSize); - if (m_fontZoom!= 100 || s->contains(QLatin1String(fontZoomKey))) + if (m_fontZoom != 100 || s->contains(QLatin1String(fontZoomKey))) s->setValue(QLatin1String(fontZoomKey), m_fontZoom); + if (m_lineSpacing != 100 || s->contains(QLatin1String(lineSpacingKey))) + s->setValue(QLatin1String(lineSpacingKey), m_lineSpacing); + if (m_antialias != DEFAULT_ANTIALIAS || s->contains(QLatin1String(antialiasKey))) s->setValue(QLatin1String(antialiasKey), m_antialias); @@ -116,7 +122,8 @@ bool FontSettings::fromSettings(const FormatDescriptions &descriptions, const QS m_family = s->value(group + QLatin1String(fontFamilyKey), defaultFixedFontFamily()).toString(); m_fontSize = s->value(group + QLatin1String(fontSizeKey), m_fontSize).toInt(); - m_fontZoom= s->value(group + QLatin1String(fontZoomKey), m_fontZoom).toInt(); + m_fontZoom = s->value(group + QLatin1String(fontZoomKey), m_fontZoom).toInt(); + m_lineSpacing = s->value(group + QLatin1String(lineSpacingKey), m_lineSpacing).toInt(); m_antialias = s->value(group + QLatin1String(antialiasKey), DEFAULT_ANTIALIAS).toBool(); if (s->contains(group + QLatin1String(schemeFileNamesKey))) { @@ -134,11 +141,12 @@ bool FontSettings::fromSettings(const FormatDescriptions &descriptions, const QS bool FontSettings::equals(const FontSettings &f) const { return m_family == f.m_family - && m_schemeFileName == f.m_schemeFileName - && m_fontSize == f.m_fontSize - && m_fontZoom == f.m_fontZoom - && m_antialias == f.m_antialias - && m_scheme == f.m_scheme; + && m_schemeFileName == f.m_schemeFileName + && m_fontSize == f.m_fontSize + && m_lineSpacing == f.m_lineSpacing + && m_fontZoom == f.m_fontZoom + && m_antialias == f.m_antialias + && m_scheme == f.m_scheme; } uint qHash(const TextStyle &textStyle) @@ -272,6 +280,13 @@ void FontSettings::addMixinStyle(QTextCharFormat &textCharFormat, }; } +void FontSettings::clearCaches() +{ + m_formatCache.clear(); + m_textCharFormatCache.clear(); + m_lineSpacingCache = 0; +} + QTextCharFormat FontSettings::toTextCharFormat(TextStyles textStyles) const { auto textCharFormatIterator = m_textCharFormatCache.find(textStyles); @@ -312,8 +327,7 @@ QString FontSettings::family() const void FontSettings::setFamily(const QString &family) { m_family = family; - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); } /** @@ -327,8 +341,7 @@ int FontSettings::fontSize() const void FontSettings::setFontSize(int size) { m_fontSize = size; - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); } /** @@ -344,6 +357,28 @@ void FontSettings::setFontZoom(int zoom) m_fontZoom = zoom; m_formatCache.clear(); m_textCharFormatCache.clear(); + m_lineSpacingCache = 0; +} + +qreal FontSettings::lineSpacing() const +{ + if (qFuzzyIsNull(m_lineSpacingCache)) { + auto currentFont = font(); + currentFont.setPointSize(m_fontSize * m_fontZoom / 100); + m_lineSpacingCache = QFontMetricsF(currentFont).lineSpacing() / 100 * m_lineSpacing; + } + return m_lineSpacingCache; +} + +int FontSettings::relativeLineSpacing() const +{ + return m_lineSpacing; +} + +void FontSettings::setRelativeLineSpacing(int relativeLineSpacing) +{ + m_lineSpacing = relativeLineSpacing; + m_lineSpacingCache = 0; } QFont FontSettings::font() const @@ -364,8 +399,7 @@ bool FontSettings::antialias() const void FontSettings::setAntialias(bool antialias) { m_antialias = antialias; - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); } /** @@ -402,8 +436,7 @@ void FontSettings::setColorSchemeFileName(const QString &fileName) bool FontSettings::loadColorScheme(const QString &fileName, const FormatDescriptions &descriptions) { - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); bool loaded = true; m_schemeFileName = fileName; @@ -459,8 +492,7 @@ const ColorScheme &FontSettings::colorScheme() const void FontSettings::setColorScheme(const ColorScheme &scheme) { m_scheme = scheme; - m_formatCache.clear(); - m_textCharFormatCache.clear(); + clearCaches(); } static QString defaultFontFamily() diff --git a/src/plugins/texteditor/fontsettings.h b/src/plugins/texteditor/fontsettings.h index 9447141434c..0fbc80e15d4 100644 --- a/src/plugins/texteditor/fontsettings.h +++ b/src/plugins/texteditor/fontsettings.h @@ -75,6 +75,10 @@ public: int fontZoom() const; void setFontZoom(int zoom); + qreal lineSpacing() const; + int relativeLineSpacing() const; + void setRelativeLineSpacing(int relativeLineSpacing); + QFont font() const; bool antialias() const; @@ -100,16 +104,19 @@ public: private: void addMixinStyle(QTextCharFormat &textCharFormat, const MixinTextStyles &mixinStyles) const; + void clearCaches(); private: QString m_family; QString m_schemeFileName; int m_fontSize; int m_fontZoom; + int m_lineSpacing; bool m_antialias; ColorScheme m_scheme; mutable QHash m_formatCache; mutable QHash m_textCharFormatCache; + mutable qreal m_lineSpacingCache; }; inline bool operator==(const FontSettings &f1, const FontSettings &f2) { return f1.equals(f2); } diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp index 0e35cade04a..41450ec8cfe 100644 --- a/src/plugins/texteditor/fontsettingspage.cpp +++ b/src/plugins/texteditor/fontsettingspage.cpp @@ -31,9 +31,9 @@ #include #include -#include #include #include +#include #include #include @@ -128,6 +128,11 @@ public: m_ui.antialias->setChecked(m_value.antialias()); m_ui.zoomSpinBox->setValue(m_value.fontZoom()); + m_ui.lineSpacingSpinBox->setValue(m_value.relativeLineSpacing()); + m_ui.lineSpacingWarningLabel->setPixmap(Utils::Icons::WARNING.pixmap()); + m_ui.lineSpacingWarningLabel->setToolTip(tr("A line spacing less than 100% can result in " + "overlapping and misaligned graphics.")); + m_ui.lineSpacingWarningLabel->setVisible(m_value.relativeLineSpacing() < 100); m_ui.schemeEdit->setFormatDescriptions(fd); m_ui.schemeEdit->setBaseFont(m_value.font()); @@ -143,6 +148,8 @@ public: this, &FontSettingsPageWidget::fontSizeSelected); connect(m_ui.zoomSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &FontSettingsPageWidget::fontZoomChanged); + connect(m_ui.lineSpacingSpinBox, QOverload::of(&QSpinBox::valueChanged), + this, &FontSettingsPageWidget::lineSpacingChanged); connect(m_ui.antialias, &QCheckBox::toggled, this, &FontSettingsPageWidget::antialiasChanged); connect(m_ui.schemeComboBox, @@ -166,6 +173,7 @@ public: void fontSelected(const QFont &font); void fontSizeSelected(int index); void fontZoomChanged(); + void lineSpacingChanged(const int &value); void antialiasChanged(); void colorSchemeSelected(int index); void openCopyColorSchemeDialog(); @@ -417,6 +425,12 @@ void FontSettingsPageWidget::fontZoomChanged() m_value.setFontZoom(m_ui.zoomSpinBox->value()); } +void FontSettingsPageWidget::lineSpacingChanged(const int &value) +{ + m_value.setRelativeLineSpacing(value); + m_ui.lineSpacingWarningLabel->setVisible(value < 100); +} + void FontSettingsPageWidget::antialiasChanged() { m_value.setAntialias(m_ui.antialias->isChecked()); diff --git a/src/plugins/texteditor/fontsettingspage.ui b/src/plugins/texteditor/fontsettingspage.ui index 5c61c007385..da8a9fa907e 100644 --- a/src/plugins/texteditor/fontsettingspage.ui +++ b/src/plugins/texteditor/fontsettingspage.ui @@ -6,7 +6,7 @@ 0 0 - 639 + 752 306 @@ -17,40 +17,23 @@ Font - - + + - 0 + 1 0 - - Size: + + true + + + - - - Zoom: - - - - - - - - 0 - 0 - - - - Family: - - - - Qt::Horizontal @@ -66,7 +49,7 @@ - + Qt::Horizontal @@ -79,6 +62,19 @@ + + + + + 0 + 0 + + + + Size: + + + @@ -86,6 +82,32 @@ + + + + % + + + 10 + + + 3000 + + + 10 + + + 100 + + + + + + + Zoom: + + + @@ -102,41 +124,61 @@ - - + + % - 10 + 50 3000 - - 10 - 100 - - - - - + + - 1 + 0 0 - - true + + Family: + + + + Line spacing: + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 20 + 20 + + + + + + + @@ -200,6 +242,7 @@ fontComboBox sizeComboBox zoomSpinBox + lineSpacingSpinBox antialias schemeComboBox copyButton diff --git a/src/plugins/texteditor/textdocumentlayout.cpp b/src/plugins/texteditor/textdocumentlayout.cpp index a2f292e9c71..70b63e0303b 100644 --- a/src/plugins/texteditor/textdocumentlayout.cpp +++ b/src/plugins/texteditor/textdocumentlayout.cpp @@ -24,8 +24,13 @@ ****************************************************************************/ #include "textdocumentlayout.h" + +#include "fontsettings.h" #include "textdocument.h" +#include "texteditorsettings.h" + #include + #include namespace TextEditor { @@ -625,6 +630,9 @@ void TextDocumentLayout::updateMarksBlock(const QTextBlock &block) QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const { QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block); + if (boundingRect.isNull()) + return boundingRect; + boundingRect.setHeight(TextEditorSettings::fontSettings().lineSpacing()); if (TextBlockUserData *userData = textUserData(block)) boundingRect.adjust(0, 0, 0, userData->additionalAnnotationHeight()); return boundingRect; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 283f2155574..aec12dc75a9 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -1105,9 +1105,9 @@ void TextEditorWidget::print(QPrinter *printer) delete dlg; } -static int foldBoxWidth(const QFontMetrics &fm) +static int foldBoxWidth() { - const int lineSpacing = fm.lineSpacing(); + const int lineSpacing = TextEditorSettings::fontSettings().lineSpacing(); return lineSpacing + lineSpacing % 2 + 1; } @@ -3604,9 +3604,9 @@ QRect TextEditorWidgetPrivate::foldBox() QRectF br = q->blockBoundingGeometry(begin).translated(q->contentOffset()); QRectF er = q->blockBoundingGeometry(end).translated(q->contentOffset()); - return QRect(m_extraArea->width() - foldBoxWidth(q->fontMetrics()), + return QRect(m_extraArea->width() - foldBoxWidth(), int(br.top()), - foldBoxWidth(q->fontMetrics()), + foldBoxWidth(), int(er.bottom() - br.top())); } @@ -4083,7 +4083,9 @@ bool TextEditorWidgetPrivate::updateAnnotationBounds(TextBlockUserData *blockUse { const bool additionalHeightNeeded = annotationsVisible && m_displaySettings.m_annotationAlignment == AnnotationAlignment::BetweenLines; - const int additionalHeight = additionalHeightNeeded ? q->fontMetrics().lineSpacing() : 0; + const int additionalHeight = additionalHeightNeeded + ? TextEditorSettings::fontSettings().lineSpacing() + : 0; if (blockUserData->additionalAnnotationHeight() == additionalHeight) return false; blockUserData->setAdditionalAnnotationHeight(additionalHeight); @@ -4124,7 +4126,7 @@ void TextEditorWidgetPrivate::updateLineAnnotation(const PaintEventData &data, return mark1->priority() > mark2->priority(); }); - const qreal itemOffset = q->fontMetrics().lineSpacing(); + const qreal itemOffset = blockData.boundingRect.height(); const qreal initialOffset = m_displaySettings.m_annotationAlignment == AnnotationAlignment::BetweenLines ? itemOffset / 2 : itemOffset * 2; const qreal minimalContentWidth = q->fontMetrics().horizontalAdvance('X') * m_displaySettings.m_minimalAnnotationContent; @@ -5018,7 +5020,7 @@ int TextEditorWidget::extraAreaWidth(int *markWidthPtr) const int markWidth = 0; if (d->m_marksVisible) { - markWidth += documentLayout->maxMarkWidthFactor * fm.lineSpacing() + 2; + markWidth += documentLayout->maxMarkWidthFactor * TextEditorSettings::fontSettings().lineSpacing() + 2; // if (documentLayout->doubleMarkCount) // markWidth += fm.lineSpacing() / 3; @@ -5033,7 +5035,7 @@ int TextEditorWidget::extraAreaWidth(int *markWidthPtr) const space += 4; if (d->m_codeFoldingVisible) - space += foldBoxWidth(fm); + space += foldBoxWidth(); if (viewportMargins() != QMargins{isLeftToRight() ? space : 0, 0, isLeftToRight() ? 0 : space, 0}) d->slotUpdateExtraAreaWidth(space); @@ -5059,9 +5061,9 @@ struct Internal::ExtraAreaPaintEventData , selectionStart(editor->textCursor().selectionStart()) , selectionEnd(editor->textCursor().selectionEnd()) , fontMetrics(d->m_extraArea->font()) - , lineSpacing(fontMetrics.lineSpacing()) + , lineSpacing(TextEditorSettings::fontSettings().lineSpacing()) , markWidth(d->m_marksVisible ? lineSpacing : 0) - , collapseColumnWidth(d->m_codeFoldingVisible ? foldBoxWidth(fontMetrics) : 0) + , collapseColumnWidth(d->m_codeFoldingVisible ? foldBoxWidth() : 0) , extraAreaWidth(d->m_extraArea->width() - collapseColumnWidth) , currentLineNumberFormat( editor->textDocument()->fontSettings().toTextCharFormat(C_CURRENT_LINE_NUMBER)) @@ -5185,7 +5187,7 @@ void TextEditorWidgetPrivate::paintCodeFolding(QPainter &painter, bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber && blockNumber <= extraAreaHighlightFoldEndBlockNumber; - int boxWidth = foldBoxWidth(data.fontMetrics); + int boxWidth = foldBoxWidth(); if (hovered) { int itop = qRound(blockBoundingRect.top()); int ibottom = qRound(blockBoundingRect.bottom()); @@ -5558,8 +5560,10 @@ void TextEditorWidget::mouseMoveEvent(QMouseEvent *e) if (cursor.positionInBlock() == cursor.block().length()-1) column += (e->pos().x() - cursorRect().center().x()) / QFontMetricsF(font()).horizontalAdvance(QLatin1Char(' ')); int block = cursor.blockNumber(); - if (block == blockCount() - 1) - block += (e->pos().y() - cursorRect().center().y()) / QFontMetricsF(font()).lineSpacing(); + if (block == blockCount() - 1) { + block += (e->pos().y() - cursorRect().center().y()) + / TextEditorSettings::fontSettings().lineSpacing(); + } d->enableBlockSelection(block, column, block, column); } } else { @@ -5609,8 +5613,11 @@ void TextEditorWidget::mousePressEvent(QMouseEvent *e) if (cursor.positionInBlock() == cursor.block().length()-1) column += (e->pos().x() - cursorRect(cursor).center().x()) / QFontMetricsF(font()).horizontalAdvance(QLatin1Char(' ')); int block = cursor.blockNumber(); - if (block == blockCount() - 1) - block += (e->pos().y() - cursorRect(cursor).center().y()) / QFontMetricsF(font()).lineSpacing(); + if (block == blockCount() - 1) { + block += (e->pos().y() - cursorRect(cursor).center().y()) + / TextEditorSettings::fontSettings().lineSpacing(); + } + if (d->m_inBlockSelectionMode) { d->m_blockSelection.positionBlock = block; d->m_blockSelection.positionColumn = column; @@ -5784,7 +5791,7 @@ void TextEditorWidget::updateFoldingHighlight(const QPoint &pos) const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber; d->extraAreaHighlightFoldedBlockNumber = -1; - if (pos.x() > extraArea()->width() - foldBoxWidth(fontMetrics())) { + if (pos.x() > extraArea()->width() - foldBoxWidth()) { d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); } else if (d->m_displaySettings.m_highlightBlocks) { QTextCursor cursor = textCursor(); @@ -5827,7 +5834,7 @@ void TextEditorWidget::extraAreaMouseEvent(QMouseEvent *e) int dist = (e->pos() - d->m_markDragStart).manhattanLength(); if (dist > QApplication::startDragDistance()) { d->m_markDragging = true; - const int height = fontMetrics().lineSpacing() - 1; + const int height = TextEditorSettings::fontSettings().lineSpacing() - 1; const int width = int(.5 + height * d->m_dragMark->widthFactor()); d->m_markDragCursor = QCursor(d->m_dragMark->icon().pixmap({height, width})); d->m_dragMark->setVisible(false); @@ -5845,7 +5852,7 @@ void TextEditorWidget::extraAreaMouseEvent(QMouseEvent *e) if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { if (e->button() == Qt::LeftButton) { - int boxWidth = foldBoxWidth(fontMetrics()); + int boxWidth = foldBoxWidth(); if (d->m_codeFoldingVisible && e->pos().x() > extraArea()->width() - boxWidth) { if (!cursor.block().next().isVisible()) { d->toggleBlockVisible(cursor.block()); @@ -6372,7 +6379,7 @@ void TextEditorWidgetPrivate::adjustScrollBarRanges() { if (!m_highlightScrollBarController) return; - const double lineSpacing = QFontMetricsF(q->font()).lineSpacing(); + const double lineSpacing = TextEditorSettings::fontSettings().lineSpacing(); if (lineSpacing == 0) return;