Last active
January 5, 2024 07:39
-
-
Save promto-c/9db1247e12a6a58521cf83290e0418a8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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