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
204 # pylint: disable=no-self-use
206 """Called during init for class-specific settings"""
209 # pylint: disable=no-self-use,unused-argument
210 def set_textwidth(self
, width
):
211 """Set the text width"""
214 # pylint: disable=no-self-use,unused-argument
215 def set_linebreak(self
, brk
):
216 """Enable word wrapping"""
220 class PlainTextEditExtension(BaseTextEditExtension
):
222 def set_linebreak(self
, brk
):
224 wrapmode
= QtWidgets
.QPlainTextEdit
.WidgetWidth
226 wrapmode
= QtWidgets
.QPlainTextEdit
.NoWrap
227 self
.widget
.setLineWrapMode(wrapmode
)
230 class PlainTextEdit(QtWidgets
.QPlainTextEdit
):
232 cursor_changed
= Signal(int, int)
235 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
236 QtWidgets
.QPlainTextEdit
.__init
__(self
, parent
)
237 self
.ext
= PlainTextEditExtension(self
, get_value
, readonly
)
238 self
.cursor_position
= self
.ext
.cursor_position
241 """Return the raw unicode value from Qt"""
242 return self
.ext
.get()
244 # For compatibility with QTextEdit
245 def setText(self
, value
):
246 self
.set_value(value
)
249 """Return a safe value, e.g. a stripped value"""
250 return self
.ext
.value()
252 def set_value(self
, value
, block
=False):
253 self
.ext
.set_value(value
, block
=block
)
255 def has_selection(self
):
256 return self
.ext
.has_selection()
258 def selected_line(self
):
259 return self
.ext
.selected_line()
261 def set_tabwidth(self
, width
):
262 self
.ext
.set_tabwidth(width
)
264 def set_textwidth(self
, width
):
265 self
.ext
.set_textwidth(width
)
267 def set_linebreak(self
, brk
):
268 self
.ext
.set_linebreak(brk
)
270 def mousePressEvent(self
, event
):
271 self
.ext
.mouse_press_event(event
)
272 super(PlainTextEdit
, self
).mousePressEvent(event
)
274 def wheelEvent(self
, event
):
275 """Disable control+wheelscroll text resizing"""
276 if event
.modifiers() & Qt
.ControlModifier
:
279 super(PlainTextEdit
, self
).wheelEvent(event
)
282 class TextEditExtension(BaseTextEditExtension
):
286 widget
.setAcceptRichText(False)
288 def set_linebreak(self
, brk
):
290 wrapmode
= QtWidgets
.QTextEdit
.FixedColumnWidth
292 wrapmode
= QtWidgets
.QTextEdit
.NoWrap
293 self
.widget
.setLineWrapMode(wrapmode
)
295 def set_textwidth(self
, width
):
296 self
.widget
.setLineWrapColumnOrWidth(width
)
299 class TextEdit(QtWidgets
.QTextEdit
):
301 cursor_changed
= Signal(int, int)
304 def __init__(self
, parent
=None, get_value
=None, readonly
=False):
305 QtWidgets
.QTextEdit
.__init
__(self
, parent
)
306 self
.ext
= TextEditExtension(self
, get_value
, readonly
)
307 self
.cursor_position
= self
.ext
.cursor_position
308 self
.expandtab_enabled
= False
311 """Return the raw unicode value from Qt"""
312 return self
.ext
.get()
315 """Return a safe value, e.g. a stripped value"""
316 return self
.ext
.value()
318 def set_value(self
, value
, block
=False):
319 self
.ext
.set_value(value
, block
=block
)
321 def selected_line(self
):
322 return self
.ext
.selected_line()
324 def set_tabwidth(self
, width
):
325 self
.ext
.set_tabwidth(width
)
327 def set_textwidth(self
, width
):
328 self
.ext
.set_textwidth(width
)
330 def set_linebreak(self
, brk
):
331 self
.ext
.set_linebreak(brk
)
333 def set_expandtab(self
, value
):
334 self
.expandtab_enabled
= value
336 def mousePressEvent(self
, event
):
337 self
.ext
.mouse_press_event(event
)
338 super(TextEdit
, self
).mousePressEvent(event
)
340 def wheelEvent(self
, event
):
341 """Disable control+wheelscroll text resizing"""
342 if event
.modifiers() & Qt
.ControlModifier
:
345 super(TextEdit
, self
).wheelEvent(event
)
347 def should_expandtab(self
, event
):
348 return event
.key() == Qt
.Key_Tab
and self
.expandtab_enabled
351 tabwidth
= max(self
.ext
.tabwidth(), 1)
352 cursor
= self
.textCursor()
353 cursor
.insertText(' ' * tabwidth
)
355 def keyPressEvent(self
, event
):
356 expandtab
= self
.should_expandtab(event
)
361 QtWidgets
.QTextEdit
.keyPressEvent(self
, event
)
363 def keyReleaseEvent(self
, event
):
364 expandtab
= self
.should_expandtab(event
)
368 QtWidgets
.QTextEdit
.keyReleaseEvent(self
, event
)
371 class TextEditCursorPosition(object):
373 def __init__(self
, widget
, ext
):
374 self
._widget
= widget
376 widget
.cursorPositionChanged
.connect(self
.emit
)
379 widget
= self
._widget
381 cursor
= widget
.textCursor()
382 position
= cursor
.position()
384 before
= txt
[:position
]
385 row
= before
.count('\n')
386 line
= before
.split('\n')[row
]
387 col
= cursor
.columnNumber()
388 col
+= line
[:col
].count('\t') * (ext
.tabwidth() - 1)
389 widget
.cursor_changed
.emit(row
+1, col
)
392 widget
= self
._widget
393 cursor
= widget
.textCursor()
394 cursor
.setPosition(0)
395 widget
.setTextCursor(cursor
)
398 class MonoTextEdit(PlainTextEdit
):
400 def __init__(self
, context
, parent
=None, readonly
=False):
401 PlainTextEdit
.__init
__(self
, parent
=parent
, readonly
=readonly
)
402 self
.setFont(qtutils
.diff_font(context
))
405 def get_value_hinted(widget
):
406 text
= get_stripped(widget
)
407 hint
= get(widget
.hint
)
413 class HintWidget(QtCore
.QObject
):
414 """Extend a widget to provide hint messages
416 This primarily exists because setPlaceholderText() is only available
417 in Qt5, so this class provides consistent behavior across versions.
421 def __init__(self
, widget
, hint
):
422 QtCore
.QObject
.__init
__(self
, widget
)
423 self
._widget
= widget
425 self
._is
_error
= False
427 self
.modern
= modern
= hasattr(widget
, 'setPlaceholderText')
429 widget
.setPlaceholderText(hint
)
431 # Palette for normal text
432 QPalette
= QtGui
.QPalette
433 palette
= widget
.palette()
435 hint_color
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
436 error_bg_color
= QtGui
.QColor(Qt
.red
).darker()
437 error_fg_color
= QtGui
.QColor(Qt
.white
)
439 hint_rgb
= qtutils
.rgb_css(hint_color
)
440 error_bg_rgb
= qtutils
.rgb_css(error_bg_color
)
441 error_fg_rgb
= qtutils
.rgb_css(error_fg_color
)
443 env
= dict(name
=widget
.__class
__.__name
__,
444 error_fg_rgb
=error_fg_rgb
,
445 error_bg_rgb
=error_bg_rgb
,
448 self
._default
_style
= ''
450 self
._hint
_style
= """
456 self
._error
_style
= """
458 color: %(error_fg_rgb)s;
459 background-color: %(error_bg_rgb)s;
464 """Defered initialization"""
466 self
.widget().setPlaceholderText(self
.value())
468 self
.widget().installEventFilter(self
)
472 """Return the parent text widget"""
476 """Return True when hint-mode is active"""
477 return self
.value() == get_stripped(self
._widget
)
480 """Return the current hint text"""
483 def set_error(self
, is_error
):
484 """Enable/disable error mode"""
485 self
._is
_error
= is_error
488 def set_value(self
, hint
):
489 """Change the hint text"""
492 self
._widget
.setPlaceholderText(hint
)
494 # If hint-mode is currently active, re-activate it
495 active
= self
.active()
497 if active
or self
.active():
500 def enable(self
, enable
):
501 """Enable/disable hint-mode"""
503 if enable
and self
._hint
:
504 self
._widget
.set_value(self
._hint
, block
=True)
505 self
._widget
.cursor_position
.reset()
508 self
._update
_palette
(enable
)
511 """Update the palette to match the current mode"""
512 self
._update
_palette
(self
.active())
514 def _update_palette(self
, hint
):
515 """Update to palette for normal/error/hint mode"""
517 style
= self
._error
_style
518 elif not self
.modern
and hint
:
519 style
= self
._hint
_style
521 style
= self
._default
_style
522 QtCore
.QTimer
.singleShot(
523 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
__(self
, parent
=parent
,
553 get_value
=get_value_hinted
,
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 self
.textChanged
.connect(self
.hint
.refresh
)
562 def set_value(self
, value
, block
=False):
563 """Set the widget text or enable hint mode when empty"""
564 if value
or self
.hint
.modern
:
565 PlainTextEdit
.set_value(self
, value
, block
=block
)
567 self
.hint
.enable(True)
570 class HintedTextEdit(TextEdit
):
571 """A hinted text edit"""
573 def __init__(self
, context
, hint
, parent
=None, readonly
=False):
574 TextEdit
.__init
__(self
, parent
=parent
,
575 get_value
=get_value_hinted
, readonly
=readonly
)
576 self
.hint
= HintWidget(self
, hint
)
578 # Refresh palettes when text changes
579 self
.textChanged
.connect(self
.hint
.refresh
)
580 self
.setFont(qtutils
.diff_font(context
))
582 def set_value(self
, value
, block
=False):
583 """Set the widget text or enable hint mode when empty"""
584 if value
or self
.hint
.modern
:
585 TextEdit
.set_value(self
, value
, block
=block
)
587 self
.hint
.enable(True)
590 # The vim-like read-only text view
592 class VimMixin(object):
594 def __init__(self
, widget
):
596 self
.Base
= widget
.Base
597 # Common vim/unix-ish keyboard actions
598 self
.add_navigation('Up', hotkeys
.MOVE_UP
,
599 shift
=hotkeys
.MOVE_UP_SHIFT
)
600 self
.add_navigation('Down', hotkeys
.MOVE_DOWN
,
601 shift
=hotkeys
.MOVE_DOWN_SHIFT
)
602 self
.add_navigation('Left', hotkeys
.MOVE_LEFT
,
603 shift
=hotkeys
.MOVE_LEFT_SHIFT
)
604 self
.add_navigation('Right', hotkeys
.MOVE_RIGHT
,
605 shift
=hotkeys
.MOVE_RIGHT_SHIFT
)
606 self
.add_navigation('WordLeft', hotkeys
.WORD_LEFT
)
607 self
.add_navigation('WordRight', hotkeys
.WORD_RIGHT
)
608 self
.add_navigation('StartOfLine', hotkeys
.START_OF_LINE
)
609 self
.add_navigation('EndOfLine', hotkeys
.END_OF_LINE
)
611 qtutils
.add_action(widget
, 'PageUp',
612 lambda: widget
.page(-widget
.height()//2),
613 hotkeys
.SECONDARY_ACTION
)
615 qtutils
.add_action(widget
, 'PageDown',
616 lambda: widget
.page(widget
.height()//2),
617 hotkeys
.PRIMARY_ACTION
)
619 def add_navigation(self
, name
, hotkey
, shift
=None):
620 """Add a hotkey along with a shift-variant"""
622 direction
= getattr(QtGui
.QTextCursor
, name
)
623 qtutils
.add_action(widget
, name
,
624 lambda: self
.move(direction
), hotkey
)
626 qtutils
.add_action(widget
, 'Shift' + name
,
627 lambda: self
.move(direction
, True), shift
)
629 def move(self
, direction
, select
=False, n
=1):
631 cursor
= widget
.textCursor()
633 mode
= QtGui
.QTextCursor
.KeepAnchor
635 mode
= QtGui
.QTextCursor
.MoveAnchor
636 if cursor
.movePosition(direction
, mode
, n
):
637 self
.set_text_cursor(cursor
)
639 def page(self
, offset
):
641 rect
= widget
.cursorRect()
643 y
= rect
.y() + offset
644 new_cursor
= widget
.cursorForPosition(QtCore
.QPoint(x
, y
))
645 if new_cursor
is not None:
646 self
.set_text_cursor(new_cursor
)
648 def set_text_cursor(self
, cursor
):
650 widget
.setTextCursor(cursor
)
651 widget
.ensureCursorVisible()
652 widget
.viewport().update()
654 def keyPressEvent(self
, event
):
655 """Custom keyboard behaviors
657 The leave() signal is emitted when `Up` is pressed and we're already
658 at the beginning of the text. This allows the parent widget to
659 orchestrate some higher-level interaction, such as giving focus to
662 When in the middle of the first line and `Up` is pressed, the cursor
663 is moved to the beginning of the line.
667 if event
.key() == Qt
.Key_Up
:
668 cursor
= widget
.textCursor()
669 position
= cursor
.position()
671 # The cursor is at the beginning of the line.
672 # Emit a signal so that the parent can e.g. change focus.
674 elif get(widget
)[:position
].count('\n') == 0:
675 # The cursor is in the middle of the first line of text.
676 # We can't go up ~ jump to the beginning of the line.
677 # Select the text if shift is pressed.
678 if event
.modifiers() & Qt
.ShiftModifier
:
679 mode
= QtGui
.QTextCursor
.KeepAnchor
681 mode
= QtGui
.QTextCursor
.MoveAnchor
682 cursor
.movePosition(QtGui
.QTextCursor
.StartOfLine
, mode
)
683 widget
.setTextCursor(cursor
)
685 return self
.Base
.keyPressEvent(widget
, event
)
688 class VimHintedPlainTextEdit(HintedPlainTextEdit
):
689 """HintedPlainTextEdit with vim hotkeys
691 This can only be used in read-only mode.
694 Base
= HintedPlainTextEdit
697 def __init__(self
, context
, hint
, parent
=None):
698 HintedPlainTextEdit
.__init
__(
699 self
, context
, hint
, parent
=parent
, readonly
=True)
700 self
._mixin
= self
.Mixin(self
)
702 def move(self
, direction
, select
=False, n
=1):
703 return self
._mixin
.page(direction
, select
=select
, n
=n
)
705 def page(self
, offset
):
706 return self
._mixin
.page(offset
)
708 def keyPressEvent(self
, event
):
709 return self
._mixin
.keyPressEvent(event
)
712 class VimTextEdit(MonoTextEdit
):
713 """Text viewer with vim-like hotkeys
715 This can only be used in read-only mode.
721 def __init__(self
, context
, parent
=None, readonly
=True):
722 MonoTextEdit
.__init
__(self
, context
, parent
=None, readonly
=readonly
)
723 self
._mixin
= self
.Mixin(self
)
725 def move(self
, direction
, select
=False, n
=1):
726 return self
._mixin
.page(direction
, select
=select
, n
=n
)
728 def page(self
, offset
):
729 return self
._mixin
.page(offset
)
731 def keyPressEvent(self
, event
):
732 return self
._mixin
.keyPressEvent(event
)
735 class HintedLineEdit(LineEdit
):
737 def __init__(self
, context
, hint
, parent
=None):
738 LineEdit
.__init
__(self
, parent
=parent
, get_value
=get_value_hinted
)
739 self
.hint
= HintWidget(self
, hint
)
741 self
.setFont(qtutils
.diff_font(context
))
742 self
.textChanged
.connect(lambda text
: self
.hint
.refresh())
745 def text_dialog(context
, text
, title
):
746 """Show a wall of text in a dialog"""
747 parent
= qtutils
.active_window()
749 label
= QtWidgets
.QLabel(parent
)
750 label
.setFont(qtutils
.diff_font(context
))
752 label
.setMargin(defs
.large_margin
)
753 text_flags
= Qt
.TextSelectableByKeyboard | Qt
.TextSelectableByMouse
754 label
.setTextInteractionFlags(text_flags
)
756 widget
= QtWidgets
.QDialog(parent
)
757 widget
.setWindowModality(Qt
.WindowModal
)
758 widget
.setWindowTitle(title
)
760 scroll
= QtWidgets
.QScrollArea()
761 scroll
.setWidget(label
)
763 layout
= qtutils
.hbox(defs
.margin
, defs
.spacing
, scroll
)
764 widget
.setLayout(layout
)
766 qtutils
.add_action(widget
, N_('Close'), widget
.accept
,
767 Qt
.Key_Question
, Qt
.Key_Enter
, Qt
.Key_Return
)
772 class VimTextBrowser(VimTextEdit
):
773 """Text viewer with line number annotations"""
775 def __init__(self
, context
, parent
=None, readonly
=True):
776 VimTextEdit
.__init
__(self
, context
, parent
=parent
, readonly
=readonly
)
777 self
.numbers
= LineNumbers(self
)
779 def resizeEvent(self
, event
):
780 super(VimTextBrowser
, self
).resizeEvent(event
)
781 self
.numbers
.refresh_size()
784 class TextDecorator(QtWidgets
.QWidget
):
785 """Common functionality for providing line numbers in text widgets"""
787 def __init__(self
, parent
):
788 QtWidgets
.QWidget
.__init
__(self
, parent
)
791 parent
.blockCountChanged
.connect(lambda x
: self
._refresh
_viewport
())
792 parent
.cursorPositionChanged
.connect(self
.refresh
)
793 parent
.updateRequest
.connect(self
._refresh
_rect
)
796 """Refresh the numbers display"""
797 rect
= self
.editor
.viewport().rect()
798 self
._refresh
_rect
(rect
, 0)
800 def _refresh_rect(self
, rect
, dy
):
804 self
.update(0, rect
.y(), self
.width(), rect
.height())
806 if rect
.contains(self
.editor
.viewport().rect()):
807 self
._refresh
_viewport
()
809 def _refresh_viewport(self
):
810 self
.editor
.setViewportMargins(self
.width_hint(), 0, 0, 0)
812 def refresh_size(self
):
813 rect
= self
.editor
.contentsRect()
814 geom
= QtCore
.QRect(rect
.left(), rect
.top(),
815 self
.width_hint(), rect
.height())
816 self
.setGeometry(geom
)
819 return QtCore
.QSize(self
.width_hint(), 0)
822 class LineNumbers(TextDecorator
):
823 """Provide line numbers for QPlainTextEdit widgets"""
825 def __init__(self
, parent
):
826 TextDecorator
.__init
__(self
, parent
)
827 self
.highlight_line
= -1
829 def width_hint(self
):
830 document
= self
.editor
.document()
831 digits
= int(math
.log(max(1, document
.blockCount()), 10)) + 2
832 return defs
.large_margin
+ self
.fontMetrics().width('0') * digits
834 def set_highlighted(self
, line_number
):
835 """Set the line to highlight"""
836 self
.highlight_line
= line_number
838 def paintEvent(self
, event
):
839 """Paint the line number"""
840 QPalette
= QtGui
.QPalette
841 painter
= QtGui
.QPainter(self
)
843 palette
= editor
.palette()
845 painter
.fillRect(event
.rect(), palette
.color(QPalette
.Base
))
847 content_offset
= editor
.contentOffset()
848 block
= editor
.firstVisibleBlock()
850 event_rect_bottom
= event
.rect().bottom()
852 highlight
= palette
.color(QPalette
.Highlight
)
853 highlighted_text
= palette
.color(QPalette
.HighlightedText
)
854 disabled
= palette
.color(QPalette
.Disabled
, QPalette
.Text
)
856 while block
.isValid():
857 block_geom
= editor
.blockBoundingGeometry(block
)
858 block_top
= block_geom
.translated(content_offset
).top()
859 if not block
.isVisible() or block_top
>= event_rect_bottom
:
862 rect
= block_geom
.translated(content_offset
).toRect()
863 block_number
= block
.blockNumber()
864 if block_number
== self
.highlight_line
:
865 painter
.fillRect(rect
.x(), rect
.y(),
866 width
, rect
.height(), highlight
)
867 painter
.setPen(highlighted_text
)
869 painter
.setPen(disabled
)
871 number
= '%s' % (block_number
+ 1)
872 painter
.drawText(rect
.x(), rect
.y(),
873 self
.width() - defs
.large_margin
,
875 Qt
.AlignRight | Qt
.AlignVCenter
,
877 block
= block
.next() # pylint: disable=next-method-called