diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index 743df4fccc3..9da3686bda2 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -130,6 +130,9 @@ VcsBase_FileRenamed_TextColor=ffffa500 Bookmarks_TextMarkColor=ff8080ff +TextEditor_SearchResult_ScrollBarColor=ff00c000 +TextEditor_CurrentLine_ScrollBarColor=ffffffff + ProjectExplorer_TaskError_TextMarkColor=ffff4040 ProjectExplorer_TaskWarn_TextMarkColor=ffffff40 diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme index ec54297d1cd..5ef0b5c86cd 100644 --- a/share/qtcreator/themes/default.creatortheme +++ b/share/qtcreator/themes/default.creatortheme @@ -124,6 +124,9 @@ VcsBase_FileRenamed_TextColor=ffd77d00 Bookmarks_TextMarkColor=ffa0a0ff +TextEditor_SearchResult_ScrollBarColor=ff00c000 +TextEditor_CurrentLine_ScrollBarColor=ff404040 + ProjectExplorer_TaskError_TextMarkColor=ffff0000 ProjectExplorer_TaskWarn_TextMarkColor=ffffa500 diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 0b136d6718b..ea18f32dcd0 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -185,6 +185,10 @@ public: /* Bookmarks Plugin */ Bookmarks_TextMarkColor, + /* TextEditor Plugin */ + TextEditor_SearchResult_ScrollBarColor, + TextEditor_CurrentLine_ScrollBarColor, + /* Debugger Plugin */ Debugger_Breakpoint_TextMarkColor, diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index f0a32893cea..d2873702f19 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -223,6 +223,8 @@ QtcPlugin { "findtoolwindow.cpp", "findtoolwindow.h", "findwidget.ui", + "highlightscrollbar.cpp", + "highlightscrollbar.h", "ifindfilter.cpp", "ifindfilter.h", "ifindsupport.cpp", diff --git a/src/plugins/coreplugin/find/find.pri b/src/plugins/coreplugin/find/find.pri index c4637ddf117..3043e4650bc 100644 --- a/src/plugins/coreplugin/find/find.pri +++ b/src/plugins/coreplugin/find/find.pri @@ -1,37 +1,40 @@ HEADERS += \ - $$PWD/findtoolwindow.h \ - $$PWD/textfindconstants.h \ - $$PWD/ifindsupport.h \ - $$PWD/ifindfilter.h \ - $$PWD/currentdocumentfind.h \ $$PWD/basetextfind.h \ - $$PWD/findtoolbar.h \ + $$PWD/currentdocumentfind.h \ $$PWD/findplugin.h \ + $$PWD/findtoolbar.h \ + $$PWD/findtoolwindow.h \ + $$PWD/ifindfilter.h \ + $$PWD/ifindsupport.h \ + $$PWD/itemviewfind.h \ $$PWD/searchresultcolor.h \ $$PWD/searchresulttreeitemdelegate.h \ $$PWD/searchresulttreeitemroles.h \ $$PWD/searchresulttreeitems.h \ $$PWD/searchresulttreemodel.h \ $$PWD/searchresulttreeview.h \ - $$PWD/searchresultwindow.h \ $$PWD/searchresultwidget.h \ - $$PWD/itemviewfind.h + $$PWD/searchresultwindow.h \ + $$PWD/textfindconstants.h \ + $$PWD/highlightscrollbar.h SOURCES += \ - $$PWD/findtoolwindow.cpp \ - $$PWD/currentdocumentfind.cpp \ $$PWD/basetextfind.cpp \ - $$PWD/findtoolbar.cpp \ + $$PWD/currentdocumentfind.cpp \ $$PWD/findplugin.cpp \ + $$PWD/findtoolbar.cpp \ + $$PWD/findtoolwindow.cpp \ + $$PWD/ifindfilter.cpp \ + $$PWD/ifindsupport.cpp \ + $$PWD/itemviewfind.cpp \ $$PWD/searchresulttreeitemdelegate.cpp \ $$PWD/searchresulttreeitems.cpp \ $$PWD/searchresulttreemodel.cpp \ $$PWD/searchresulttreeview.cpp \ - $$PWD/searchresultwindow.cpp \ - $$PWD/ifindfilter.cpp \ - $$PWD/ifindsupport.cpp \ $$PWD/searchresultwidget.cpp \ - $$PWD/itemviewfind.cpp + $$PWD/searchresultwindow.cpp \ + $$PWD/highlightscrollbar.cpp + FORMS += \ $$PWD/findwidget.ui \ diff --git a/src/plugins/coreplugin/find/highlightscrollbar.cpp b/src/plugins/coreplugin/find/highlightscrollbar.cpp new file mode 100644 index 00000000000..35d3e3ecaa8 --- /dev/null +++ b/src/plugins/coreplugin/find/highlightscrollbar.cpp @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://www.qt.io/licensing. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "highlightscrollbar.h" + +#include +#include +#include +#include +#include + +using namespace Core; +using namespace Utils; + +HighlightScrollBar::HighlightScrollBar(Qt::Orientation orientation, QWidget *parent) + : QScrollBar(orientation, parent) + , m_widget(parent) + , m_overlay(new HighlightScrollBarOverlay(this)) +{ + connect(m_overlay, &HighlightScrollBarOverlay::destroyed, + this, &HighlightScrollBar::overlayDestroyed); + // valueChanged(0) flashes transient scroll bars, which is needed + // for a correct initialization. + emit valueChanged(0); +} + +HighlightScrollBar::~HighlightScrollBar() +{ + if (!m_overlay || m_overlay->parent() == this) + return; + + delete m_overlay; +} + +void HighlightScrollBar::setVisibleRange(float visibleRange) +{ + if (!m_overlay) + return; + m_overlay->m_visibleRange = visibleRange; +} + +void HighlightScrollBar::setRangeOffset(float offset) +{ + if (!m_overlay) + return; + m_overlay->m_offset = offset; +} + +void HighlightScrollBar::setColor(Id category, Theme::Color color) +{ + if (!m_overlay) + return; + m_overlay->m_colors[category] = color; +} + +QRect HighlightScrollBar::overlayRect() +{ + QStyleOptionSlider opt; + initStyleOption(&opt); + return style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); +} + +void HighlightScrollBar::overlayDestroyed() +{ + m_overlay = 0; +} + +void HighlightScrollBar::setPriority(Id category, HighlightScrollBar::Priority prio) +{ + if (!m_overlay) + return; + m_overlay->m_priorities[category] = prio; + m_overlay->scheduleUpdate(); +} + +void HighlightScrollBar::addHighlights(Id category, QSet highlights) +{ + if (!m_overlay) + return; + m_overlay->m_highlights[category].unite(highlights); + m_overlay->scheduleUpdate(); +} + +void HighlightScrollBar::addHighlight(Id category, int highlight) +{ + if (!m_overlay) + return; + m_overlay->m_highlights[category] << highlight; + m_overlay->scheduleUpdate(); +} + +void HighlightScrollBar::removeHighlights(Id category) +{ + if (!m_overlay) + return; + m_overlay->m_highlights.remove(category); + m_overlay->scheduleUpdate(); +} + +void HighlightScrollBar::removeAllHighlights() +{ + if (!m_overlay) + return; + m_overlay->m_highlights.clear(); + m_overlay->scheduleUpdate(); +} + +bool HighlightScrollBar::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_widget && m_overlay && m_widget == m_overlay->parent() && + (event->type() == QEvent::Resize || event->type() == QEvent::Move)) { + QStyleOptionSlider opt; + initStyleOption(&opt); + const int width = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, this); + m_overlay->move(m_widget->width() - width, 0); + resize(width, m_widget->height()); + } + return false; +} + +void HighlightScrollBar::resizeEvent(QResizeEvent *event) +{ + if (!m_overlay) + return; + QScrollBar::resizeEvent(event); + m_overlay->resize(size()); +} + +void HighlightScrollBar::moveEvent(QMoveEvent *event) +{ + if (!m_overlay) + return; + QScrollBar::moveEvent(event); + m_overlay->adjustPosition(); +} + +void HighlightScrollBar::showEvent(QShowEvent *event) +{ + if (!m_overlay) + return; + QScrollBar::showEvent(event); + if (parentWidget() != this) { + m_widget->removeEventFilter(this); + m_overlay->setParent(this); + m_overlay->adjustPosition(); + m_overlay->show(); + } +} + +void HighlightScrollBar::hideEvent(QHideEvent *event) +{ + if (!m_overlay) + return; + QScrollBar::hideEvent(event); + if (parentWidget() != m_widget) { + m_widget->installEventFilter(this); + m_overlay->setParent(m_widget); + m_overlay->adjustPosition(); + m_overlay->show(); + } +} + +void HighlightScrollBar::changeEvent(QEvent *event) +{ + // Workaround for QTBUG-45579 + if (event->type() == QEvent::ParentChange) + setStyle(style()); +} + +void HighlightScrollBarOverlay::scheduleUpdate() +{ + if (m_cacheUpdateScheduled) + return; + + m_cacheUpdateScheduled = true; + QTimer::singleShot(0, this, static_cast(&QWidget::update)); +} + +void HighlightScrollBarOverlay::updateCache() +{ + if (!m_cacheUpdateScheduled) + return; + + m_cache.clear(); + foreach (const Id &category, m_highlights.keys()) { + foreach (const int &highlight, m_highlights[category]) { + Id highlightCategory = m_cache[highlight]; + if (highlightCategory.isValid() && (m_priorities[highlightCategory] >= m_priorities[category])) + continue; + m_cache[highlight] = category; + } + } + m_cacheUpdateScheduled = false; +} + +void HighlightScrollBarOverlay::adjustPosition() +{ + move(parentWidget()->mapFromGlobal(m_scrollBar->mapToGlobal(m_scrollBar->pos()))); +} + +void HighlightScrollBarOverlay::paintEvent(QPaintEvent *paintEvent) +{ + QWidget::paintEvent(paintEvent); + + updateCache(); + + if (m_cache.isEmpty()) + return; + + const QRect &rect = m_scrollBar->overlayRect(); + + Id previousCategory; + QRect *previousRect = 0; + + const int scrollbarRange = m_scrollBar->maximum() + m_scrollBar->pageStep(); + const int range = qMax(m_visibleRange, float(scrollbarRange)); + const int horizontalMargin = 3; + const int resultWidth = rect.width() - 2 * horizontalMargin + 1; + const int resultHeight = qMin(int(rect.height() / range) + 1, 4); + const int offset = rect.height() / range * m_offset; + const int verticalMargin = ((rect.height() / range) - resultHeight) / 2; + int previousBottom = -1; + + QHash > highlights; + QMapIterator it(m_cache); + while (it.hasNext()) { + const Id currentCategory = it.next().value(); + + // Calculate start and end + int top = rect.top() + offset + verticalMargin + float(it.key()) / range * rect.height(); + const int bottom = top + resultHeight; + + if (previousCategory == currentCategory && previousBottom + 1 >= top) { + // If the previous highlight has the same category and is directly prior to this highlight + // we just extend the previous highlight. + previousRect->setBottom(bottom - 1); + + } else { // create a new highlight + if (previousRect && previousBottom >= top) { + // make sure that highlights with higher priority are drawn on top of other highlights + // when rectangles are overlapping + + if (m_priorities[previousCategory] > m_priorities[currentCategory]) { + // Moving the top of the current highlight when the previous + // highlight has a higher priority + top = previousBottom + 1; + if (top == bottom) // if this would result in an empty highlight just skip + continue; + } else { + previousRect->setBottom(top - 1); // move the end of the last highlight + if (previousRect->height() == 0) // if the result is an empty rect, remove it. + highlights[previousCategory].removeLast(); + } + } + highlights[currentCategory] << QRect(rect.left() + horizontalMargin, top, + resultWidth, bottom - top); + previousRect = &highlights[currentCategory].last(); + previousCategory = currentCategory; + } + previousBottom = previousRect->bottom(); + } + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, false); + foreach (Id category, highlights.keys()) { + const QColor &color = creatorTheme()->color(m_colors[category]); + for (int i = 0, total = highlights[category].size(); i < total; ++i) { + const QRect rect = highlights[category][i]; + painter.fillRect(rect, color); + } + } +} diff --git a/src/plugins/coreplugin/find/highlightscrollbar.h b/src/plugins/coreplugin/find/highlightscrollbar.h new file mode 100644 index 00000000000..ada6be06f86 --- /dev/null +++ b/src/plugins/coreplugin/find/highlightscrollbar.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://www.qt.io/licensing. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef HIGHLIGHTSCROLLBAR_H +#define HIGHLIGHTSCROLLBAR_H + +#include +#include +#include + +#include +#include +#include + +namespace Core { + +class HighlightScrollBarOverlay; + +class CORE_EXPORT HighlightScrollBar : public QScrollBar +{ + Q_OBJECT + +public: + HighlightScrollBar(Qt::Orientation orientation, QWidget *parent = 0); + ~HighlightScrollBar() override; + + void setVisibleRange(float visibleRange); + void setRangeOffset(float offset); + void setColor(Id category, Utils::Theme::Color color); + + enum Priority + { + LowPriority = 0, + NormalPriority = 1, + HighPriority = 2, + HighestPriority = 3 + }; + + void setPriority(Id category, Priority prio); + void addHighlight(Id category, int highlight); + void addHighlights(Id category, QSet highlights); + + void removeHighlights(Id id); + void removeAllHighlights(); + + bool eventFilter(QObject *, QEvent *event) override; + +protected: + void moveEvent(QMoveEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + void changeEvent(QEvent *even) override; + +private: + QRect overlayRect(); + void overlayDestroyed(); + + QWidget *m_widget; + HighlightScrollBarOverlay *m_overlay; + friend class HighlightScrollBarOverlay; +}; + +class HighlightScrollBarOverlay : public QWidget +{ + Q_OBJECT +public: + HighlightScrollBarOverlay(HighlightScrollBar *scrollBar) + : QWidget(scrollBar) + , m_visibleRange(0.0) + , m_offset(0.0) + , m_cacheUpdateScheduled(false) + , m_scrollBar(scrollBar) + {} + + void scheduleUpdate(); + void updateCache(); + void adjustPosition(); + + float m_visibleRange; + float m_offset; + QHash > m_highlights; + QHash m_colors; + QHash m_priorities; + + bool m_cacheUpdateScheduled; + QMap m_cache; + +protected: + void paintEvent(QPaintEvent *paintEvent) override; + +private: + HighlightScrollBar *m_scrollBar; +}; + +} // namespace Core + +#endif // HIGHLIGHTSCROLLBAR_H diff --git a/src/plugins/texteditor/displaysettings.cpp b/src/plugins/texteditor/displaysettings.cpp index d6a745b7376..f938a80d30e 100644 --- a/src/plugins/texteditor/displaysettings.cpp +++ b/src/plugins/texteditor/displaysettings.cpp @@ -46,6 +46,7 @@ static const char autoFoldFirstCommentKey[] = "AutoFoldFirstComment"; static const char centerCursorOnScrollKey[] = "CenterCursorOnScroll"; static const char openLinksInNextSplitKey[] = "OpenLinksInNextSplitKey"; static const char displayFileEncodingKey[] = "DisplayFileEncoding"; +static const char scrollBarHighlightsKey[] = "ScrollBarHighlights"; static const char groupPostfix[] = "DisplaySettings"; namespace TextEditor { @@ -64,7 +65,8 @@ DisplaySettings::DisplaySettings() : m_centerCursorOnScroll(false), m_openLinksInNextSplit(false), m_forceOpenLinksInNextSplit(false), - m_displayFileEncoding(false) + m_displayFileEncoding(false), + m_scrollBarHighlights(true) { } @@ -87,6 +89,7 @@ void DisplaySettings::toSettings(const QString &category, QSettings *s) const s->setValue(QLatin1String(centerCursorOnScrollKey), m_centerCursorOnScroll); s->setValue(QLatin1String(openLinksInNextSplitKey), m_openLinksInNextSplit); s->setValue(QLatin1String(displayFileEncodingKey), m_displayFileEncoding); + s->setValue(QLatin1String(scrollBarHighlightsKey), m_scrollBarHighlights); s->endGroup(); } @@ -112,6 +115,7 @@ void DisplaySettings::fromSettings(const QString &category, const QSettings *s) m_centerCursorOnScroll = s->value(group + QLatin1String(centerCursorOnScrollKey), m_centerCursorOnScroll).toBool(); m_openLinksInNextSplit = s->value(group + QLatin1String(openLinksInNextSplitKey), m_openLinksInNextSplit).toBool(); m_displayFileEncoding = s->value(group + QLatin1String(displayFileEncodingKey), m_displayFileEncoding).toBool(); + m_scrollBarHighlights = s->value(group + QLatin1String(scrollBarHighlightsKey), m_scrollBarHighlights).toBool(); } bool DisplaySettings::equals(const DisplaySettings &ds) const @@ -130,6 +134,7 @@ bool DisplaySettings::equals(const DisplaySettings &ds) const && m_openLinksInNextSplit == ds.m_openLinksInNextSplit && m_forceOpenLinksInNextSplit == ds.m_forceOpenLinksInNextSplit && m_displayFileEncoding == ds.m_displayFileEncoding + && m_scrollBarHighlights == ds.m_scrollBarHighlights ; } diff --git a/src/plugins/texteditor/displaysettings.h b/src/plugins/texteditor/displaysettings.h index 7c3c059fcc2..fc1aa906d87 100644 --- a/src/plugins/texteditor/displaysettings.h +++ b/src/plugins/texteditor/displaysettings.h @@ -61,6 +61,7 @@ public: bool m_openLinksInNextSplit; bool m_forceOpenLinksInNextSplit; bool m_displayFileEncoding; + bool m_scrollBarHighlights; bool equals(const DisplaySettings &ds) const; }; diff --git a/src/plugins/texteditor/displaysettingspage.cpp b/src/plugins/texteditor/displaysettingspage.cpp index b0896317f0b..f78c42c09e5 100644 --- a/src/plugins/texteditor/displaysettingspage.cpp +++ b/src/plugins/texteditor/displaysettingspage.cpp @@ -122,6 +122,7 @@ void DisplaySettingsPage::settingsFromUI(DisplaySettings &displaySettings, displaySettings.m_centerCursorOnScroll = d->m_page->centerOnScroll->isChecked(); displaySettings.m_openLinksInNextSplit = d->m_page->openLinksInNextSplit->isChecked(); displaySettings.m_displayFileEncoding = d->m_page->displayFileEncoding->isChecked(); + displaySettings.m_scrollBarHighlights = d->m_page->scrollBarHighlights->isChecked(); } void DisplaySettingsPage::settingsToUI() @@ -143,6 +144,7 @@ void DisplaySettingsPage::settingsToUI() d->m_page->centerOnScroll->setChecked(displaySettings.m_centerCursorOnScroll); d->m_page->openLinksInNextSplit->setChecked(displaySettings.m_openLinksInNextSplit); d->m_page->displayFileEncoding->setChecked(displaySettings.m_displayFileEncoding); + d->m_page->scrollBarHighlights->setChecked(displaySettings.m_scrollBarHighlights); } const DisplaySettings &DisplaySettingsPage::displaySettings() const diff --git a/src/plugins/texteditor/displaysettingspage.ui b/src/plugins/texteditor/displaysettingspage.ui index b60d8abe71f..dfc7e5e9ef9 100644 --- a/src/plugins/texteditor/displaysettingspage.ui +++ b/src/plugins/texteditor/displaysettingspage.ui @@ -7,7 +7,7 @@ 0 0 501 - 323 + 331 @@ -80,34 +80,10 @@ Display - - + + - &Highlight matching parentheses - - - - - - - Mark &text changes - - - - - - - Shows tabs and spaces. - - - &Visualize whitespace - - - - - - - Highlight &blocks + Display &folding markers @@ -118,17 +94,17 @@ - - + + - Display &folding markers + Highlight &blocks - - + + - Highlight current &line + Auto-fold first &comment @@ -139,6 +115,13 @@ + + + + Highlight current &line + + + @@ -146,13 +129,6 @@ - - - - Auto-fold first &comment - - - @@ -167,6 +143,37 @@ + + + + Shows tabs and spaces. + + + &Visualize whitespace + + + + + + + &Highlight matching parentheses + + + + + + + Mark &text changes + + + + + + + Highlight search results on the scrollbar + + + diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 3b78f9dd651..f1ba302603b 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -77,15 +77,18 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -271,6 +274,7 @@ public: void copyLineUpDown(bool up); void saveCurrentCursorPositionForNavigation(); void updateHighlights(); + void updateCurrentLineInScrollbar(); void updateCurrentLineHighlight(); void drawFoldingMarker(QPainter *painter, const QPalette &pal, @@ -309,6 +313,19 @@ public: void documentAboutToBeReloaded(); void documentReloadFinished(bool success); void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags); + void searchResultsReady(int beginIndex, int endIndex); + void searchFinished(); + void setupScrollBar(); + void highlightSearchResultsInScrollBar(); + void scheduleUpdateHighlightScrollBar(); + void updateHighlightScrollBarNow(); + struct SearchResult { + int start; + int length; + }; + void addSearchResultsToScrollBar(QVector results); + void adjustScrollBarRanges(); + void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int); void updateCursorPosition(); @@ -448,6 +465,12 @@ public: QScopedPointer m_autoCompleter; CommentDefinition m_commentDefinition; + + QFutureWatcher *m_searchWatcher; + QVector m_searchResults; + QTimer m_scrollBarUpdateTimer; + HighlightScrollBar *m_highlightScrollBar; + bool m_scrollBarUpdateScheduled; }; TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) @@ -499,7 +522,11 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) m_markDragging(false), m_clipboardAssistProvider(new ClipboardAssistProvider), m_isMissingSyntaxDefinition(false), - m_autoCompleter(new AutoCompleter) + m_autoCompleter(new AutoCompleter), + m_searchWatcher(0), + m_scrollBarUpdateTimer(0), + m_highlightScrollBar(0), + m_scrollBarUpdateScheduled(false) { Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; BaseTextFind *baseTextFind = new BaseTextFind(q); @@ -606,6 +633,29 @@ void TextEditorWidget::setTextDocument(const QSharedPointer &doc) d->ctor(doc); } +void TextEditorWidgetPrivate::setupScrollBar() +{ + if (m_displaySettings.m_scrollBarHighlights) { + if (m_highlightScrollBar) + return; + m_highlightScrollBar = new HighlightScrollBar(Qt::Vertical, q); + m_highlightScrollBar->setColor(Constants::SCROLL_BAR_SEARCH_RESULT, + Theme::TextEditor_SearchResult_ScrollBarColor); + m_highlightScrollBar->setColor(Constants::SCROLL_BAR_CURRENT_LINE, + Theme::TextEditor_CurrentLine_ScrollBarColor); + m_highlightScrollBar->setPriority( + Constants::SCROLL_BAR_SEARCH_RESULT, HighlightScrollBar::HighPriority); + m_highlightScrollBar->setPriority( + Constants::SCROLL_BAR_CURRENT_LINE, HighlightScrollBar::HighestPriority); + q->setVerticalScrollBar(m_highlightScrollBar); + highlightSearchResultsInScrollBar(); + scheduleUpdateHighlightScrollBar(); + } else if (m_highlightScrollBar) { + q->setVerticalScrollBar(new QScrollBar(Qt::Vertical, q)); + m_highlightScrollBar = 0; + } +} + void TextEditorWidgetPrivate::ctor(const QSharedPointer &doc) { q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); @@ -668,6 +718,10 @@ void TextEditorWidgetPrivate::ctor(const QSharedPointer &doc) QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout, this, &TextEditorWidgetPrivate::_q_highlightBlocks); + m_scrollBarUpdateTimer.setSingleShot(true); + QObject::connect(&m_scrollBarUpdateTimer, &QTimer::timeout, + this, &TextEditorWidgetPrivate::highlightSearchResultsInScrollBar); + m_animator = 0; slotUpdateExtraAreaWidth(); @@ -1073,6 +1127,7 @@ void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemove q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount); } m_blockCount = newBlockCount; + m_scrollBarUpdateTimer.start(500); } void TextEditorWidgetPrivate::slotSelectionChanged() @@ -2568,6 +2623,9 @@ void TextEditorWidgetPrivate::documentAboutToBeReloaded() m_snippetOverlay->clear(); m_searchResultOverlay->clear(); m_refactorOverlay->clear(); + + // clear search results + m_searchResults.clear(); } void TextEditorWidgetPrivate::documentReloadFinished(bool success) @@ -2873,6 +2931,15 @@ void TextEditorWidgetPrivate::setupDocumentSignals() QObject::connect(q, &TextEditorWidget::requestBlockUpdate, documentLayout, &QPlainTextDocumentLayout::updateBlock); + QObject::connect(documentLayout, &TextDocumentLayout::updateExtraArea, + this, &TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar); + + QObject::connect(documentLayout, &QAbstractTextDocumentLayout::documentSizeChanged, + this, &TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar); + + QObject::connect(documentLayout, &QAbstractTextDocumentLayout::update, + this, &TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar); + QObject::connect(doc, &QTextDocument::contentsChange, this, &TextEditorWidgetPrivate::editorContentsChange); @@ -3050,6 +3117,8 @@ void TextEditorWidget::resizeEvent(QResizeEvent *e) d->m_extraArea->setGeometry( QStyle::visualRect(layoutDirection(), cr, QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height()))); + d->adjustScrollBarRanges(); + d->updateCurrentLineInScrollbar(); } QRect TextEditorWidgetPrivate::foldBox() @@ -4584,6 +4653,7 @@ void TextEditorWidgetPrivate::updateCurrentLineHighlight() sel.cursor.clearSelection(); extraSelections.append(sel); } + updateCurrentLineInScrollbar(); q->setExtraSelections(TextEditorWidget::CurrentLineSelection, extraSelections); @@ -4649,6 +4719,20 @@ void TextEditorWidgetPrivate::updateHighlights() } } +void TextEditorWidgetPrivate::updateCurrentLineInScrollbar() +{ + if (m_highlightCurrentLine && m_highlightScrollBar) { + m_highlightScrollBar->removeHighlights(Constants::SCROLL_BAR_CURRENT_LINE); + if (m_highlightScrollBar->maximum() > 0) { + const QTextCursor &tc = q->textCursor(); + const int lineNumberInBlock = + tc.block().layout()->lineForTextPosition(tc.positionInBlock()).lineNumber(); + m_highlightScrollBar->addHighlight(Constants::SCROLL_BAR_CURRENT_LINE, + q->textCursor().block().firstLineNumber() + lineNumberInBlock); + } + } +} + void TextEditorWidgetPrivate::slotUpdateBlockNotify(const QTextBlock &block) { static bool blockRecursion = false; @@ -5466,6 +5550,158 @@ void TextEditorWidgetPrivate::highlightSearchResultsSlot(const QString &txt, Fin m_findFlags = findFlags; m_delayedUpdateTimer.start(50); + + if (m_highlightScrollBar) + m_scrollBarUpdateTimer.start(50); +} + +void TextEditorWidgetPrivate::searchResultsReady(int beginIndex, int endIndex) +{ + QVector results; + for (int index = beginIndex; index < endIndex; ++index) { + foreach (Utils::FileSearchResult result, m_searchWatcher->resultAt(index)) { + const QTextBlock &block = q->document()->findBlockByNumber(result.lineNumber - 1); + const int matchStart = block.position() + result.matchStart; + if (!q->inFindScope(matchStart, matchStart + result.matchLength)) + continue; + results << SearchResult{matchStart, result.matchLength}; + } + } + m_searchResults << results; + addSearchResultsToScrollBar(results); +} + +void TextEditorWidgetPrivate::searchFinished() +{ + delete m_searchWatcher; + m_searchWatcher = 0; +} + +void TextEditorWidgetPrivate::adjustScrollBarRanges() +{ + if (!m_highlightScrollBar) + return; + const float lineSpacing = QFontMetricsF(q->font()).lineSpacing(); + if (lineSpacing == 0) + return; + + const float offset = q->contentOffset().y(); + m_highlightScrollBar->setVisibleRange((q->viewport()->rect().height() - offset) / lineSpacing); + m_highlightScrollBar->setRangeOffset(offset / lineSpacing); +} + +void TextEditorWidgetPrivate::highlightSearchResultsInScrollBar() +{ + if (!m_highlightScrollBar) + return; + m_highlightScrollBar->removeHighlights(Constants::SCROLL_BAR_SEARCH_RESULT); + m_searchResults.clear(); + + if (m_searchWatcher) { + m_searchWatcher->disconnect(); + m_searchWatcher->cancel(); + m_searchWatcher->deleteLater(); + m_searchWatcher = 0; + } + + const QString &txt = m_searchExpr.pattern(); + if (txt.isEmpty()) + return; + + adjustScrollBarRanges(); + + m_searchWatcher = new QFutureWatcher(); + connect(m_searchWatcher, &QFutureWatcher::resultsReadyAt, + this, &TextEditorWidgetPrivate::searchResultsReady); + connect(m_searchWatcher, &QFutureWatcher::finished, + this, &TextEditorWidgetPrivate::searchFinished); + m_searchWatcher->setPendingResultsLimit(10); + + const QTextDocument::FindFlags findFlags = textDocumentFlagsForFindFlags(m_findFlags); + + const QString &fileName = m_document->filePath().toString(); + FileListIterator *it = + new FileListIterator( { fileName } , { const_cast(m_document->codec()) } ); + QMap fileToContentsMap; + fileToContentsMap[fileName] = m_document->plainText(); + + if (m_findFlags & FindRegularExpression) + m_searchWatcher->setFuture(findInFilesRegExp(txt, it, findFlags, fileToContentsMap)); + else + m_searchWatcher->setFuture(findInFiles(txt, it, findFlags, fileToContentsMap)); +} + +void TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar() +{ + if (m_scrollBarUpdateScheduled) + return; + + m_scrollBarUpdateScheduled = true; + QTimer::singleShot(0, this, &TextEditorWidgetPrivate::updateHighlightScrollBarNow); +} + +HighlightScrollBar::Priority textMarkPrioToScrollBarPrio(const TextMark::Priority &prio) +{ + switch (prio) { + case TextMark::LowPriority: + return HighlightScrollBar::LowPriority; + case TextMark::NormalPriority: + return HighlightScrollBar::NormalPriority; + case TextMark::HighPriority: + return HighlightScrollBar::HighPriority; + default: + return HighlightScrollBar::NormalPriority; + } +} + +void TextEditorWidgetPrivate::addSearchResultsToScrollBar(QVector results) +{ + QSet searchResults; + foreach (SearchResult result, results) { + const QTextBlock &block = q->document()->findBlock(result.start); + if (block.isValid() && block.isVisible()) { + const int firstLine = block.layout()->lineForTextPosition(result.start - block.position()).lineNumber(); + const int lastLine = block.layout()->lineForTextPosition(result.start - block.position() + result.length).lineNumber(); + for (int line = firstLine; line <= lastLine; ++line) + searchResults << block.firstLineNumber() + line; + } + } + if (m_highlightScrollBar) + m_highlightScrollBar->addHighlights(Constants::SCROLL_BAR_SEARCH_RESULT, searchResults); +} + +void TextEditorWidgetPrivate::updateHighlightScrollBarNow() +{ + typedef QSet IntSet; + + m_scrollBarUpdateScheduled = false; + if (!m_highlightScrollBar) + return; + + m_highlightScrollBar->removeAllHighlights(); + + updateCurrentLineInScrollbar(); + + // update search results + addSearchResultsToScrollBar(m_searchResults); + + // update text marks + QHash marks; + foreach (TextMark *mark, m_document->marks()) { + Id category = mark->category(); + if (!mark->isVisible() || !TextMark::categoryHasColor(category)) + continue; + m_highlightScrollBar->setPriority(category, textMarkPrioToScrollBarPrio(mark->priority())); + const QTextBlock &block = q->document()->findBlockByNumber(mark->lineNumber() - 1); + if (block.isVisible()) + marks[category] << block.firstLineNumber(); + } + QHashIterator it(marks); + while (it.hasNext()) { + it.next(); + m_highlightScrollBar->setColor(it.key(), TextMark::categoryColor(it.key())); + m_highlightScrollBar->addHighlights(it.key(), it.value()); + } } int TextEditorWidget::verticalBlockSelectionFirstColumn() const @@ -5510,6 +5746,7 @@ void TextEditorWidgetPrivate::setFindScope(const QTextCursor &start, const QText m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn; m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn; q->viewport()->update(); + highlightSearchResultsInScrollBar(); } } @@ -6204,6 +6441,7 @@ void TextEditorWidget::setDisplaySettings(const DisplaySettings &ds) d->updateCodeFoldingVisible(); d->updateHighlights(); + d->setupScrollBar(); viewport()->update(); extraArea()->update(); } diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 4433ad04b03..17ea0d56849 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -178,6 +178,9 @@ const char FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbol const char JUMP_TO_FILE_UNDER_CURSOR[] = "TextEditor.JumpToFileUnderCursor"; const char JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.JumpToFileUnderCursorInNextSplit"; +const char SCROLL_BAR_SEARCH_RESULT[] = "TextEditor.ScrollBarSearchResult"; +const char SCROLL_BAR_CURRENT_LINE[] = "TextEditor.ScrollBarCurrentLine"; + const char *nameForStyle(TextStyle style); TextStyle styleFromName(const char *name);