Source code for usdmanager.linenumbers

#
# Copyright 2018 DreamWorks Animation L.L.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Line numbers widget for optimized display of line numbers on the left side of
a text widget.
"""
from __future__ import division

from Qt import QtCore
from Qt.QtCore import QRect, QSize, Slot
from Qt.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextFormat
from Qt.QtWidgets import QTextEdit, QWidget

# Shadow round for Python 3 compatibility
from .utils import round as round


[docs]class PlainTextLineNumbers(QWidget): """ Line number widget for `QPlainTextEdit` widgets. """ def __init__(self, parent): """ Initialize the line numbers widget. :Parameters: parent : `QPlainTextEdit` Text widget """ super(PlainTextLineNumbers, self).__init__(parent) self.textWidget = parent self._hiddenByUser = False self._highlightCurrentLine = True # Monospaced font to keep width from shifting. font = QFont() font.setStyleHint(QFont.Courier) font.setFamily("Monospace") self.setFont(font) self.connectSignals() self.updateLineWidth() self.highlightCurrentLine()
[docs] def blockCount(self): return self.textWidget.blockCount()
[docs] def connectSignals(self): """ Connect signals from the text widget that affect line numbers. """ self.textWidget.blockCountChanged.connect(self.updateLineWidth) self.textWidget.updateRequest.connect(self.updateLineNumbers) self.textWidget.cursorPositionChanged.connect(self.highlightCurrentLine)
[docs] @Slot() def highlightCurrentLine(self): """ Highlight the line the cursor is on. :Returns: If highlighting was enabled or not. :Rtype: `bool` """ if not self._highlightCurrentLine: return False extras = [x for x in self.textWidget.extraSelections() if x.format.property(QTextFormat.UserProperty) != "line"] selection = QTextEdit.ExtraSelection() lineColor = QColor(QtCore.Qt.darkGray).darker() if self.window().isDarkTheme() else \ QColor(QtCore.Qt.yellow).lighter(180) selection.format.setBackground(lineColor) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.format.setProperty(QTextFormat.UserProperty, "line") selection.cursor = self.textWidget.textCursor() selection.cursor.clearSelection() # Put at the beginning of the list so we preserve any highlighting from Find's highlight all functionality. extras.insert(0, selection) ''' if self.window().buttonHighlightAll.isChecked() and self.window().findBar.text(): selection = QTextEdit.ExtraSelection() lineColor = QColor(QtCore.Qt.yellow) selection.format.setBackground(lineColor) selection.cursor = QtGui.QTextCursor(self.textWidget.document()) selection.find(self.window().findBar.text()) ''' self.textWidget.setExtraSelections(extras) return True
[docs] def lineWidth(self, count=0): """ Calculate the width of the widget based on the block count. :Parameters: count : `int` Block count. Defaults to current block count. """ if self._hiddenByUser: return 0 blocks = str(count or self.blockCount()) try: # horizontalAdvance added in Qt 5.11. return 6 + self.fontMetrics().horizontalAdvance(blocks) except AttributeError: # Obsolete in Qt 5. return 6 + self.fontMetrics().width(blocks)
[docs] def onEditorResize(self): """ Adjust line numbers size if the text widget is resized. """ cr = self.textWidget.contentsRect() self.setGeometry(QRect(cr.left(), cr.top(), self.lineWidth(), cr.height()))
[docs] def paintEvent(self, event): """ Draw the visible line numbers. """ painter = QPainter(self) painter.fillRect(event.rect(), QColor(QtCore.Qt.darkGray).darker(300) if self.window().isDarkTheme() \ else QtCore.Qt.lightGray) textWidget = self.textWidget currBlock = textWidget.document().findBlock(textWidget.textCursor().position()) block = textWidget.firstVisibleBlock() blockNumber = block.blockNumber() + 1 geo = textWidget.blockBoundingGeometry(block).translated(textWidget.contentOffset()) top = round(geo.top()) bottom = round(geo.bottom()) width = self.width() - 3 # 3 is magic padding number height = round(geo.height()) flags = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter font = painter.font() # Shrink the line numbers if we zoom out so numbers don't overlap, but don't increase the size, since we don't # (yet) account for that in this widget's width, leading to larger numbers cutting off the leading digits. size = max(1, min(width, height - 3)) if size < font.pointSize(): font.setPointSize(size) while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): # Make the line number for the selected line bold. font.setBold(block == currBlock) painter.setFont(font) painter.drawText(0, top, width, height, flags, str(blockNumber)) block = block.next() top = bottom bottom = top + round(textWidget.blockBoundingRect(block).height()) blockNumber += 1
[docs] def setVisible(self, visible): super(PlainTextLineNumbers, self).setVisible(visible) self._hiddenByUser = not visible self.updateLineWidth()
[docs] def sizeHint(self): return QSize(self.lineWidth(), 0)
[docs] @Slot(QRect, int) def updateLineNumbers(self, rect, dY): """ Scroll the line numbers or repaint the visible numbers. """ if dY: self.scroll(0, dY) else: self.update(0, rect.y(), self.width(), rect.height()) if rect.contains(self.textWidget.viewport().rect()): self.updateLineWidth()
[docs] @Slot(int) def updateLineWidth(self, count=0): """ Adjust display of text widget to account for the widget of the line numbers. :Parameters: count : `int` Block count of document. """ self.textWidget.setViewportMargins(self.lineWidth(count), 0, 0, 0)
[docs]class LineNumbers(PlainTextLineNumbers): """ Line number widget for `QTextBrowser` and `QTextEdit` widgets. Currently does not support `QPlainTextEdit` widgets. """
[docs] def blockCount(self): return self.textWidget.document().blockCount()
[docs] def connectSignals(self): """ Connect relevant `QTextBrowser` or `QTextEdit` signals. """ self.doc = self.textWidget.document() self.textWidget.verticalScrollBar().valueChanged.connect(self.update) self.textWidget.currentCharFormatChanged.connect(self.resizeAndUpdate) self.textWidget.cursorPositionChanged.connect(self.highlightCurrentLine) self.doc.blockCountChanged.connect(self.updateLineWidth)
[docs] @Slot() def highlightCurrentLine(self): """ Make sure the active line number is redrawn in bold by calling update. """ if super(LineNumbers, self).highlightCurrentLine(): self.update()
[docs] @Slot(QTextCharFormat) def resizeAndUpdate(self, *args): """ Resize bar if needed. """ self.updateLineWidth() super(LineNumbers, self).update()
[docs] def paintEvent(self, event): """ Draw line numbers. """ painter = QPainter(self) painter.fillRect(event.rect(), QColor(QtCore.Qt.darkGray).darker(300) if self.window().isDarkTheme() \ else QtCore.Qt.lightGray) textWidget = self.textWidget doc = textWidget.document() vScrollPos = textWidget.verticalScrollBar().value() pageBtm = vScrollPos + textWidget.viewport().height() currBlock = doc.findBlock(textWidget.textCursor().position()) width = self.width() - 3 # 3 is magic padding number height = textWidget.fontMetrics().height() flags = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter font = painter.font() # Shrink the line numbers if we zoom out so numbers don't overlap, but don't increase the size, since we don't # (yet) account for that in this widget's width, leading to larger numbers cutting off the leading digits. size = max(1, min(width, height - 3)) if size < font.pointSize(): font.setPointSize(size) # Find roughly the current top-most visible block. block = doc.begin() lineHeight = doc.documentLayout().blockBoundingRect(block).height() block = doc.findBlockByNumber(int(vScrollPos / lineHeight)) currLine = block.blockNumber() while block.isValid(): currLine += 1 # Check if the position of the block is outside the visible area. yPos = doc.documentLayout().blockBoundingRect(block).topLeft().y() if yPos > pageBtm: break # Make the line number for the selected line bold. font.setBold(block == currBlock) painter.setFont(font) painter.drawText(0, yPos - vScrollPos, width, height, flags, str(currLine)) # Go to the next block. block = block.next()