Skip to content

Instantly share code, notes, and snippets.

@JokerMartini
Last active April 27, 2021 15:17
Show Gist options
  • Save JokerMartini/4a78b3c5db1dff8b7ed8 to your computer and use it in GitHub Desktop.
Save JokerMartini/4a78b3c5db1dff8b7ed8 to your computer and use it in GitHub Desktop.
PySide + Python: Demonstrates a way to handle a hierarchy of nodes along with instancing.
# Imports
# ------------------------------------------------------------------------------
import sys
import uuid
import json
from random import randint
from PySide import QtCore, QtGui
NODES = []
HIERARCHY = {}
INSTANCED_NODES = []
FONTS = None
# Fonts
# ------------------------------------------------------------------------------
class Fonts(object):
def __init__(self):
self._fonts = {}
def add_font(self, name="", bold=False, italic=False):
font = QtGui.QFont()
font.setBold(bold)
font.setItalic(italic)
self._fonts.update({name: font})
def get_font(self, name):
return self._fonts.get(name)
# Class Object
# ------------------------------------------------------------------------------
class Person():
def __init__(self, name="", age=0):
self.name = name
self.uid = str( uuid.uuid4( ) )
self.age = (randint(0,20))
NODES.append( self )
# Custom QTreeWidgetItem
# ------------------------------------------------------------------------------
class CustomTreeNode( QtGui.QTreeWidgetItem ):
def __init__( self, parent, person ):
super( CustomTreeNode, self ).__init__( parent )
self.person = person
@property
def person(self):
return self._person
@person.setter
def person(self, value):
self._person = value
self.setText( 0, self.person.name )
def update(self):
if self.person:
self.setText(0, self.person.name)
# Custom QTreeWidgetItem
# ------------------------------------------------------------------------------
class CustomTreeWidget( QtGui.QTreeWidget ):
def __init__(self, parent=None):
QtGui.QTreeWidget.__init__(self, parent)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setItemsExpandable(True)
self.setAnimated(True)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.setAlternatingRowColors(True)
self._hierarchy = {}
self._instances = {}
# signals
self.itemExpanded.connect(self.on_item_expanded)
self.itemCollapsed.connect(self.on_item_collapsed)
self.itemSelectionChanged.connect(self.on_selection_changed)
def dropEvent(self, event):
results = super( CustomTreeWidget, self ).dropEvent( event )
print "Get Hierarhcy"
self.get_node_hierarchy()
return results
def set_headers(self, headers=[]):
self.setColumnCount( len(headers) )
self.setHeaderLabels( headers )
def keyPressEvent(self, event):
if (event.key() == QtCore.Qt.Key_Escape and
event.modifiers() == QtCore.Qt.NoModifier):
self.clearSelection()
else:
QtGui.QTreeWidget.keyPressEvent(self, event)
def mousePressEvent(self, event):
item = self.itemAt(event.pos())
if item is None:
self.clearSelection()
QtGui.QTreeWidget.mousePressEvent(self, event)
def on_item_expanded(self):
key_mod = QtGui.QApplication.keyboardModifiers()
if key_mod == QtCore.Qt.ControlModifier:
self.expandAll()
def on_item_collapsed(self):
key_mod = QtGui.QApplication.keyboardModifiers()
if key_mod == QtCore.Qt.ControlModifier:
self.collapseAll()
def get_tree_nodes(self, root=None):
results = []
for i in range( root.childCount() ):
item = root.child(i)
results.append( item )
results.extend ( self.get_tree_nodes(root=item) )
return results
def get_instances(self):
nodes = self.get_tree_nodes( root=self.invisibleRootItem() )
self._instances = {}
for n in nodes:
if n.person.uid not in self._instances.keys():
self._instances.update( {n.person.uid : [n]} )
else:
self._instances[n.person.uid].append( n )
def accent_instances(self):
print "highilight"
self.get_instances()
# reset accent highlighting
for key, values in self._instances.items():
for item in values:
item.setFont(0, FONTS.get_font("regular"))
# bold instances
uids = [x.person.uid for x in self.selectedItems()]
for uid in uids:
items = self._instances.get(uid, [])
if len(items) > 1:
for item in items:
item.setFont(0, FONTS.get_font("accent"))
def on_selection_changed(self):
print "selection changed?"
self.accent_instances()
def build_node_hierarchy(self, root=None):
results = []
for i in range( root.childCount() ):
item = root.child(i)
data = {}
data["node"] = item.person.uid
data["children"] = [self.build_node_hierarchy(root=item)]
results.append( data )
return results
def get_node_hierarchy(self):
global HIERARCHY
self._hierarchy = self.build_node_hierarchy( root=self.invisibleRootItem() )
HIERARCHY = self._hierarchy
return self._hierarchy
# UI
# ------------------------------------------------------------------------------
class ExampleWidget(QtGui.QWidget):
def __init__( self, parent=None ):
super(ExampleWidget, self).__init__()
self.init_fonts()
self.initUI()
def init_fonts(self):
global FONTS
FONTS = Fonts()
FONTS.add_font(name="accent", bold=True, italic=True)
FONTS.add_font(name="regular", bold=False, italic=False)
def initUI(self):
# widgets
self.treewidget = CustomTreeWidget()
self.treewidget.set_headers( ["items"] )
self.btn_add_tree_nodes = QtGui.QPushButton("Add")
self.btn_instance_tree_nodes = QtGui.QPushButton("Instance")
self.btn_delete_tree_nodes = QtGui.QPushButton("Delete")
# layout
self.mainLayout = QtGui.QGridLayout(self)
self.mainLayout.addWidget(self.btn_add_tree_nodes, 0,0)
self.mainLayout.addWidget(self.btn_instance_tree_nodes, 0,1)
self.mainLayout.addWidget(self.btn_delete_tree_nodes, 0,2)
self.mainLayout.addWidget(self.treewidget, 1,0,1,3)
# signals
self.btn_add_tree_nodes.clicked.connect(self.add_tree_nodes_clicked)
self.btn_delete_tree_nodes.clicked.connect(self.delete_tree_nodes_clicked)
self.btn_instance_tree_nodes.clicked.connect(self.instance_tree_nodes_clicked)
self.treewidget.itemDoubleClicked.connect(self.item_doubleclicked)
# self.treewidget.itemSelectionChanged.connect(self.item_selection_changed)
# display
self.resize(200, 400)
self.show()
self.center_window(self, True)
# Functions
# --------------------------------------------------------------------------
def closeEvent(self, event):
print "saving"
self.save_nodes()
def showEvent(self, event):
print "loading"
def serialize_node(self, node):
data = {}
if node:
# base attributes
data["class_name"] = node.__class__.__name__
data["name"] = node.name
data["age"] = node.age
data["uid"] = node.uid
return data
def save_nodes(self):
print "Saving serialized nodes..."
data = {}
items = [self.serialize_node(x) for x in NODES]
data.update( {"nodes" : items} )
data.update( {"hierarchy" : HIERARCHY} )
json.dump(data, open("test.json",'w'), indent=4)
def add_tree_nodes_clicked(self):
roots = [self.treewidget] if self.treewidget.selectedItems() == [] else self.treewidget.selectedItems()
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
if ok:
print "Created new!"
for root in roots:
person = Person( text )
node = CustomTreeNode( root, person )
node.setExpanded(True)
self.treewidget.get_node_hierarchy()
self.treewidget.itemSelectionChanged.emit()
def instance_tree_nodes_clicked(self):
print "Instanced!"
roots = self.treewidget.selectedItems()
for root in roots:
parent = self.treewidget if not root.parent() else root.parent()
node = CustomTreeNode( parent, root.person )
node.setExpanded(True)
self.treewidget.get_node_hierarchy()
self.treewidget.itemSelectionChanged.emit()
def delete_tree_nodes_clicked(self):
global NODES
root = self.treewidget.invisibleRootItem()
# delete treewidget items from gui
for item in self.treewidget.selectedItems():
(item.parent() or root).removeChild(item)
# collect all uids used in GUI
uids = [x.person.uid for x in self.treewidget.get_tree_nodes( root=self.treewidget.invisibleRootItem() )]
for n in reversed(NODES):
if n.uid not in uids:
NODES.remove(n)
self.treewidget.get_node_hierarchy()
def update_nodes(self, root=None):
for i in range( root.childCount() ):
item = root.child(i)
item.update()
self.update_nodes(root=item)
def item_doubleclicked(self):
item = self.treewidget.selectedItems()[0]
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
if ok:
item.person.name = text
item.update()
# update - update all GUI items who are instances of the modified item
self.update_nodes( root=self.treewidget.invisibleRootItem() )
def center_window(self, window , cursor=False):
qr = window.frameGeometry()
cp = QtGui.QCursor.pos() if cursor else QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
window.move(qr.topLeft())
# __name__
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = ExampleWidget()
sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment