Skip to content

Instantly share code, notes, and snippets.

@promto-c
Last active January 5, 2024 07:39
Show Gist options
  • Save promto-c/9db1247e12a6a58521cf83290e0418a8 to your computer and use it in GitHub Desktop.
Save promto-c/9db1247e12a6a58521cf83290e0418a8 to your computer and use it in GitHub Desktop.
from typing import List
from PyQt5 import QtCore, QtGui, QtWidgets
import re, time # Regular expression module for splitting text
class TagLabel(QtWidgets.QLabel):
removed = QtCore.pyqtSignal(str)
def __init__(self, text, parent=None):
super(TagLabel, self).__init__(text, parent)
self.setStyleSheet("background-color: lightblue; border-radius: 5px; padding: 2px 5px;")
self.setCursor(QtCore.Qt.PointingHandCursor)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.removed.emit(self.text())
else:
self.parent().mousePressEvent(event) # Forward event to parent
def mouseMoveEvent(self, event):
self.parent().mouseMoveEvent(event) # Forward event to parent
def mouseReleaseEvent(self, event):
self.parent().mouseReleaseEvent(event) # Forward event to parent
class TagWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TagWidget, self).__init__(parent)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.tagLayout = QtWidgets.QHBoxLayout()
self.tagLayout.addStretch()
self.mainLayout.addLayout(self.tagLayout)
self.tags = set()
def addTag(self, text):
if text not in self.tags:
self.tags.add(text)
tagLabel = TagLabel(text)
tagLabel.removed.connect(self.removeTag)
self.tagLayout.insertWidget(self.tagLayout.count() - 1, tagLabel)
self.adjustTagLayout()
def removeTag(self, text):
if text in self.tags:
self.tags.remove(text)
for i in reversed(range(self.tagLayout.count())):
widget = self.tagLayout.itemAt(i).widget()
if widget is not None and isinstance(widget, TagLabel) and widget.text() == text:
widget.deleteLater()
self.adjustTagLayout()
def adjustTagLayout(self):
# Adjust the layout to reflow the tags
pass
def resizeEvent(self, event):
super(TagWidget, self).resizeEvent(event)
self.adjustTagLayout()
class ScrollableTagWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.tag_widget = TagWidget(self) # Assuming TagWidget is previously defined
# Replace QScrollArea with InertiaScrollArea
self.scrollArea = InertiaScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.tag_widget)
self.scrollArea.setFixedHeight(24)
self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.addWidget(self.scrollArea)
class InertiaScrollArea(QtWidgets.QScrollArea):
def __init__(self, parent=None, max_velocity=15, deceleration_rate=0.9, timer_interval=10):
super(InertiaScrollArea, self).__init__(parent)
self.maxVelocity = max_velocity
self.decelerationRate = deceleration_rate
self.timerInterval = timer_interval
self.velocity = 0
self.lastTime = 0
self.dragging = False
self.inertiaTimer = QtCore.QTimer()
self.inertiaTimer.timeout.connect(self.handleInertia)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.dragging = True
self.lastPos = event.pos()
self.lastTime = time.time()
self.velocity = 0
self.setCursor(QtCore.Qt.ClosedHandCursor)
event.accept()
def mouseMoveEvent(self, event):
if self.dragging:
currentTime = time.time()
newPos = event.pos()
delta = newPos - self.lastPos
deltaTime = currentTime - self.lastTime
if deltaTime > 0:
newVelocity = delta.x() / deltaTime
self.velocity = max(min(newVelocity, self.maxVelocity), -self.maxVelocity)
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - delta.x())
self.lastPos = newPos
self.lastTime = currentTime
event.accept()
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.dragging = False
self.setCursor(QtCore.Qt.ArrowCursor)
self.inertiaTimer.start(self.timerInterval)
def handleInertia(self):
self.velocity *= self.decelerationRate
if abs(self.velocity) < 0.5:
self.inertiaTimer.stop()
return
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - int(self.velocity))
if self.horizontalScrollBar().value() == self.horizontalScrollBar().maximum() or \
self.horizontalScrollBar().value() == self.horizontalScrollBar().minimum():
self.inertiaTimer.stop()
class TagLineEdit(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TagLineEdit, self).__init__(parent)
self.line_edit = QtWidgets.QLineEdit(self)
self.tag_widget = ScrollableTagWidget(self)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.tag_widget)
self.mainLayout.addWidget(self.line_edit)
self.line_edit.editingFinished.connect(self.add_tags)
# Completer setup
self.completer = QtWidgets.QCompleter(self)
self.line_edit.setCompleter(self.completer)
# Connect completer's activated signal to add tags
self.completer.activated.connect(self.add_tags)
def set_completer_items(self, items):
model = QtCore.QStringListModel(items)
self.completer.setModel(model)
def add_tags(self):
text = self.line_edit.text()
tags = [t.strip() for t in re.split('[\t\n,]+', text) if t.strip()]
for tag in tags:
self.tag_widget.tag_widget.addTag(tag)
self.line_edit.clear()
def clearLineEdit(self):
# Clear the line edit when a tag is selected from the completer
self.line_edit.clear()
def extract_all_items_from_tree(tree_widget: QtWidgets.QTreeWidget) -> List[QtWidgets.QTreeWidgetItem]:
"""This function returns all the items in the tree widget as a list.
The items are sorted based on their order in the tree structure,
with children appearing after their parent items for each grouping.
Returns:
List[TreeWidgetItem]: A list containing all the items in the tree widget.
"""
def traverse_items(item: QtWidgets.QTreeWidgetItem):
# Recursively traverse the children of the current item
for child_index in range(item.childCount()):
# Get the child item at the current index
child = item.child(child_index)
# Add the current child item to the list
items.append(child)
# Recursively traverse the children of the current child item
traverse_items(child)
# Get the root item of the tree widget
root = tree_widget.invisibleRootItem()
# Traverse the items in a depth-first manner and collect them in a list
items = list()
traverse_items(root)
# Return the list of items
return items
app = QtWidgets.QApplication([])
# Example QTreeWidget setup
tree_widget = QtWidgets.QTreeWidget()
tree_widget.setHeaderLabel("Tree Items")
tree_widget.addTopLevelItem(QtWidgets.QTreeWidgetItem(["100"]))
tree_widget.topLevelItem(0).addChildren([QtWidgets.QTreeWidgetItem(["100_010_001"]),
QtWidgets.QTreeWidgetItem(["100_020_050"])])
tree_widget.addTopLevelItem(QtWidgets.QTreeWidgetItem(["101"]))
tree_widget.topLevelItem(1).addChildren([QtWidgets.QTreeWidgetItem(["101_022_232"]),
QtWidgets.QTreeWidgetItem(["101_023_200"])])
# Main window setup
window = TagLineEdit()
text_items = [item.text(0) for item in extract_all_items_from_tree(tree_widget)]
window.set_completer_items(text_items)
window.show()
app.exec_()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment