2 # pylint: disable=unexpected-keyword-arg
3 from __future__
import division
, absolute_import
, unicode_literals
6 from qtpy
import QtCore
8 from qtpy
import QtWidgets
9 from qtpy
.QtCore
import Qt
10 from qtpy
.QtCore
import Signal
12 from ..models
import prefs
13 from ..qtutils
import get
14 from .. import hotkeys
15 from .. import qtutils
20 def get_stripped(widget
):
21 return widget
.get().strip()
24 class LineEdit(QtWidgets
.QLineEdit
):
26 cursor_changed
= Signal(int, int)
28 def __init__(self
, parent
=None, row
=1, get_value
=None, clear_button
=False):
29 QtWidgets
.QLineEdit
.__init
__(self
, parent
)
32 get_value
= get_stripped
33 self
._get
_value
= get_value
34 self
.cursor_position
= LineEditCursorPosition(self
, row
)
36 if clear_button
and hasattr(self
, 'setClearButtonEnabled'):
37 self
.setClearButtonEnabled(True)
40 """Return the raw unicode value from Qt"""
44 """Return the processed value, e.g. stripped"""
45 return self
._get
_value
(self
)
47 def set_value(self
, value
, block
=False):
49 blocksig
= self
.blockSignals(True)
50 pos
= self
.cursorPosition()
52 self
.setCursorPosition(pos
)
54 self
.blockSignals(blocksig
)
57 class LineEditCursorPosition(object):
58 """Translate cursorPositionChanged(int,int) into cursorPosition(int,int)
60 def __init__(self
, widget
, row
):
63 # Translate cursorPositionChanged into cursor_changed(int, int)
64 widget
.cursorPositionChanged
.connect(lambda old
, new
: self
.emit())
69 col
= widget
.cursorPosition()
70 widget
.cursor_changed
.emit(row
, col
)
73 self
._widget
.setCursorPosition(0)
76 class BaseTextEditExtension(QtCore
.QObject
):
78 def __init__(self
, widget
, get_value
, readonly
):
79 QtCore
.QObject
.__init
__(self
, widget
)
81 self
.cursor_position
= TextEditCursorPosition(widget
, self
)
83 get_value
= get_stripped
84 self
._get
_value
= get_value
86 self
._readonly
= readonly
90 def _init_flags(self
):
92 widget
.setMinimumSize(QtCore
.QSize(1, 1))
93 widget
.setWordWrapMode(QtGui
.QTextOption
.WordWrap
)
94 widget
.setLineWrapMode(widget
.NoWrap
)
96 widget
.setReadOnly(True)
97 widget
.setAcceptDrops(False)
98 widget
.setTabChangesFocus(True)
99 widget
.setUndoRedoEnabled(False)
100 widget
.setTextInteractionFlags(Qt
.TextSelectableByKeyboard |
101 Qt
.TextSelectableByMouse
)
104 """Return the raw unicode value from Qt"""
105 return self
.widget
.toPlainText()
108 """Return a safe value, e.g. a stripped value"""
109 return self
._get
_value
(self
.widget
)
111 def set_value(self
, value
, block
=False):
113 blocksig
= self
.widget
.blockSignals(True)
115 # Save cursor position
116 offset
, selection_text
= self
.offset_and_selection()
117 old_value
= get(self
.widget
)
120 self
.widget
.setPlainText(value
)
123 if selection_text
and selection_text
in value
:
124 # If the old selection exists in the new text then re-select it.
125 idx
= value
.index(selection_text
)
126 cursor
= self
.widget
.textCursor()
127 cursor
.setPosition(idx
)
128 cursor
.setPosition(idx
+ len(selection_text
),
129 QtGui
.QTextCursor
.KeepAnchor
)
130 self
.widget
.setTextCursor(cursor
)
132 elif value
== old_value
:
133 # Otherwise, if the text is identical and there is no selection
134 # then restore the cursor position.
135 cursor
= self
.widget
.textCursor()
136 cursor
.setPosition(offset
)
137 self
.widget
.setTextCursor(cursor
)
139 # If none of the above applied then restore the cursor position.
140 position
= max(0, min(offset
, len(value
) - 1))
141 cursor
= self
.widget
.textCursor()
142 cursor
.setPosition(position
)
143 self
.widget
.setTextCursor(cursor
)
144 cursor
= self
.widget
.textCursor()
145 cursor
.movePosition(QtGui
.QTextCursor
.StartOfLine
)
146 self
.widget
.setTextCursor(cursor
)
149 self
.widget
.blockSignals(blocksig
)
151 def set_cursor_position(self
, new_position
):
152 cursor
= self
.widget
.textCursor()
153 cursor
.setPosition(new_position
)
154 self
.widget
.setTextCursor(cursor
)
157 return self
._tabwidth
159 def set_tabwidth(self
, width
):
160 self
._tabwidth
= width
161 font
= self
.widget
.font()
162 fm
= QtGui
.QFontMetrics(font
)
163 pixels
= fm
.width('M' * width
)
164 self
.widget
.setTabStopWidth(pixels
)
166 def selected_line(self
):
167 contents
= self
.value()
168 cursor
= self
.widget
.textCursor()
169 offset
= min(cursor
.position(), len(contents
)-1)
170 while (offset
>= 1 and
171 contents
[offset
-1] and
172 contents
[offset
-1] != '\n'):
174 data
= contents
[offset
:]
176 line
, _
= data
.split('\n', 1)
182 return self
.widget
.textCursor()
184 def has_selection(self
):
185 return self
.cursor().hasSelection()
187 def offset_and_selection(self
):
188 cursor
= self
.cursor()
189 offset
= cursor
.selectionStart()
190 selection_text
= cursor
.selection().toPlainText()
191 return offset
, selection_text
193 def mouse_press_event(self
, event
):
194 # Move the text cursor so that the right-click events operate
195 # on the current position, not the last left-clicked position.
197 if event
.button() == Qt
.RightButton
:
198 if not widget
.textCursor().hasSelection():
199 cursor
= widget
.cursorForPosition(event
.pos())
200 widget
.setTextCursor(cursor
)
202 # For extension by sub-classes
205 """Called during init for class-specific settings"""
208 def set_textwidth(self
, width
):
209 """Set the text width"""
212 def set_linebreak(self
, brk
):
213 """Enable word wrapping"""
217 class PlainTextEditExtension(BaseTextEditExtension
):
219 def set_linebreak(self
, brk
):
221 wrapmode
= QtWidgets
.QPlainTextEdit
.WidgetWidth
223 wrapmode
= QtWidgets
.QPlainTextEdit
.NoWrap
224 self
.widget
.setLineWrapMode(wrapmode
)
227 class PlainTextEdit(QtWidgets
.QPlainTextEdit
):
229 cursor_changed
= Signal(int, int)
232 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
233 QtWidgets
.QPlainTextEdit
.__init
__(self
, parent
)
234 self
.ext
= PlainTextEditExtension(self
, get_value
, readonly
)
235 self
.cursor_position
= self
.ext
.cursor_position
238 """Return the raw unicode value from Qt"""
239 return self
.ext
.get()
241 # For compatibility with QTextEdit
242 def setText(self
, value
):
243 self
.set_value(value
)
246 """Return a safe value, e.g. a stripped value"""
247 return self
.ext
.value()
249 def set_value(self
, value
, block
=False):
250 self
.ext
.set_value(value
, block
=block
)
252 def has_selection(self
):
253 return self
.ext
.has_selection()
255 def selected_line(self
):
256 return self
.ext
.selected_line()
258 def set_tabwidth(self
, width
):
259 self
.ext
.set_tabwidth(width
)
261 def set_textwidth(self
, width
):
262 self
.ext
.set_textwidth(width
)
264 def set_linebreak(self
, brk
):
265 self
.ext
.set_linebreak(brk
)
267 def mousePressEvent(self
, event
):
268 self
.ext
.mouse_press_event(event
)
269 super(PlainTextEdit
, self
).mousePressEvent(event
)
271 def wheelEvent(self
, event
):
272 """Disable control+wheelscroll text resizing"""
273 if event
.modifiers() & Qt
.ControlModifier
:
276 super(PlainTextEdit
, self
).wheelEvent(event
)
279 class TextEditExtension(BaseTextEditExtension
):
283 widget
.setAcceptRichText(False)
285 def set_linebreak(self
, brk
):
287 wrapmode
= QtWidgets
.QTextEdit
.FixedColumnWidth
289 wrapmode
= QtWidgets
.QTextEdit
.NoWrap
290 self
.widget
.setLineWrapMode(wrapmode
)
292 def set_textwidth(self
, width
):
293 self
.widget
.setLineWrapColumnOrWidth(width
)
296 class TextEdit(QtWidgets
.QTextEdit
):
298 cursor_changed
= Signal(int, int)
301 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
302 QtWidgets
.QTextEdit
.__init
__(self
, parent
)
303 self
.ext
= TextEditExtension(self
, get_value
, readonly
)
304 self
.cursor_position
= self
.ext
.cursor_position
305 self
.expandtab_enabled
= False
308 """Return the raw unicode value from Qt"""
309 return self
.ext
.get()
312 """Return a safe value, e.g. a stripped value"""
313 return self
.ext
.value()
315 def set_value(self
, value
, block
=False):
316 self
.ext
.set_value(value
, block
=block
)
318 def selected_line(self
):
319 return self
.ext
.selected_line()
321 def set_tabwidth(self
, width
):
322 self
.ext
.set_tabwidth(width
)
324 def set_textwidth(self
, width
):
325 self
.ext
.set_textwidth(width
)
327 def set_linebreak(self
, brk
):
328 self
.ext
.set_linebreak(brk
)
330 def set_expandtab(self
, value
):
331 self
.expandtab_enabled
= value
333 def mousePressEvent(self
, event
):
334 self
.ext
.mouse_press_event(event
)
335 super(TextEdit
, self
).mousePressEvent(event
)
337 def wheelEvent(self
, event
):
338 """Disable control+wheelscroll text resizing"""
339 if event
.modifiers() & Qt
.ControlModifier
:
342 super(TextEdit
, self
).wheelEvent(event
)
344 def should_expandtab(self
, event
):
345 return event
.key() == Qt
.Key_Tab
and self
.expandtab_enabled
348 tabwidth
= max(self
.ext
.tabwidth(), 1)
349 cursor
= self
.textCursor()
350 cursor
.insertText(' ' * tabwidth
)
352 def keyPressEvent(self
, event
):
353 expandtab
= self
.should_expandtab(event
)
358 QtWidgets
.QTextEdit
.keyPressEvent(self
, event
)
360 def keyReleaseEvent(self
, event
):
361 expandtab
= self
.should_expandtab(event
)
365 QtWidgets
.QTextEdit
.keyReleaseEvent(self
, event
)
368 class TextEditCursorPosition(object):
370 def __init__(self
, widget
, ext
):
371 self
._widget
= widget
373 widget
.cursorPositionChanged
.connect(self
.emit
)
376 widget
= self
._widget
378 cursor
= widget
.textCursor()
379 position
= cursor
.position()
381 before
= txt
[:position
]
382 row
= before
.count('\n')
383 line
= before
.split('\n')[row
]
384 col
= cursor
.columnNumber()
385 col
+= line
[:col
].count('\t') * (ext
.tabwidth() - 1)
386 widget
.cursor_changed
.emit(row
+1, col
)
389 widget
= self
._widget
390 cursor
= widget
.textCursor()
391 cursor
.setPosition(0)
392 widget
.setTextCursor(cursor
)
395 class MonoTextEdit(PlainTextEdit
):
397 def __init__(self
, context
, parent
=None, readonly
=False):
398 PlainTextEdit
.__init
__(self
, parent
=parent
, readonly
=readonly
)
399 self
.setFont(qtutils
.diff_font(context
))
402 def get_value_hinted(widget
):
403 text
= get_stripped(widget
)
404 hint
= get(widget
.hint
)
410 class HintWidget(QtCore
.QObject
):
411 """Extend a widget to provide hint messages
413 This primarily exists because setPlaceholderText() is only available
414 in Qt5, so this class provides consistent behavior across versions.
418 def __init__(self
, widget
, hint
):
419 QtCore
.QObject
.__init
__(self
, widget
)
420 self
._widget
= widget
422 self
._is
_error
= False
424 self
.modern
= modern
= hasattr(widget
, 'setPlaceholderText')
426 widget
.setPlaceholderText(hint
)
428 # Palette for normal text
429 QPalette
= QtGui
.QPalette
430 palette
= widget
.palette()
432 hint_color
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
433 error_bg_color
= QtGui
.QColor(Qt
.red
).darker()
434 error_fg_color
= QtGui
.QColor(Qt
.white
)
436 hint_rgb
= qtutils
.rgb_css(hint_color
)
437 error_bg_rgb
= qtutils
.rgb_css(error_bg_color
)
438 error_fg_rgb
= qtutils
.rgb_css(error_fg_color
)
440 env
= dict(name
=widget
.__class
__.__name
__,
441 error_fg_rgb
=error_fg_rgb
,
442 error_bg_rgb
=error_bg_rgb
,
445 self
._default
_style
= ''
447 self
._hint
_style
= """
453 self
._error
_style
= """
455 color: %(error_fg_rgb)s;
456 background-color: %(error_bg_rgb)s;
461 """Defered initialization"""
463 self
.widget().setPlaceholderText(self
.value())
465 self
.widget().installEventFilter(self
)
469 """Return the parent text widget"""
473 """Return True when hint-mode is active"""
474 return self
.value() == get_stripped(self
._widget
)
477 """Return the current hint text"""
480 def set_error(self
, is_error
):
481 """Enable/disable error mode"""
482 self
._is
_error
= is_error
485 def set_value(self
, hint
):
486 """Change the hint text"""
489 self
._widget
.setPlaceholderText(hint
)
491 # If hint-mode is currently active, re-activate it
492 active
= self
.active()
494 if active
or self
.active():
497 def enable(self
, enable
):
498 """Enable/disable hint-mode"""
500 if enable
and self
._hint
:
501 self
._widget
.set_value(self
._hint
, block
=True)
502 self
._widget
.cursor_position
.reset()
505 self
._update
_palette
(enable
)
508 """Update the palette to match the current mode"""
509 self
._update
_palette
(self
.active())
511 def _update_palette(self
, hint
):
512 """Update to palette for normal/error/hint mode"""
514 style
= self
._error
_style
515 elif not self
.modern
and hint
:
516 style
= self
._hint
_style
518 style
= self
._default
_style
519 self
._widget
.setStyleSheet(style
)
521 def eventFilter(self
, _obj
, event
):
522 """Enable/disable hint-mode when focus changes"""
524 if etype
== QtCore
.QEvent
.FocusIn
:
526 elif etype
== QtCore
.QEvent
.FocusOut
:
531 """Disable hint-mode when focused"""
532 widget
= self
.widget()
535 widget
.cursor_position
.emit()
538 """Re-enable hint-mode when losing focus"""
539 widget
= self
.widget()
544 class HintedPlainTextEdit(PlainTextEdit
):
545 """A hinted plain text edit"""
547 def __init__(self
, context
, hint
, parent
=None, readonly
=False):
548 PlainTextEdit
.__init
__(self
, parent
=parent
,
549 get_value
=get_value_hinted
,
551 self
.hint
= HintWidget(self
, hint
)
553 self
.setFont(qtutils
.diff_font(context
))
554 self
.set_tabwidth(prefs
.tabwidth(context
))
555 # Refresh palettes when text changes
556 self
.textChanged
.connect(self
.hint
.refresh
)
558 def set_value(self
, value
, block
=False):
559 """Set the widget text or enable hint mode when empty"""
560 if value
or self
.hint
.modern
:
561 PlainTextEdit
.set_value(self
, value
, block
=block
)
563 self
.hint
.enable(True)
566 class HintedTextEdit(TextEdit
):
567 """A hinted text edit"""
569 def __init__(self
, context
, hint
, parent
=None, readonly
=False):
570 TextEdit
.__init
__(self
, parent
=parent
,
571 get_value
=get_value_hinted
, readonly
=readonly
)
572 self
.hint
= HintWidget(self
, hint
)
574 # Refresh palettes when text changes
575 self
.textChanged
.connect(self
.hint
.refresh
)
576 self
.setFont(qtutils
.diff_font(context
))
578 def set_value(self
, value
, block
=False):
579 """Set the widget text or enable hint mode when empty"""
580 if value
or self
.hint
.modern
:
581 TextEdit
.set_value(self
, value
, block
=block
)
583 self
.hint
.enable(True)
586 # The vim-like read-only text view
588 class VimMixin(object):
590 def __init__(self
, widget
):
592 self
.Base
= widget
.Base
593 # Common vim/unix-ish keyboard actions
594 self
.add_navigation('Up', hotkeys
.MOVE_UP
,
595 shift
=hotkeys
.MOVE_UP_SHIFT
)
596 self
.add_navigation('Down', hotkeys
.MOVE_DOWN
,
597 shift
=hotkeys
.MOVE_DOWN_SHIFT
)
598 self
.add_navigation('Left', hotkeys
.MOVE_LEFT
,
599 shift
=hotkeys
.MOVE_LEFT_SHIFT
)
600 self
.add_navigation('Right', hotkeys
.MOVE_RIGHT
,
601 shift
=hotkeys
.MOVE_RIGHT_SHIFT
)
602 self
.add_navigation('WordLeft', hotkeys
.WORD_LEFT
)
603 self
.add_navigation('WordRight', hotkeys
.WORD_RIGHT
)
604 self
.add_navigation('StartOfLine', hotkeys
.START_OF_LINE
)
605 self
.add_navigation('EndOfLine', hotkeys
.END_OF_LINE
)
607 qtutils
.add_action(widget
, 'PageUp',
608 lambda: widget
.page(-widget
.height()//2),
609 hotkeys
.SECONDARY_ACTION
)
611 qtutils
.add_action(widget
, 'PageDown',
612 lambda: widget
.page(widget
.height()//2),
613 hotkeys
.PRIMARY_ACTION
)
615 def add_navigation(self
, name
, hotkey
, shift
=None):
616 """Add a hotkey along with a shift-variant"""
618 direction
= getattr(QtGui
.QTextCursor
, name
)
619 qtutils
.add_action(widget
, name
,
620 lambda: self
.move(direction
), hotkey
)
622 qtutils
.add_action(widget
, 'Shift' + name
,
623 lambda: self
.move(direction
, True), shift
)
625 def move(self
, direction
, select
=False, n
=1):
627 cursor
= widget
.textCursor()
629 mode
= QtGui
.QTextCursor
.KeepAnchor
631 mode
= QtGui
.QTextCursor
.MoveAnchor
632 if cursor
.movePosition(direction
, mode
, n
):
633 self
.set_text_cursor(cursor
)
635 def page(self
, offset
):
637 rect
= widget
.cursorRect()
639 y
= rect
.y() + offset
640 new_cursor
= widget
.cursorForPosition(QtCore
.QPoint(x
, y
))
641 if new_cursor
is not None:
642 self
.set_text_cursor(new_cursor
)
644 def set_text_cursor(self
, cursor
):
646 widget
.setTextCursor(cursor
)
647 widget
.ensureCursorVisible()
648 widget
.viewport().update()
650 def keyPressEvent(self
, event
):
651 """Custom keyboard behaviors
653 The leave() signal is emitted when `Up` is pressed and we're already
654 at the beginning of the text. This allows the parent widget to
655 orchestrate some higher-level interaction, such as giving focus to
658 When in the middle of the first line and `Up` is pressed, the cursor
659 is moved to the beginning of the line.
663 if event
.key() == Qt
.Key_Up
:
664 cursor
= widget
.textCursor()
665 position
= cursor
.position()
667 # The cursor is at the beginning of the line.
668 # Emit a signal so that the parent can e.g. change focus.
670 elif get(widget
)[:position
].count('\n') == 0:
671 # The cursor is in the middle of the first line of text.
672 # We can't go up ~ jump to the beginning of the line.
673 # Select the text if shift is pressed.
674 if event
.modifiers() & Qt
.ShiftModifier
:
675 mode
= QtGui
.QTextCursor
.KeepAnchor
677 mode
= QtGui
.QTextCursor
.MoveAnchor
678 cursor
.movePosition(QtGui
.QTextCursor
.StartOfLine
, mode
)
679 widget
.setTextCursor(cursor
)
681 return self
.Base
.keyPressEvent(widget
, event
)
684 class VimHintedPlainTextEdit(HintedPlainTextEdit
):
685 """HintedPlainTextEdit with vim hotkeys
687 This can only be used in read-only mode.
690 Base
= HintedPlainTextEdit
693 def __init__(self
, context
, hint
, parent
=None):
694 HintedPlainTextEdit
.__init
__(
695 self
, context
, hint
, parent
=parent
, readonly
=True)
696 self
._mixin
= self
.Mixin(self
)
698 def move(self
, direction
, select
=False, n
=1):
699 return self
._mixin
.page(direction
, select
=select
, n
=n
)
701 def page(self
, offset
):
702 return self
._mixin
.page(offset
)
704 def keyPressEvent(self
, event
):
705 return self
._mixin
.keyPressEvent(event
)
708 class VimTextEdit(MonoTextEdit
):
709 """Text viewer with vim-like hotkeys
711 This can only be used in read-only mode.
717 def __init__(self
, context
, parent
=None, readonly
=True):
718 MonoTextEdit
.__init
__(self
, context
, parent
=None, readonly
=readonly
)
719 self
._mixin
= self
.Mixin(self
)
721 def move(self
, direction
, select
=False, n
=1):
722 return self
._mixin
.page(direction
, select
=select
, n
=n
)
724 def page(self
, offset
):
725 return self
._mixin
.page(offset
)
727 def keyPressEvent(self
, event
):
728 return self
._mixin
.keyPressEvent(event
)
731 class HintedLineEdit(LineEdit
):
733 def __init__(self
, context
, hint
, parent
=None):
734 LineEdit
.__init
__(self
, parent
=parent
, get_value
=get_value_hinted
)
735 self
.hint
= HintWidget(self
, hint
)
737 self
.setFont(qtutils
.diff_font(context
))
738 self
.textChanged
.connect(lambda text
: self
.hint
.refresh())
741 def text_dialog(context
, text
, title
):
742 """Show a wall of text in a dialog"""
743 parent
= qtutils
.active_window()
745 label
= QtWidgets
.QLabel(parent
)
746 label
.setFont(qtutils
.diff_font(context
))
748 label
.setMargin(defs
.large_margin
)
749 text_flags
= Qt
.TextSelectableByKeyboard | Qt
.TextSelectableByMouse
750 label
.setTextInteractionFlags(text_flags
)
752 widget
= QtWidgets
.QDialog(parent
)
753 widget
.setWindowModality(Qt
.WindowModal
)
754 widget
.setWindowTitle(title
)
756 scroll
= QtWidgets
.QScrollArea()
757 scroll
.setWidget(label
)
759 layout
= qtutils
.hbox(defs
.margin
, defs
.spacing
, scroll
)
760 widget
.setLayout(layout
)
762 qtutils
.add_action(widget
, N_('Close'), widget
.accept
,
763 Qt
.Key_Question
, Qt
.Key_Enter
, Qt
.Key_Return
)
768 class VimTextBrowser(VimTextEdit
):
769 """Text viewer with line number annotations"""
771 def __init__(self
, context
, parent
=None, readonly
=True):
772 VimTextEdit
.__init
__(self
, context
, parent
=parent
, readonly
=readonly
)
773 self
.numbers
= LineNumbers(self
)
775 def resizeEvent(self
, event
):
776 super(VimTextBrowser
, self
).resizeEvent(event
)
777 self
.numbers
.refresh_size()
780 class TextDecorator(QtWidgets
.QWidget
):
781 """Common functionality for providing line numbers in text widgets"""
783 def __init__(self
, parent
):
784 QtWidgets
.QWidget
.__init
__(self
, parent
)
787 parent
.blockCountChanged
.connect(lambda x
: self
._refresh
_viewport
())
788 parent
.cursorPositionChanged
.connect(self
.refresh
)
789 parent
.updateRequest
.connect(self
._refresh
_rect
)
792 """Refresh the numbers display"""
793 rect
= self
.editor
.viewport().rect()
794 self
._refresh
_rect
(rect
, 0)
796 def _refresh_rect(self
, rect
, dy
):
800 self
.update(0, rect
.y(), self
.width(), rect
.height())
802 if rect
.contains(self
.editor
.viewport().rect()):
803 self
._refresh
_viewport
()
805 def _refresh_viewport(self
):
806 self
.editor
.setViewportMargins(self
.width_hint(), 0, 0, 0)
808 def refresh_size(self
):
809 rect
= self
.editor
.contentsRect()
810 geom
= QtCore
.QRect(rect
.left(), rect
.top(),
811 self
.width_hint(), rect
.height())
812 self
.setGeometry(geom
)
815 return QtCore
.QSize(self
.width_hint(), 0)
818 class LineNumbers(TextDecorator
):
819 """Provide line numbers for QPlainTextEdit widgets"""
821 def __init__(self
, parent
):
822 TextDecorator
.__init
__(self
, parent
)
823 self
.highlight_line
= -1
825 def width_hint(self
):
826 document
= self
.editor
.document()
827 digits
= int(math
.log(max(1, document
.blockCount()), 10)) + 2
828 return defs
.large_margin
+ self
.fontMetrics().width('0') * digits
830 def set_highlighted(self
, line_number
):
831 """Set the line to highlight"""
832 self
.highlight_line
= line_number
834 def paintEvent(self
, event
):
835 """Paint the line number"""
836 QPalette
= QtGui
.QPalette
837 painter
= QtGui
.QPainter(self
)
839 palette
= editor
.palette()
841 painter
.fillRect(event
.rect(), palette
.color(QPalette
.Base
))
843 content_offset
= editor
.contentOffset()
844 block
= editor
.firstVisibleBlock()
846 event_rect_bottom
= event
.rect().bottom()
848 highlight
= palette
.color(QPalette
.Highlight
)
849 highlighted_text
= palette
.color(QPalette
.HighlightedText
)
850 disabled
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
852 while block
.isValid():
853 block_geom
= editor
.blockBoundingGeometry(block
)
854 block_top
= block_geom
.translated(content_offset
).top()
855 if not block
.isVisible() or block_top
>= event_rect_bottom
:
858 rect
= block_geom
.translated(content_offset
).toRect()
859 block_number
= block
.blockNumber()
860 if block_number
== self
.highlight_line
:
861 painter
.fillRect(rect
.x(), rect
.y(),
862 width
, rect
.height(), highlight
)
863 painter
.setPen(highlighted_text
)
865 painter
.setPen(disabled
)
867 number
= '%s' % (block_number
+ 1)
868 painter
.drawText(rect
.x(), rect
.y(),
869 self
.width() - defs
.large_margin
,
871 Qt
.AlignRight | Qt
.AlignVCenter
,
873 block
= block
.next() # pylint: disable=next-method-called