Skip to content

Commit

Permalink
Create widget to manage list history
Browse files Browse the repository at this point in the history
  • Loading branch information
jsbautista committed Oct 1, 2024
1 parent a72387f commit 206b4eb
Showing 1 changed file with 207 additions and 0 deletions.
207 changes: 207 additions & 0 deletions qtconsole/history_list_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"""A dropdown completer widget for the qtconsole."""

import os
import sys

from qtpy import QT6
from qtpy import QtCore, QtGui, QtWidgets


class HistoryListWidget(QtWidgets.QListWidget):
""" A widget for GUI list history.
"""

#--------------------------------------------------------------------------
# 'QObject' interface
#--------------------------------------------------------------------------

def __init__(self, console_widget, height=0):
""" Create a history 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().__init__(parent=console_widget)

self._text_edit = text_edit
self._height_max = height if height > 0 else self.sizeHint().height()
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().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().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().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().showEvent(event)
self._text_edit.cursorPositionChanged.connect(self._update_current)
self.installEventFilter(self)

#--------------------------------------------------------------------------
# 'HistoryListWidget' interface
#--------------------------------------------------------------------------
"""
def show_items(self, cursor, items, prefix_length=0):
""" Shows the completion widget with 'items' at the position specified
by 'cursor'.
"""
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)

if QT6:
screen_rect = self.screen().availableGeometry()
else:
screen_rect = QtWidgets.QApplication.desktop().availableGeometry(self)
screen_height = screen_rect.height()
height = int(min(self._height_max, screen_height - 50)) # -50px
if ((screen_height - point.y() - height) < 0):
point = self._text_edit.mapToGlobal(self._text_edit.cursorRect().topRight())
py = point.y()
point.setY(int(py - min(height, py - 10))) # -10px
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.
"""
return self._text_edit.mapToGlobal(self._text_edit.cursorRect().bottomRight())

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)
point.setY(self.y())
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()

0 comments on commit 206b4eb

Please sign in to comment.