Show More
@@ -11,9 +11,17 b' from completion_widget import CompletionWidget' | |||||
11 |
|
11 | |||
12 |
|
12 | |||
13 | class ConsoleWidget(QtGui.QWidget): |
|
13 | class ConsoleWidget(QtGui.QWidget): | |
14 |
""" |
|
14 | """ An abstract base class for console-type widgets. This class has | |
15 | dealing with the prompt, keeping the cursor inside the editing line, and |
|
15 | functionality for: | |
16 | handling ANSI escape sequences. |
|
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 | # Whether to process ANSI escape codes. |
|
27 | # Whether to process ANSI escape codes. | |
@@ -35,6 +43,10 b' class ConsoleWidget(QtGui.QWidget):' | |||||
35 | redo_available = QtCore.pyqtSignal(bool) |
|
43 | redo_available = QtCore.pyqtSignal(bool) | |
36 | undo_available = QtCore.pyqtSignal(bool) |
|
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 | # Protected class variables. |
|
50 | # Protected class variables. | |
39 | _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left, |
|
51 | _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left, | |
40 | QtCore.Qt.Key_F : QtCore.Qt.Key_Right, |
|
52 | QtCore.Qt.Key_F : QtCore.Qt.Key_Right, | |
@@ -50,25 +62,61 b' class ConsoleWidget(QtGui.QWidget):' | |||||
50 | # 'QObject' interface |
|
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 | """ Create a ConsoleWidget. |
|
66 | """ Create a ConsoleWidget. | |
55 |
|
67 | |||
56 | Parameters |
|
68 | Parameters | |
57 | ---------- |
|
69 | ---------- | |
58 | kind : str, optional [default 'plain'] |
|
70 | kind : str, optional [default 'plain'] | |
59 |
The type of text widget to use. Valid values are 'plain', |
|
71 | The type of underlying text widget to use. Valid values are 'plain', | |
60 |
specifies a QPlainTextEdit, and 'rich', which specifies a |
|
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 | parent : QWidget, optional [default None] |
|
87 | parent : QWidget, optional [default None] | |
63 | The parent for this widget. |
|
88 | The parent for this widget. | |
64 | """ |
|
89 | """ | |
65 | super(ConsoleWidget, self).__init__(parent) |
|
90 | super(ConsoleWidget, self).__init__(parent) | |
66 |
|
91 | |||
67 |
# Create and |
|
92 | # Create the layout and underlying text widget. | |
68 |
layout = QtGui.Q |
|
93 | layout = QtGui.QStackedLayout(self) | |
69 | layout.setMargin(0) |
|
94 | layout.setMargin(0) | |
70 | self._control = self._create_control(kind) |
|
95 | self._control = self._create_control(kind) | |
71 | layout.addWidget(self._control) |
|
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: | |||
|
107 | layout.addWidget(self._control) | |||
|
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)) | |||
72 |
|
120 | |||
73 | # Initialize protected variables. Some variables contain useful state |
|
121 | # Initialize protected variables. Some variables contain useful state | |
74 | # information for subclasses; they should be considered read-only. |
|
122 | # information for subclasses; they should be considered read-only. | |
@@ -118,11 +166,39 b' class ConsoleWidget(QtGui.QWidget):' | |||||
118 | return True |
|
166 | return True | |
119 |
|
167 | |||
120 | elif etype == QtCore.QEvent.KeyPress: |
|
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 | return super(ConsoleWidget, self).eventFilter(obj, event) |
|
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 | # 'ConsoleWidget' public interface |
|
202 | # 'ConsoleWidget' public interface | |
127 | #--------------------------------------------------------------------------- |
|
203 | #--------------------------------------------------------------------------- | |
128 |
|
204 | |||
@@ -410,14 +486,7 b' class ConsoleWidget(QtGui.QWidget):' | |||||
410 | ANSI codes if enabled. |
|
486 | ANSI codes if enabled. | |
411 | """ |
|
487 | """ | |
412 | cursor = self._get_end_cursor() |
|
488 | cursor = self._get_end_cursor() | |
413 | cursor.beginEditBlock() |
|
489 | self._insert_plain_text(cursor, text) | |
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() |
|
|||
421 |
|
490 | |||
422 | def _append_plain_text_keeping_prompt(self, text): |
|
491 | def _append_plain_text_keeping_prompt(self, text): | |
423 | """ Writes 'text' after the current prompt, then restores the old prompt |
|
492 | """ Writes 'text' after the current prompt, then restores the old prompt | |
@@ -478,7 +547,16 b' class ConsoleWidget(QtGui.QWidget):' | |||||
478 | control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) |
|
547 | control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) | |
479 | return control |
|
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 | """ Filter key events for the underlying text widget to create a |
|
560 | """ Filter key events for the underlying text widget to create a | |
483 | console-like interface. |
|
561 | console-like interface. | |
484 | """ |
|
562 | """ | |
@@ -612,6 +690,28 b' class ConsoleWidget(QtGui.QWidget):' | |||||
612 |
|
690 | |||
613 | return intercepted |
|
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 | def _format_as_columns(self, items, separator=' '): |
|
715 | def _format_as_columns(self, items, separator=' '): | |
616 | """ Transform a list of strings into a single string with columns. |
|
716 | """ Transform a list of strings into a single string with columns. | |
617 |
|
717 | |||
@@ -786,6 +886,19 b' class ConsoleWidget(QtGui.QWidget):' | |||||
786 | cursor.insertText(' ', QtGui.QTextCharFormat()) |
|
886 | cursor.insertText(' ', QtGui.QTextCharFormat()) | |
787 | cursor.endEditBlock() |
|
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 | def _insert_into_buffer(self, text): |
|
902 | def _insert_into_buffer(self, text): | |
790 | """ Inserts text into the input buffer at the current cursor position, |
|
903 | """ Inserts text into the input buffer at the current cursor position, | |
791 | ensuring that continuation prompts are inserted as necessary. |
|
904 | ensuring that continuation prompts are inserted as necessary. | |
@@ -831,6 +944,26 b' class ConsoleWidget(QtGui.QWidget):' | |||||
831 | cursor.movePosition(QtGui.QTextCursor.End) |
|
944 | cursor.movePosition(QtGui.QTextCursor.End) | |
832 | self._control.setTextCursor(cursor) |
|
945 | self._control.setTextCursor(cursor) | |
833 | return True |
|
946 | return True | |
|
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) | |||
834 |
|
967 | |||
835 | def _prompt_started(self): |
|
968 | def _prompt_started(self): | |
836 | """ Called immediately after a new prompt is displayed. |
|
969 | """ Called immediately after a new prompt is displayed. |
@@ -45,7 +45,6 b' def main():' | |||||
45 | widget = IPythonWidget() |
|
45 | widget = IPythonWidget() | |
46 | widget.kernel_manager = kernel_manager |
|
46 | widget.kernel_manager = kernel_manager | |
47 | widget.setWindowTitle('Python') |
|
47 | widget.setWindowTitle('Python') | |
48 | widget.resize(640, 480) |
|
|||
49 | widget.show() |
|
48 | widget.show() | |
50 | app.exec_() |
|
49 | app.exec_() | |
51 |
|
50 |
@@ -16,13 +16,14 b' class RichIPythonWidget(IPythonWidget):' | |||||
16 | _svg_text_format_property = 1 |
|
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 | """ Create a RichIPythonWidget. |
|
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 | # 'ConsoleWidget' protected interface |
|
29 | # 'ConsoleWidget' protected interface |
General Comments 0
You need to be logged in to leave comments.
Login now