diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 0e3b687..c214738 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -234,6 +234,19 @@ class ConsoleWidget(Configurable, QtGui.QWidget): # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- + def can_cut(self): + """ Returns whether text can be cut to the clipboard. + """ + cursor = self._control.textCursor() + return (cursor.hasSelection() and + self._in_buffer(cursor.anchor()) and + self._in_buffer(cursor.position())) + + def can_copy(self): + """ Returns whether text can be copied to the clipboard. + """ + return self._control.textCursor().hasSelection() + def can_paste(self): """ Returns whether text can be pasted from the clipboard. """ @@ -264,6 +277,14 @@ class ConsoleWidget(Configurable, QtGui.QWidget): """ self._control.copy() + def cut(self): + """ Copy the currently selected text to the clipboard and delete it + if it's inside the input buffer. + """ + self.copy() + if self.can_cut(): + self._control.textCursor().removeSelectedText() + def execute(self, source=None, hidden=False, interactive=False): """ Executes source or the input buffer, possibly prompting for more input. @@ -460,7 +481,6 @@ class ConsoleWidget(Configurable, QtGui.QWidget): # FIXME: remove Consolas as a default on Linux once our font # selections are configurable by the user. family, fallback = 'Consolas', 'Monospace' - font = get_font(family, fallback) font.setPointSize(QtGui.qApp.font().pointSize()) font.setStyleHint(QtGui.QFont.TypeWriter) @@ -640,6 +660,34 @@ class ConsoleWidget(Configurable, QtGui.QWidget): self._control.setTextCursor(cursor) self._text_completing_pos = current_pos + def _context_menu_make(self, pos): + """ Creates a context menu for the given QPoint (in widget coordinates). + """ + menu = QtGui.QMenu() + + cut_action = menu.addAction('Cut', self.cut) + cut_action.setEnabled(self.can_cut()) + cut_action.setShortcut(QtGui.QKeySequence.Cut) + + copy_action = menu.addAction('Copy', self.copy) + copy_action.setEnabled(self.can_copy()) + copy_action.setShortcut(QtGui.QKeySequence.Copy) + + paste_action = menu.addAction('Paste', self.paste) + paste_action.setEnabled(self.can_paste()) + paste_action.setShortcut(QtGui.QKeySequence.Paste) + + menu.addSeparator() + menu.addAction('Select All', self.select_all) + + return menu + + def _context_menu_show(self, pos): + """ Shows a context menu at the given QPoint (in widget coordinates). + """ + menu = self._context_menu_make(pos) + menu.exec_(self._control.mapToGlobal(pos)) + def _control_key_down(self, modifiers, include_command=True): """ Given a KeyboardModifiers flags object, return whether the Control key is down. @@ -675,7 +723,7 @@ class ConsoleWidget(Configurable, QtGui.QWidget): # Connect signals. control.cursorPositionChanged.connect(self._cursor_position_changed) - control.customContextMenuRequested.connect(self._show_context_menu) + control.customContextMenuRequested.connect(self._context_menu_show) control.copyAvailable.connect(self.copy_available) control.redoAvailable.connect(self.redo_available) control.undoAvailable.connect(self.undo_available) @@ -715,6 +763,10 @@ class ConsoleWidget(Configurable, QtGui.QWidget): self.copy() intercepted = True + elif event.matches(QtGui.QKeySequence.Cut): + self.cut() + intercepted = True + elif event.matches(QtGui.QKeySequence.Paste): self.paste() intercepted = True @@ -793,11 +845,6 @@ class ConsoleWidget(Configurable, QtGui.QWidget): self._page_control.setFocus() intercept = True - elif key == QtCore.Qt.Key_X: - # FIXME: Instead of disabling cut completely, only allow it - # when safe. - intercepted = True - elif key == QtCore.Qt.Key_Y: self.paste() intercepted = True @@ -1393,24 +1440,6 @@ class ConsoleWidget(Configurable, QtGui.QWidget): """ self._control.setTextCursor(cursor) - def _show_context_menu(self, pos): - """ Shows a context menu at the given QPoint (in widget coordinates). - """ - menu = QtGui.QMenu() - - copy_action = menu.addAction('Copy', self.copy) - copy_action.setEnabled(self._get_cursor().hasSelection()) - copy_action.setShortcut(QtGui.QKeySequence.Copy) - - paste_action = menu.addAction('Paste', self.paste) - paste_action.setEnabled(self.can_paste()) - paste_action.setShortcut(QtGui.QKeySequence.Paste) - - menu.addSeparator() - menu.addAction('Select All', self.select_all) - - menu.exec_(self._control.mapToGlobal(pos)) - def _show_prompt(self, prompt=None, html=False, newline=True): """ Writes a new prompt at the end of the buffer. diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index f52df3a..3f7b70a 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -111,6 +111,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self._bracket_matcher = BracketMatcher(self._control) self._call_tip_widget = CallTipWidget(self._control) self._completion_lexer = CompletionLexer(PythonLexer()) + self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self) self._input_splitter = self._input_splitter_class(input_mode='block') @@ -122,6 +123,16 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self.tab_width = 4 self._set_continuation_prompt('... ') + # Configure actions. + action = self._copy_raw_action + key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C + action.setEnabled(False) + action.setShortcut(QtGui.QKeySequence(key)) + action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + action.triggered.connect(self.copy_raw) + self.copy_available.connect(action.setEnabled) + self.addAction(action) + # Connect signal handlers. document = self._control.document() document.contentsChange.connect(self._document_contents_change) @@ -196,6 +207,17 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- + def _context_menu_make(self, pos): + """ Reimplemented to add an action for raw copy. + """ + menu = super(FrontendWidget, self)._context_menu_make(pos) + for before_action in menu.actions(): + if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \ + QtGui.QKeySequence.ExactMatch: + menu.insertAction(before_action, self._copy_raw_action) + break + return menu + def _event_filter_console_keypress(self, event): """ Reimplemented to allow execution interruption. """ @@ -324,6 +346,12 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # 'FrontendWidget' public interface #--------------------------------------------------------------------------- + def copy_raw(self): + """ Copy the currently selected text to the clipboard without attempting + to remove prompts or otherwise alter the text. + """ + self._control.copy() + def execute_file(self, path, hidden=False): """ Attempts to execute file with 'path'. If 'hidden', no output is shown. diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py index a22f314..5a74eb7 100644 --- a/IPython/frontend/qt/console/rich_ipython_widget.py +++ b/IPython/frontend/qt/console/rich_ipython_widget.py @@ -30,13 +30,13 @@ class RichIPythonWidget(IPythonWidget): # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- - def _show_context_menu(self, pos): - """ Reimplemented to show a custom context menu for images. + def _context_menu_make(self, pos): + """ Reimplemented to return a custom context menu for images. """ format = self._control.cursorForPosition(pos).charFormat() name = format.stringProperty(QtGui.QTextFormat.ImageName) if name.isEmpty(): - super(RichIPythonWidget, self)._show_context_menu(pos) + menu = super(RichIPythonWidget, self)._context_menu_make(pos) else: menu = QtGui.QMenu() @@ -50,8 +50,7 @@ class RichIPythonWidget(IPythonWidget): menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg)) menu.addAction('Save SVG As...', lambda: save_svg(svg, self._control)) - - menu.exec_(self._control.mapToGlobal(pos)) + return menu #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface diff --git a/IPython/frontend/qt/util.py b/IPython/frontend/qt/util.py index 1191d29..dd8205a 100644 --- a/IPython/frontend/qt/util.py +++ b/IPython/frontend/qt/util.py @@ -16,14 +16,16 @@ MetaQObject = type(QtCore.QObject) # You can switch the order of the parents here and it doesn't seem to matter. class MetaQObjectHasTraits(MetaQObject, MetaHasTraits): - """ A metaclass that inherits from the metaclasses of both HasTraits and - QObject. + """ A metaclass that inherits from the metaclasses of HasTraits and QObject. - Using this metaclass allows a class to inherit from both HasTraits and - QObject. See QtKernelManager for an example. + Using this metaclass allows a class to inherit from both HasTraits and + QObject. See QtKernelManager for an example. """ pass +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- def get_font(family, fallback=None): """Return a font of the requested family, using fallback as alternative.