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
):
77 def __init__(self
, widget
, get_value
, readonly
):
78 QtCore
.QObject
.__init
__(self
, widget
)
80 self
.cursor_position
= TextEditCursorPosition(widget
, self
)
82 get_value
= get_stripped
83 self
._get
_value
= get_value
85 self
._readonly
= readonly
89 def _init_flags(self
):
91 widget
.setMinimumSize(QtCore
.QSize(1, 1))
92 widget
.setWordWrapMode(QtGui
.QTextOption
.WordWrap
)
93 widget
.setLineWrapMode(widget
.NoWrap
)
95 widget
.setReadOnly(True)
96 widget
.setAcceptDrops(False)
97 widget
.setTabChangesFocus(True)
98 widget
.setUndoRedoEnabled(False)
99 widget
.setTextInteractionFlags(
100 Qt
.TextSelectableByKeyboard | 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
), QtGui
.QTextCursor
.KeepAnchor
)
129 self
.widget
.setTextCursor(cursor
)
131 elif value
== old_value
:
132 # Otherwise, if the text is identical and there is no selection
133 # then restore the cursor position.
134 cursor
= self
.widget
.textCursor()
135 cursor
.setPosition(offset
)
136 self
.widget
.setTextCursor(cursor
)
138 # If none of the above applied then restore the cursor position.
139 position
= max(0, min(offset
, len(value
) - 1))
140 cursor
= self
.widget
.textCursor()
141 cursor
.setPosition(position
)
142 self
.widget
.setTextCursor(cursor
)
143 cursor
= self
.widget
.textCursor()
144 cursor
.movePosition(QtGui
.QTextCursor
.StartOfLine
)
145 self
.widget
.setTextCursor(cursor
)
148 self
.widget
.blockSignals(blocksig
)
150 def set_cursor_position(self
, new_position
):
151 cursor
= self
.widget
.textCursor()
152 cursor
.setPosition(new_position
)
153 self
.widget
.setTextCursor(cursor
)
156 return self
._tabwidth
158 def set_tabwidth(self
, width
):
159 self
._tabwidth
= width
160 font
= self
.widget
.font()
161 fm
= QtGui
.QFontMetrics(font
)
162 pixels
= fm
.width('M' * width
)
163 self
.widget
.setTabStopWidth(pixels
)
165 def selected_line(self
):
166 contents
= self
.value()
167 cursor
= self
.widget
.textCursor()
168 offset
= min(cursor
.position(), len(contents
) - 1)
169 while offset
>= 1 and contents
[offset
- 1] and contents
[offset
- 1] != '\n':
171 data
= contents
[offset
:]
173 line
, _
= data
.split('\n', 1)
179 return self
.widget
.textCursor()
181 def has_selection(self
):
182 return self
.cursor().hasSelection()
184 def offset_and_selection(self
):
185 cursor
= self
.cursor()
186 offset
= cursor
.selectionStart()
187 selection_text
= cursor
.selection().toPlainText()
188 return offset
, selection_text
190 def mouse_press_event(self
, event
):
191 # Move the text cursor so that the right-click events operate
192 # on the current position, not the last left-clicked position.
194 if event
.button() == Qt
.RightButton
:
195 if not widget
.textCursor().hasSelection():
196 cursor
= widget
.cursorForPosition(event
.pos())
197 widget
.setTextCursor(cursor
)
199 # For extension by sub-classes
201 # pylint: disable=no-self-use
203 """Called during init for class-specific settings"""
206 # pylint: disable=no-self-use,unused-argument
207 def set_textwidth(self
, width
):
208 """Set the text width"""
211 # pylint: disable=no-self-use,unused-argument
212 def set_linebreak(self
, brk
):
213 """Enable word wrapping"""
217 class PlainTextEditExtension(BaseTextEditExtension
):
218 def set_linebreak(self
, brk
):
220 wrapmode
= QtWidgets
.QPlainTextEdit
.WidgetWidth
222 wrapmode
= QtWidgets
.QPlainTextEdit
.NoWrap
223 self
.widget
.setLineWrapMode(wrapmode
)
226 class PlainTextEdit(QtWidgets
.QPlainTextEdit
):
228 cursor_changed
= Signal(int, int)
231 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
232 QtWidgets
.QPlainTextEdit
.__init
__(self
, parent
)
233 self
.ext
= PlainTextEditExtension(self
, get_value
, readonly
)
234 self
.cursor_position
= self
.ext
.cursor_position
237 """Return the raw unicode value from Qt"""
238 return self
.ext
.get()
240 # For compatibility with QTextEdit
241 def setText(self
, value
):
242 self
.set_value(value
)
245 """Return a safe value, e.g. a stripped value"""
246 return self
.ext
.value()
248 def set_value(self
, value
, block
=False):
249 self
.ext
.set_value(value
, block
=block
)
251 def has_selection(self
):
252 return self
.ext
.has_selection()
254 def selected_line(self
):
255 return self
.ext
.selected_line()
257 def set_tabwidth(self
, width
):
258 self
.ext
.set_tabwidth(width
)
260 def set_textwidth(self
, width
):
261 self
.ext
.set_textwidth(width
)
263 def set_linebreak(self
, brk
):
264 self
.ext
.set_linebreak(brk
)
266 def mousePressEvent(self
, event
):
267 self
.ext
.mouse_press_event(event
)
268 super(PlainTextEdit
, self
).mousePressEvent(event
)
270 def wheelEvent(self
, event
):
271 """Disable control+wheelscroll text resizing"""
272 if event
.modifiers() & Qt
.ControlModifier
:
275 super(PlainTextEdit
, self
).wheelEvent(event
)
278 class TextEditExtension(BaseTextEditExtension
):
281 widget
.setAcceptRichText(False)
283 def set_linebreak(self
, brk
):
285 wrapmode
= QtWidgets
.QTextEdit
.FixedColumnWidth
287 wrapmode
= QtWidgets
.QTextEdit
.NoWrap
288 self
.widget
.setLineWrapMode(wrapmode
)
290 def set_textwidth(self
, width
):
291 self
.widget
.setLineWrapColumnOrWidth(width
)
294 class TextEdit(QtWidgets
.QTextEdit
):
296 cursor_changed
= Signal(int, int)
299 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
300 QtWidgets
.QTextEdit
.__init
__(self
, parent
)
301 self
.ext
= TextEditExtension(self
, get_value
, readonly
)
302 self
.cursor_position
= self
.ext
.cursor_position
303 self
.expandtab_enabled
= False
306 """Return the raw unicode value from Qt"""
307 return self
.ext
.get()
310 """Return a safe value, e.g. a stripped value"""
311 return self
.ext
.value()
313 def set_value(self
, value
, block
=False):
314 self
.ext
.set_value(value
, block
=block
)
316 def selected_line(self
):
317 return self
.ext
.selected_line()
319 def set_tabwidth(self
, width
):
320 self
.ext
.set_tabwidth(width
)
322 def set_textwidth(self
, width
):
323 self
.ext
.set_textwidth(width
)
325 def set_linebreak(self
, brk
):
326 self
.ext
.set_linebreak(brk
)
328 def set_expandtab(self
, value
):
329 self
.expandtab_enabled
= value
331 def mousePressEvent(self
, event
):
332 self
.ext
.mouse_press_event(event
)
333 super(TextEdit
, self
).mousePressEvent(event
)
335 def wheelEvent(self
, event
):
336 """Disable control+wheelscroll text resizing"""
337 if event
.modifiers() & Qt
.ControlModifier
:
340 super(TextEdit
, self
).wheelEvent(event
)
342 def should_expandtab(self
, event
):
343 return event
.key() == Qt
.Key_Tab
and self
.expandtab_enabled
346 tabwidth
= max(self
.ext
.tabwidth(), 1)
347 cursor
= self
.textCursor()
348 cursor
.insertText(' ' * tabwidth
)
350 def keyPressEvent(self
, event
):
351 expandtab
= self
.should_expandtab(event
)
356 QtWidgets
.QTextEdit
.keyPressEvent(self
, event
)
358 def keyReleaseEvent(self
, event
):
359 expandtab
= self
.should_expandtab(event
)
363 QtWidgets
.QTextEdit
.keyReleaseEvent(self
, event
)
366 class TextEditCursorPosition(object):
367 def __init__(self
, widget
, ext
):
368 self
._widget
= widget
370 widget
.cursorPositionChanged
.connect(self
.emit
)
373 widget
= self
._widget
375 cursor
= widget
.textCursor()
376 position
= cursor
.position()
378 before
= txt
[:position
]
379 row
= before
.count('\n')
380 line
= before
.split('\n')[row
]
381 col
= cursor
.columnNumber()
382 col
+= line
[:col
].count('\t') * (ext
.tabwidth() - 1)
383 widget
.cursor_changed
.emit(row
+ 1, col
)
386 widget
= self
._widget
387 cursor
= widget
.textCursor()
388 cursor
.setPosition(0)
389 widget
.setTextCursor(cursor
)
392 class MonoTextEdit(PlainTextEdit
):
393 def __init__(self
, context
, parent
=None, readonly
=False):
394 PlainTextEdit
.__init
__(self
, parent
=parent
, readonly
=readonly
)
395 self
.setFont(qtutils
.diff_font(context
))
398 def get_value_hinted(widget
):
399 text
= get_stripped(widget
)
400 hint
= get(widget
.hint
)
406 class HintWidget(QtCore
.QObject
):
407 """Extend a widget to provide hint messages
409 This primarily exists because setPlaceholderText() is only available
410 in Qt5, so this class provides consistent behavior across versions.
414 def __init__(self
, widget
, hint
):
415 QtCore
.QObject
.__init
__(self
, widget
)
416 self
._widget
= widget
418 self
._is
_error
= False
420 self
.modern
= modern
= hasattr(widget
, 'setPlaceholderText')
422 widget
.setPlaceholderText(hint
)
424 # Palette for normal text
425 QPalette
= QtGui
.QPalette
426 palette
= widget
.palette()
428 hint_color
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
429 error_bg_color
= QtGui
.QColor(Qt
.red
).darker()
430 error_fg_color
= QtGui
.QColor(Qt
.white
)
432 hint_rgb
= qtutils
.rgb_css(hint_color
)
433 error_bg_rgb
= qtutils
.rgb_css(error_bg_color
)
434 error_fg_rgb
= qtutils
.rgb_css(error_fg_color
)
437 name
=widget
.__class
__.__name
__,
438 error_fg_rgb
=error_fg_rgb
,
439 error_bg_rgb
=error_bg_rgb
,
443 self
._default
_style
= ''
454 self
._error
_style
= (
457 color: %(error_fg_rgb)s;
458 background-color: %(error_bg_rgb)s;
465 """Defered initialization"""
467 self
.widget().setPlaceholderText(self
.value())
469 self
.widget().installEventFilter(self
)
473 """Return the parent text widget"""
477 """Return True when hint-mode is active"""
478 return self
.value() == get_stripped(self
._widget
)
481 """Return the current hint text"""
484 def set_error(self
, is_error
):
485 """Enable/disable error mode"""
486 self
._is
_error
= is_error
489 def set_value(self
, hint
):
490 """Change the hint text"""
493 self
._widget
.setPlaceholderText(hint
)
495 # If hint-mode is currently active, re-activate it
496 active
= self
.active()
498 if active
or self
.active():
501 def enable(self
, enable
):
502 """Enable/disable hint-mode"""
504 if enable
and self
._hint
:
505 self
._widget
.set_value(self
._hint
, block
=True)
506 self
._widget
.cursor_position
.reset()
509 self
._update
_palette
(enable
)
512 """Update the palette to match the current mode"""
513 self
._update
_palette
(self
.active())
515 def _update_palette(self
, hint
):
516 """Update to palette for normal/error/hint mode"""
518 style
= self
._error
_style
519 elif not self
.modern
and hint
:
520 style
= self
._hint
_style
522 style
= self
._default
_style
523 QtCore
.QTimer
.singleShot(0, lambda: self
._widget
.setStyleSheet(style
))
525 def eventFilter(self
, _obj
, event
):
526 """Enable/disable hint-mode when focus changes"""
528 if etype
== QtCore
.QEvent
.FocusIn
:
530 elif etype
== QtCore
.QEvent
.FocusOut
:
535 """Disable hint-mode when focused"""
536 widget
= self
.widget()
539 widget
.cursor_position
.emit()
542 """Re-enable hint-mode when losing focus"""
543 widget
= self
.widget()
548 class HintedPlainTextEdit(PlainTextEdit
):
549 """A hinted plain text edit"""
551 def __init__(self
, context
, hint
, parent
=None, readonly
=False):
552 PlainTextEdit
.__init
__(
553 self
, parent
=parent
, get_value
=get_value_hinted
, readonly
=readonly
555 self
.hint
= HintWidget(self
, hint
)
557 self
.setFont(qtutils
.diff_font(context
))
558 self
.set_tabwidth(prefs
.tabwidth(context
))
559 # Refresh palettes when text changes
560 # pylint: disable=no-member
561 self
.textChanged
.connect(self
.hint
.refresh
)
563 def set_value(self
, value
, block
=False):
564 """Set the widget text or enable hint mode when empty"""
565 if value
or self
.hint
.modern
:
566 PlainTextEdit
.set_value(self
, value
, block
=block
)
568 self
.hint
.enable(True)
571 class HintedTextEdit(TextEdit
):
572 """A hinted text edit"""
574 def __init__(self
, context
, hint
, parent
=None, readonly
=False):
576 self
, parent
=parent
, get_value
=get_value_hinted
, readonly
=readonly
578 self
.hint
= HintWidget(self
, hint
)
580 # Refresh palettes when text changes
581 # pylint: disable=no-member
582 self
.textChanged
.connect(self
.hint
.refresh
)
583 self
.setFont(qtutils
.diff_font(context
))
585 def set_value(self
, value
, block
=False):
586 """Set the widget text or enable hint mode when empty"""
587 if value
or self
.hint
.modern
:
588 TextEdit
.set_value(self
, value
, block
=block
)
590 self
.hint
.enable(True)
593 def anchor_mode(select
):
594 """Return the QTextCursor mode to keep/discard the cursor selection"""
596 mode
= QtGui
.QTextCursor
.KeepAnchor
598 mode
= QtGui
.QTextCursor
.MoveAnchor
602 # The vim-like read-only text view
605 class VimMixin(object):
606 def __init__(self
, widget
):
608 self
.Base
= widget
.Base
609 # Common vim/unix-ish keyboard actions
610 self
.add_navigation('End', hotkeys
.GOTO_END
)
611 self
.add_navigation('Up', hotkeys
.MOVE_UP
, shift
=hotkeys
.MOVE_UP_SHIFT
)
612 self
.add_navigation('Down', hotkeys
.MOVE_DOWN
, shift
=hotkeys
.MOVE_DOWN_SHIFT
)
613 self
.add_navigation('Left', hotkeys
.MOVE_LEFT
, shift
=hotkeys
.MOVE_LEFT_SHIFT
)
614 self
.add_navigation('Right', hotkeys
.MOVE_RIGHT
, shift
=hotkeys
.MOVE_RIGHT_SHIFT
)
615 self
.add_navigation('WordLeft', hotkeys
.WORD_LEFT
)
616 self
.add_navigation('WordRight', hotkeys
.WORD_RIGHT
)
617 self
.add_navigation('Start', hotkeys
.GOTO_START
)
618 self
.add_navigation('StartOfLine', hotkeys
.START_OF_LINE
)
619 self
.add_navigation('EndOfLine', hotkeys
.END_OF_LINE
)
622 widget
, 'PageUp', widget
.page_up
, hotkeys
.SECONDARY_ACTION
, hotkeys
.UP
625 widget
, 'PageDown', widget
.page_down
, hotkeys
.PRIMARY_ACTION
, hotkeys
.DOWN
630 lambda: widget
.page_up(select
=True),
637 lambda: widget
.page_down(select
=True),
638 hotkeys
.SELECT_FORWARD
,
642 def add_navigation(self
, name
, hotkey
, shift
=None):
643 """Add a hotkey along with a shift-variant"""
645 direction
= getattr(QtGui
.QTextCursor
, name
)
646 qtutils
.add_action(widget
, name
, lambda: self
.move(direction
), hotkey
)
649 widget
, 'Shift' + name
, lambda: self
.move(direction
, select
=True), shift
652 def move(self
, direction
, select
=False, n
=1):
654 cursor
= widget
.textCursor()
655 mode
= anchor_mode(select
)
657 if cursor
.movePosition(direction
, mode
, 1):
658 self
.set_text_cursor(cursor
)
660 def page(self
, offset
, select
=False):
662 rect
= widget
.cursorRect()
664 y
= rect
.y() + offset
665 new_cursor
= widget
.cursorForPosition(QtCore
.QPoint(x
, y
))
666 if new_cursor
is not None:
667 cursor
= widget
.textCursor()
668 mode
= anchor_mode(select
)
669 cursor
.setPosition(new_cursor
.position(), mode
)
671 self
.set_text_cursor(cursor
)
673 def page_down(self
, select
=False):
675 widget
.page(widget
.height() // 2, select
=select
)
677 def page_up(self
, select
=False):
679 widget
.page(-widget
.height() // 2, select
=select
)
681 def set_text_cursor(self
, cursor
):
683 widget
.setTextCursor(cursor
)
684 widget
.ensureCursorVisible()
685 widget
.viewport().update()
687 def keyPressEvent(self
, event
):
688 """Custom keyboard behaviors
690 The leave() signal is emitted when `Up` is pressed and we're already
691 at the beginning of the text. This allows the parent widget to
692 orchestrate some higher-level interaction, such as giving focus to
695 When in the middle of the first line and `Up` is pressed, the cursor
696 is moved to the beginning of the line.
700 if event
.key() == Qt
.Key_Up
:
701 cursor
= widget
.textCursor()
702 position
= cursor
.position()
704 # The cursor is at the beginning of the line.
705 # Emit a signal so that the parent can e.g. change focus.
707 elif get(widget
)[:position
].count('\n') == 0:
708 # The cursor is in the middle of the first line of text.
709 # We can't go up ~ jump to the beginning of the line.
710 # Select the text if shift is pressed.
711 select
= event
.modifiers() & Qt
.ShiftModifier
712 mode
= anchor_mode(select
)
713 cursor
.movePosition(QtGui
.QTextCursor
.StartOfLine
, mode
)
714 widget
.setTextCursor(cursor
)
716 return self
.Base
.keyPressEvent(widget
, event
)
719 # pylint: disable=too-many-ancestors
720 class VimHintedPlainTextEdit(HintedPlainTextEdit
):
721 """HintedPlainTextEdit with vim hotkeys
723 This can only be used in read-only mode.
727 Base
= HintedPlainTextEdit
730 def __init__(self
, context
, hint
, parent
=None):
731 HintedPlainTextEdit
.__init
__(self
, context
, hint
, parent
=parent
, readonly
=True)
732 self
._mixin
= self
.Mixin(self
)
734 def move(self
, direction
, select
=False, n
=1):
735 return self
._mixin
.page(direction
, select
=select
, n
=n
)
737 def page(self
, offset
, select
=False):
738 return self
._mixin
.page(offset
, select
=select
)
740 def page_up(self
, select
=False):
741 return self
._mixin
.page_up(select
=select
)
743 def page_down(self
, select
=False):
744 return self
._mixin
.page_down(select
=select
)
746 def keyPressEvent(self
, event
):
747 return self
._mixin
.keyPressEvent(event
)
750 # pylint: disable=too-many-ancestors
751 class VimTextEdit(MonoTextEdit
):
752 """Text viewer with vim-like hotkeys
754 This can only be used in read-only mode.
761 def __init__(self
, context
, parent
=None, readonly
=True):
762 MonoTextEdit
.__init
__(self
, context
, parent
=None, readonly
=readonly
)
763 self
._mixin
= self
.Mixin(self
)
765 def move(self
, direction
, select
=False, n
=1):
766 return self
._mixin
.page(direction
, select
=select
, n
=n
)
768 def page(self
, offset
, select
=False):
769 return self
._mixin
.page(offset
, select
=select
)
771 def page_up(self
, select
=False):
772 return self
._mixin
.page_up(select
=select
)
774 def page_down(self
, select
=False):
775 return self
._mixin
.page_down(select
=select
)
777 def keyPressEvent(self
, event
):
778 return self
._mixin
.keyPressEvent(event
)
781 class HintedDefaultLineEdit(LineEdit
):
782 """A line edit with hint text"""
784 def __init__(self
, hint
, tooltip
=None, parent
=None):
785 LineEdit
.__init
__(self
, parent
=parent
, get_value
=get_value_hinted
)
787 self
.setToolTip(tooltip
)
788 self
.hint
= HintWidget(self
, hint
)
790 # pylint: disable=no-member
791 self
.textChanged
.connect(lambda text
: self
.hint
.refresh())
794 class HintedLineEdit(HintedDefaultLineEdit
):
795 """A monospace line edit with hint text"""
797 def __init__(self
, context
, hint
, tooltip
=None, parent
=None):
798 super(HintedLineEdit
, self
).__init
__(hint
, tooltip
=tooltip
, parent
=parent
)
799 self
.setFont(qtutils
.diff_font(context
))
802 def text_dialog(context
, text
, title
):
803 """Show a wall of text in a dialog"""
804 parent
= qtutils
.active_window()
806 label
= QtWidgets
.QLabel(parent
)
807 label
.setFont(qtutils
.diff_font(context
))
809 label
.setMargin(defs
.large_margin
)
810 text_flags
= Qt
.TextSelectableByKeyboard | Qt
.TextSelectableByMouse
811 label
.setTextInteractionFlags(text_flags
)
813 widget
= QtWidgets
.QDialog(parent
)
814 widget
.setWindowModality(Qt
.WindowModal
)
815 widget
.setWindowTitle(title
)
817 scroll
= QtWidgets
.QScrollArea()
818 scroll
.setWidget(label
)
820 layout
= qtutils
.hbox(defs
.margin
, defs
.spacing
, scroll
)
821 widget
.setLayout(layout
)
824 widget
, N_('Close'), widget
.accept
, Qt
.Key_Question
, Qt
.Key_Enter
, Qt
.Key_Return
830 class VimTextBrowser(VimTextEdit
):
831 """Text viewer with line number annotations"""
833 def __init__(self
, context
, parent
=None, readonly
=True):
834 VimTextEdit
.__init
__(self
, context
, parent
=parent
, readonly
=readonly
)
835 self
.numbers
= LineNumbers(self
)
837 def resizeEvent(self
, event
):
838 super(VimTextBrowser
, self
).resizeEvent(event
)
839 self
.numbers
.refresh_size()
842 class TextDecorator(QtWidgets
.QWidget
):
843 """Common functionality for providing line numbers in text widgets"""
845 def __init__(self
, parent
):
846 QtWidgets
.QWidget
.__init
__(self
, parent
)
849 parent
.blockCountChanged
.connect(lambda x
: self
._refresh
_viewport
())
850 parent
.cursorPositionChanged
.connect(self
.refresh
)
851 parent
.updateRequest
.connect(self
._refresh
_rect
)
854 """Refresh the numbers display"""
855 rect
= self
.editor
.viewport().rect()
856 self
._refresh
_rect
(rect
, 0)
858 def _refresh_rect(self
, rect
, dy
):
862 self
.update(0, rect
.y(), self
.width(), rect
.height())
864 if rect
.contains(self
.editor
.viewport().rect()):
865 self
._refresh
_viewport
()
867 def _refresh_viewport(self
):
868 self
.editor
.setViewportMargins(self
.width_hint(), 0, 0, 0)
870 def refresh_size(self
):
871 rect
= self
.editor
.contentsRect()
872 geom
= QtCore
.QRect(rect
.left(), rect
.top(), self
.width_hint(), rect
.height())
873 self
.setGeometry(geom
)
876 return QtCore
.QSize(self
.width_hint(), 0)
879 class LineNumbers(TextDecorator
):
880 """Provide line numbers for QPlainTextEdit widgets"""
882 def __init__(self
, parent
):
883 TextDecorator
.__init
__(self
, parent
)
884 self
.highlight_line
= -1
886 def width_hint(self
):
887 document
= self
.editor
.document()
888 digits
= int(math
.log(max(1, document
.blockCount()), 10)) + 2
889 return defs
.large_margin
+ self
.fontMetrics().width('0') * digits
891 def set_highlighted(self
, line_number
):
892 """Set the line to highlight"""
893 self
.highlight_line
= line_number
895 def paintEvent(self
, event
):
896 """Paint the line number"""
897 QPalette
= QtGui
.QPalette
898 painter
= QtGui
.QPainter(self
)
900 palette
= editor
.palette()
902 painter
.fillRect(event
.rect(), palette
.color(QPalette
.Base
))
904 content_offset
= editor
.contentOffset()
905 block
= editor
.firstVisibleBlock()
907 event_rect_bottom
= event
.rect().bottom()
909 highlight
= palette
.color(QPalette
.Highlight
)
910 highlighted_text
= palette
.color(QPalette
.HighlightedText
)
911 disabled
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
913 while block
.isValid():
914 block_geom
= editor
.blockBoundingGeometry(block
)
915 block_top
= block_geom
.translated(content_offset
).top()
916 if not block
.isVisible() or block_top
>= event_rect_bottom
:
919 rect
= block_geom
.translated(content_offset
).toRect()
920 block_number
= block
.blockNumber()
921 if block_number
== self
.highlight_line
:
922 painter
.fillRect(rect
.x(), rect
.y(), width
, rect
.height(), highlight
)
923 painter
.setPen(highlighted_text
)
925 painter
.setPen(disabled
)
927 number
= '%s' % (block_number
+ 1)
931 self
.width() - defs
.large_margin
,
933 Qt
.AlignRight | Qt
.AlignVCenter
,
936 block
= block
.next() # pylint: disable=next-method-called