Show More
@@ -11,9 +11,17 b' from completion_widget import CompletionWidget' | |||
|
11 | 11 | |
|
12 | 12 | |
|
13 | 13 | class ConsoleWidget(QtGui.QWidget): |
|
14 |
""" |
|
|
15 | dealing with the prompt, keeping the cursor inside the editing line, and | |
|
16 | handling ANSI escape sequences. | |
|
14 | """ An abstract base class for console-type widgets. This class has | |
|
15 | functionality for: | |
|
16 | ||
|
17 | * Maintaining a prompt and editing region | |
|
18 | * Providing the traditional Unix-style console keyboard shortcuts | |
|
19 | * Performing tab completion | |
|
20 | * Paging text | |
|
21 | * Handling ANSI escape codes | |
|
22 | ||
|
23 | ConsoleWidget also provides a number of utility methods that will be | |
|
24 | convenient to implementors of a console-style widget. | |
|
17 | 25 | """ |
|
18 | 26 | |
|
19 | 27 | # Whether to process ANSI escape codes. |
@@ -35,6 +43,10 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
35 | 43 | redo_available = QtCore.pyqtSignal(bool) |
|
36 | 44 | undo_available = QtCore.pyqtSignal(bool) |
|
37 | 45 | |
|
46 | # Signal emitted when paging is needed and the paging style has been | |
|
47 | # specified as 'custom'. | |
|
48 | custom_page_requested = QtCore.pyqtSignal(QtCore.QString) | |
|
49 | ||
|
38 | 50 | # Protected class variables. |
|
39 | 51 | _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left, |
|
40 | 52 | QtCore.Qt.Key_F : QtCore.Qt.Key_Right, |
@@ -50,26 +62,62 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
50 | 62 | # 'QObject' interface |
|
51 | 63 | #--------------------------------------------------------------------------- |
|
52 | 64 | |
|
53 | def __init__(self, kind='plain', parent=None): | |
|
65 | def __init__(self, kind='plain', paging='inside', parent=None): | |
|
54 | 66 | """ Create a ConsoleWidget. |
|
55 | 67 | |
|
56 | 68 | Parameters |
|
57 | 69 | ---------- |
|
58 | 70 | kind : str, optional [default 'plain'] |
|
59 |
The type of text widget to use. Valid values are 'plain', |
|
|
60 |
specifies a QPlainTextEdit, and 'rich', which specifies a |
|
|
71 | The type of underlying text widget to use. Valid values are 'plain', | |
|
72 | which specifies a QPlainTextEdit, and 'rich', which specifies a | |
|
73 | QTextEdit. | |
|
74 | ||
|
75 | paging : str, optional [default 'inside'] | |
|
76 | The type of paging to use. Valid values are: | |
|
77 | 'inside' : The widget pages like a traditional terminal pager. | |
|
78 | 'hsplit' : When paging is requested, the widget is split | |
|
79 | horizontally. The top pane contains the console, | |
|
80 | and the bottom pane contains the paged text. | |
|
81 | 'vsplit' : Similar to 'hsplit', except that a vertical splitter | |
|
82 | used. | |
|
83 | 'custom' : No action is taken by the widget beyond emitting a | |
|
84 | 'custom_page_requested(QString)' signal. | |
|
85 | 'none' : The text is written directly to the console. | |
|
61 | 86 | |
|
62 | 87 | parent : QWidget, optional [default None] |
|
63 | 88 | The parent for this widget. |
|
64 | 89 | """ |
|
65 | 90 | super(ConsoleWidget, self).__init__(parent) |
|
66 | 91 | |
|
67 |
# Create and |
|
|
68 |
layout = QtGui.Q |
|
|
92 | # Create the layout and underlying text widget. | |
|
93 | layout = QtGui.QStackedLayout(self) | |
|
69 | 94 | layout.setMargin(0) |
|
70 | 95 | self._control = self._create_control(kind) |
|
96 | self._page_control = None | |
|
97 | self._splitter = None | |
|
98 | if paging in ('hsplit', 'vsplit'): | |
|
99 | self._splitter = QtGui.QSplitter() | |
|
100 | if paging == 'hsplit': | |
|
101 | self._splitter.setOrientation(QtCore.Qt.Horizontal) | |
|
102 | else: | |
|
103 | self._splitter.setOrientation(QtCore.Qt.Vertical) | |
|
104 | self._splitter.addWidget(self._control) | |
|
105 | layout.addWidget(self._splitter) | |
|
106 | else: | |
|
71 | 107 | layout.addWidget(self._control) |
|
72 | 108 | |
|
109 | # Create the paging widget, if necessary. | |
|
110 | self._page_style = paging | |
|
111 | if paging in ('inside', 'hsplit', 'vsplit'): | |
|
112 | self._page_control = self._create_page_control() | |
|
113 | if self._splitter: | |
|
114 | self._page_control.hide() | |
|
115 | self._splitter.addWidget(self._page_control) | |
|
116 | else: | |
|
117 | layout.addWidget(self._page_control) | |
|
118 | elif paging not in ('custom', 'none'): | |
|
119 | raise ValueError('Paging style %s unknown.' % repr(paging)) | |
|
120 | ||
|
73 | 121 | # Initialize protected variables. Some variables contain useful state |
|
74 | 122 | # information for subclasses; they should be considered read-only. |
|
75 | 123 | self._ansi_processor = QtAnsiCodeProcessor() |
@@ -118,11 +166,39 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
118 | 166 | return True |
|
119 | 167 | |
|
120 | 168 | elif etype == QtCore.QEvent.KeyPress: |
|
121 | return self._event_filter_keypress(event) | |
|
169 | return self._event_filter_console_keypress(event) | |
|
170 | ||
|
171 | elif obj == self._page_control: | |
|
172 | if etype == QtCore.QEvent.KeyPress: | |
|
173 | return self._event_filter_page_keypress(event) | |
|
122 | 174 | |
|
123 | 175 | return super(ConsoleWidget, self).eventFilter(obj, event) |
|
124 | 176 | |
|
125 | 177 | #--------------------------------------------------------------------------- |
|
178 | # 'QWidget' interface | |
|
179 | #--------------------------------------------------------------------------- | |
|
180 | ||
|
181 | def sizeHint(self): | |
|
182 | """ Reimplemented to suggest a size that is 80 characters wide and | |
|
183 | 25 lines high. | |
|
184 | """ | |
|
185 | style = self.style() | |
|
186 | opt = QtGui.QStyleOptionHeader() | |
|
187 | font_metrics = QtGui.QFontMetrics(self.font) | |
|
188 | splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth, opt, self) | |
|
189 | ||
|
190 | width = font_metrics.width(' ') * 80 | |
|
191 | width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self) | |
|
192 | if self._page_style == 'hsplit': | |
|
193 | width = width * 2 + splitwidth | |
|
194 | ||
|
195 | height = font_metrics.height() * 25 | |
|
196 | if self._page_style == 'vsplit': | |
|
197 | height = height * 2 + splitwidth | |
|
198 | ||
|
199 | return QtCore.QSize(width, height) | |
|
200 | ||
|
201 | #--------------------------------------------------------------------------- | |
|
126 | 202 | # 'ConsoleWidget' public interface |
|
127 | 203 | #--------------------------------------------------------------------------- |
|
128 | 204 | |
@@ -410,14 +486,7 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
410 | 486 | ANSI codes if enabled. |
|
411 | 487 | """ |
|
412 | 488 | cursor = self._get_end_cursor() |
|
413 | cursor.beginEditBlock() | |
|
414 | if self.ansi_codes: | |
|
415 | for substring in self._ansi_processor.split_string(text): | |
|
416 | format = self._ansi_processor.get_format() | |
|
417 | cursor.insertText(substring, format) | |
|
418 | else: | |
|
419 | cursor.insertText(text) | |
|
420 | cursor.endEditBlock() | |
|
489 | self._insert_plain_text(cursor, text) | |
|
421 | 490 | |
|
422 | 491 | def _append_plain_text_keeping_prompt(self, text): |
|
423 | 492 | """ Writes 'text' after the current prompt, then restores the old prompt |
@@ -478,7 +547,16 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
478 | 547 | control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) |
|
479 | 548 | return control |
|
480 | 549 | |
|
481 | def _event_filter_keypress(self, event): | |
|
550 | def _create_page_control(self): | |
|
551 | """ Creates and connects the underlying paging widget. | |
|
552 | """ | |
|
553 | control = QtGui.QPlainTextEdit() | |
|
554 | control.installEventFilter(self) | |
|
555 | control.setReadOnly(True) | |
|
556 | control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) | |
|
557 | return control | |
|
558 | ||
|
559 | def _event_filter_console_keypress(self, event): | |
|
482 | 560 | """ Filter key events for the underlying text widget to create a |
|
483 | 561 | console-like interface. |
|
484 | 562 | """ |
@@ -612,6 +690,28 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
612 | 690 | |
|
613 | 691 | return intercepted |
|
614 | 692 | |
|
693 | def _event_filter_page_keypress(self, event): | |
|
694 | """ Filter key events for the paging widget to create console-like | |
|
695 | interface. | |
|
696 | """ | |
|
697 | key = event.key() | |
|
698 | ||
|
699 | if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape): | |
|
700 | if self._splitter: | |
|
701 | self._page_control.hide() | |
|
702 | else: | |
|
703 | self.layout().setCurrentWidget(self._control) | |
|
704 | return True | |
|
705 | ||
|
706 | elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): | |
|
707 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |
|
708 | QtCore.Qt.Key_Down, | |
|
709 | QtCore.Qt.NoModifier) | |
|
710 | QtGui.qApp.sendEvent(self._page_control, new_event) | |
|
711 | return True | |
|
712 | ||
|
713 | return False | |
|
714 | ||
|
615 | 715 | def _format_as_columns(self, items, separator=' '): |
|
616 | 716 | """ Transform a list of strings into a single string with columns. |
|
617 | 717 | |
@@ -786,6 +886,19 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
786 | 886 | cursor.insertText(' ', QtGui.QTextCharFormat()) |
|
787 | 887 | cursor.endEditBlock() |
|
788 | 888 | |
|
889 | def _insert_plain_text(self, cursor, text): | |
|
890 | """ Inserts plain text using the specified cursor, processing ANSI codes | |
|
891 | if enabled. | |
|
892 | """ | |
|
893 | cursor.beginEditBlock() | |
|
894 | if self.ansi_codes: | |
|
895 | for substring in self._ansi_processor.split_string(text): | |
|
896 | format = self._ansi_processor.get_format() | |
|
897 | cursor.insertText(substring, format) | |
|
898 | else: | |
|
899 | cursor.insertText(text) | |
|
900 | cursor.endEditBlock() | |
|
901 | ||
|
789 | 902 | def _insert_into_buffer(self, text): |
|
790 | 903 | """ Inserts text into the input buffer at the current cursor position, |
|
791 | 904 | ensuring that continuation prompts are inserted as necessary. |
@@ -832,6 +945,26 b' class ConsoleWidget(QtGui.QWidget):' | |||
|
832 | 945 | self._control.setTextCursor(cursor) |
|
833 | 946 | return True |
|
834 | 947 | |
|
948 | def _page(self, text): | |
|
949 | """ Displays text using the pager. | |
|
950 | """ | |
|
951 | if self._page_style == 'custom': | |
|
952 | self.custom_page_requested.emit(text) | |
|
953 | elif self._page_style == 'none': | |
|
954 | self._append_plain_text(text) | |
|
955 | else: | |
|
956 | self._page_control.clear() | |
|
957 | cursor = self._page_control.textCursor() | |
|
958 | self._insert_plain_text(cursor, text) | |
|
959 | self._page_control.moveCursor(QtGui.QTextCursor.Start) | |
|
960 | ||
|
961 | self._page_control.viewport().resize(self._control.size()) | |
|
962 | if self._splitter: | |
|
963 | self._page_control.show() | |
|
964 | self._page_control.setFocus() | |
|
965 | else: | |
|
966 | self.layout().setCurrentWidget(self._page_control) | |
|
967 | ||
|
835 | 968 | def _prompt_started(self): |
|
836 | 969 | """ Called immediately after a new prompt is displayed. |
|
837 | 970 | """ |
@@ -45,7 +45,6 b' def main():' | |||
|
45 | 45 | widget = IPythonWidget() |
|
46 | 46 | widget.kernel_manager = kernel_manager |
|
47 | 47 | widget.setWindowTitle('Python') |
|
48 | widget.resize(640, 480) | |
|
49 | 48 | widget.show() |
|
50 | 49 | app.exec_() |
|
51 | 50 |
@@ -16,13 +16,14 b' class RichIPythonWidget(IPythonWidget):' | |||
|
16 | 16 | _svg_text_format_property = 1 |
|
17 | 17 | |
|
18 | 18 | #--------------------------------------------------------------------------- |
|
19 |
# ' |
|
|
19 | # 'object' interface | |
|
20 | 20 | #--------------------------------------------------------------------------- |
|
21 | 21 | |
|
22 |
def __init__(self, |
|
|
22 | def __init__(self, *args, **kw): | |
|
23 | 23 | """ Create a RichIPythonWidget. |
|
24 | 24 | """ |
|
25 | super(RichIPythonWidget, self).__init__(kind='rich', parent=parent) | |
|
25 | kw['kind'] = 'rich' | |
|
26 | super(RichIPythonWidget, self).__init__(*args, **kw) | |
|
26 | 27 | |
|
27 | 28 | #--------------------------------------------------------------------------- |
|
28 | 29 | # 'ConsoleWidget' protected interface |
General Comments 0
You need to be logged in to leave comments.
Login now