123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- """A dropdown completer widget for the qtconsole."""
- import os
- import sys
- from qtpy import QtCore, QtGui, QtWidgets
- class CompletionWidget(QtWidgets.QListWidget):
- """ A widget for GUI tab completion.
- """
- #--------------------------------------------------------------------------
- # 'QObject' interface
- #--------------------------------------------------------------------------
- def __init__(self, console_widget):
- """ Create a completion widget that is attached to the specified Qt
- text edit widget.
- """
- text_edit = console_widget._control
- assert isinstance(text_edit, (QtWidgets.QTextEdit, QtWidgets.QPlainTextEdit))
- super(CompletionWidget, self).__init__(parent=console_widget)
- self._text_edit = text_edit
- self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
- self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
- self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
- # We need Popup style to ensure correct mouse interaction
- # (dialog would dissappear on mouse click with ToolTip style)
- self.setWindowFlags(QtCore.Qt.Popup)
- self.setAttribute(QtCore.Qt.WA_StaticContents)
- original_policy = text_edit.focusPolicy()
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- text_edit.setFocusPolicy(original_policy)
- # Ensure that the text edit keeps focus when widget is displayed.
- self.setFocusProxy(self._text_edit)
- self.setFrameShadow(QtWidgets.QFrame.Plain)
- self.setFrameShape(QtWidgets.QFrame.StyledPanel)
- self.itemActivated.connect(self._complete_current)
- def eventFilter(self, obj, event):
- """ Reimplemented to handle mouse input and to auto-hide when the
- text edit loses focus.
- """
- if obj is self:
- if event.type() == QtCore.QEvent.MouseButtonPress:
- pos = self.mapToGlobal(event.pos())
- target = QtWidgets.QApplication.widgetAt(pos)
- if (target and self.isAncestorOf(target) or target is self):
- return False
- else:
- self.cancel_completion()
- return super(CompletionWidget, self).eventFilter(obj, event)
- def keyPressEvent(self, event):
- key = event.key()
- if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter,
- QtCore.Qt.Key_Tab):
- self._complete_current()
- elif key == QtCore.Qt.Key_Escape:
- self.hide()
- elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
- QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown,
- QtCore.Qt.Key_Home, QtCore.Qt.Key_End):
- return super(CompletionWidget, self).keyPressEvent(event)
- else:
- QtWidgets.QApplication.sendEvent(self._text_edit, event)
- #--------------------------------------------------------------------------
- # 'QWidget' interface
- #--------------------------------------------------------------------------
- def hideEvent(self, event):
- """ Reimplemented to disconnect signal handlers and event filter.
- """
- super(CompletionWidget, self).hideEvent(event)
- try:
- self._text_edit.cursorPositionChanged.disconnect(self._update_current)
- except TypeError:
- pass
- self.removeEventFilter(self)
- def showEvent(self, event):
- """ Reimplemented to connect signal handlers and event filter.
- """
- super(CompletionWidget, self).showEvent(event)
- self._text_edit.cursorPositionChanged.connect(self._update_current)
- self.installEventFilter(self)
- #--------------------------------------------------------------------------
- # 'CompletionWidget' interface
- #--------------------------------------------------------------------------
- def show_items(self, cursor, items, prefix_length=0):
- """ Shows the completion widget with 'items' at the position specified
- by 'cursor'.
- """
- text_edit = self._text_edit
- point = self._get_top_left_position(cursor)
- self.clear()
- path_items = []
- for item in items:
- # Check if the item could refer to a file or dir. The replacing
- # of '"' is needed for items on Windows
- if (os.path.isfile(os.path.abspath(item.replace("\"", ""))) or
- os.path.isdir(os.path.abspath(item.replace("\"", "")))):
- path_items.append(item.replace("\"", ""))
- else:
- list_item = QtWidgets.QListWidgetItem()
- list_item.setData(QtCore.Qt.UserRole, item)
- # Need to split to only show last element of a dot completion
- list_item.setText(item.split(".")[-1])
- self.addItem(list_item)
- common_prefix = os.path.dirname(os.path.commonprefix(path_items))
- for path_item in path_items:
- list_item = QtWidgets.QListWidgetItem()
- list_item.setData(QtCore.Qt.UserRole, path_item)
- if common_prefix:
- text = path_item.split(common_prefix)[-1]
- else:
- text = path_item
- list_item.setText(text)
- self.addItem(list_item)
- height = self.sizeHint().height()
- screen_rect = QtWidgets.QApplication.desktop().availableGeometry(self)
- if (screen_rect.size().height() + screen_rect.y() -
- point.y() - height < 0):
- point = text_edit.mapToGlobal(text_edit.cursorRect().topRight())
- point.setY(point.y() - height)
- w = (self.sizeHintForColumn(0) +
- self.verticalScrollBar().sizeHint().width() +
- 2 * self.frameWidth())
- self.setGeometry(point.x(), point.y(), w, height)
- # Move cursor to start of the prefix to replace it
- # when a item is selected
- cursor.movePosition(QtGui.QTextCursor.Left, n=prefix_length)
- self._start_position = cursor.position()
- self.setCurrentRow(0)
- self.raise_()
- self.show()
- #--------------------------------------------------------------------------
- # Protected interface
- #--------------------------------------------------------------------------
- def _get_top_left_position(self, cursor):
- """ Get top left position for this widget.
- """
- point = self._text_edit.cursorRect(cursor).center()
- point_size = self._text_edit.font().pointSize()
- if sys.platform == 'darwin':
- delta = int((point_size * 1.20) ** 0.98)
- elif os.name == 'nt':
- delta = int((point_size * 1.20) ** 1.05)
- else:
- delta = int((point_size * 1.20) ** 0.98)
- y = delta - (point_size / 2)
- point.setY(point.y() + y)
- point = self._text_edit.mapToGlobal(point)
- return point
- def _complete_current(self):
- """ Perform the completion with the currently selected item.
- """
- text = self.currentItem().data(QtCore.Qt.UserRole)
- self._current_text_cursor().insertText(text)
- self.hide()
- def _current_text_cursor(self):
- """ Returns a cursor with text between the start position and the
- current position selected.
- """
- cursor = self._text_edit.textCursor()
- if cursor.position() >= self._start_position:
- cursor.setPosition(self._start_position,
- QtGui.QTextCursor.KeepAnchor)
- return cursor
- def _update_current(self):
- """ Updates the current item based on the current text and the
- position of the widget.
- """
- # Update widget position
- cursor = self._text_edit.textCursor()
- point = self._get_top_left_position(cursor)
- self.move(point)
- # Update current item
- prefix = self._current_text_cursor().selection().toPlainText()
- if prefix:
- items = self.findItems(prefix, (QtCore.Qt.MatchStartsWith |
- QtCore.Qt.MatchCaseSensitive))
- if items:
- self.setCurrentItem(items[0])
- else:
- self.hide()
- else:
- self.hide()
- def cancel_completion(self):
- self.hide()
|