Comparing QTreeView/QAbstractItemModel to QTreeWidget - python

I'm building a tool in PySide for Maya (3D software) that auto versions and saves files in a directory.
I've spent the past few days trying to convert my initial QTreeWidget code into a model/view pattern with QTreeView and QAbstractItemModel to get tool tips and other functions, but I'm finding it harder than anticipated. When I'm learning a new coding language/technique I like to find two scripts that do the same thing- one with the new technique and one with the old technique. This way I can compare and breakdown what the new code is doing.
My big obstacle is that I'm having trouble finding a QTreeView sample that does what my QTreeWidget sample does. Also, most examples manually populate the QTreeView which doesn't help me much either. It would be great if someone could modify the QTreeView code so it does what my QTreeWidget code does. Comments on best practices for QTreeView would be great as well!
My QTreeWidget code currently...
1) gets a list of strings from a source and lists them in the first column
2) gets the date from each string and places it in the second column
QTreeView code:
from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui
import sys, os
def get_parent():
ptr = mui.MQtUtil.mainWindow()
return wrapInstance( long( ptr ), QtGui.QWidget )
################################################################################
class MyTree(QtGui.QMainWindow):
def __init__(self, parent=get_parent() ):
super(MyTree, self).__init__(parent)
data = MyData.init()
frame = QtGui.QFrame();
frame.setLayout( QtGui.QHBoxLayout() );
treeViewModel = TreeViewModel(data)
treeView = Widget_TreeView(treeViewModel)
frame.layout().addWidget( treeView );
self.setCentralWidget(frame)
################################################################################
class MyData():
def __init__(self, txt, parent=None):
self.txt = txt
self.tooltip = None
self.parent = parent
self.child = []
self.icon = []
self.index = None
self.widget = None
#---------------------------------------------------------------------------
# test initialization
#staticmethod
def init():
root = MyData("root")
root.tooltip = "root tooltip"
for i in range(0, 2):
child1 = MyData("child %i" % (i), root)
child1.tooltip = "child1 tooltip"
root.child.append(child1)
for x in range(0, 2):
child2 = MyData("child %i %i" % (i, x), child1)
child2.tooltip = "child2 tooltip"
child1.child.append(child2)
return root
# my failed attempt at adding my own data.
'''
path = "C:\Program Files"
contents = os.listdir( path )
data_list = []
for item in contents:
_data = MyData(item)
_data.tooltip = "_data tooltip"
data_list.append(_data)
return data_list # [0] adding this adds the first item to the UI,
# but i need every item from the directory
'''
################################################################################
class TreeViewModel(QtCore.QAbstractItemModel):
#---------------------------------------------------------------------------
def __init__(self, tree):
super(TreeViewModel, self).__init__()
self.__tree = tree
self.__view = None
#---------------------------------------------------------------------------
def flags(self, index):
flag = QtCore.Qt.ItemIsEnabled
if index.isValid():
flag |= QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
return flag
#---------------------------------------------------------------------------
def index(self, row, column, parent=QtCore.QModelIndex()):
node = QtCore.QModelIndex()
if parent.isValid():
nodeS = parent.internalPointer()
nodeX = nodeS.child[row]
node = self.__createIndex(row, column, nodeX)
else:
node = self.__createIndex(row, column, self.__tree)
return node
#---------------------------------------------------------------------------
def parent(self, index):
return QtCore.QModelIndex()
#---------------------------------------------------------------------------
def rowCount(self, index=QtCore.QModelIndex()):
count = 1
node = index.internalPointer()
if node is not None:
count = len(node.child)
return count
#---------------------------------------------------------------------------
def columnCount(self, index=QtCore.QModelIndex()):
return 2
#---------------------------------------------------------------------------
def data(self, index, role=QtCore.Qt.DisplayRole):
data = None
return data
#---------------------------------------------------------------------------
def setView(self, view):
self.__view = view
#---------------------------------------------------------------------------
def __createIndex(self, row, column, node):
if node.index == None:
index = self.createIndex(row, column, node)
node.index = index
if node.widget is None:
node.widget = Widget_Tooltip(node)
self.__view.setIndexWidget(index, node.widget)
return node.index
################################################################################
class Widget_TreeView(QtGui.QTreeView):
#---------------------------------------------------------------------------
def __init__(self, model, parent=None):
super(Widget_TreeView, self).__init__(parent)
self.setModel(model)
#self.setIndentation(0)
model.setView(self)
root = model.index(0,0)
################################################################################
class Widget_Tooltip(QtGui.QWidget):
#---------------------------------------------------------------------------
def __init__(self, node):
super(Widget_Tooltip, self).__init__()
# Vars
self.node = node
self.txt = None
# Commands
self.create_tooltip(self.node)
############################################
def create_tooltip(self, node):
layout = QtGui.QHBoxLayout()
self.txt = QtGui.QLabel( node.txt)
self.txt.setToolTip("Text tooltip %s %s" % (node.txt, node.tooltip))
layout.addWidget(self.txt, 1)
self.setLayout(layout)
################################################################################
if __name__ == '__main__':
try:
form_ui.close()
form_ui.deleteLater()
except:
pass
form_ui = MyTree()
form_ui.show()
try:
form_ui.show()
except:
form_ui.close()
form_ui.deleteLater()
QTreeWidget code:
import sys, os, time
from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui
def get_parent():
ptr = mui.MQtUtil.mainWindow()
return wrapInstance( long( ptr ), QtGui.QWidget )
class Main_Window(QtGui.QDialog):
def __init__(self, parent = get_parent()):
super(Main_Window, self).__init__(parent)
# Commands
self.create_gui()
self.create_layout()
self.get_contents( None )
def create_gui( self ):
self.tw_file_list = File_List( self )
self.parent = self.tw_file_list.invisibleRootItem()
def create_layout( self ):
self.layout = QtGui.QHBoxLayout( self )
self.layout.addWidget(self.tw_file_list)
self.setLayout( self.layout )
def get_contents( self, path ):
self.tw_file_list.clear()
path = "C:\Program Files"
contents = os.listdir( path )
for item in contents:
print item
parent = self.tw_file_list.invisibleRootItem()
date = self.get_date( item, path)
self.add_item(item, date, parent)
def add_item(self, name, date, parent):
item = QtGui.QTreeWidgetItem(parent)
item.setText(0, name)
item.setText(1, date)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled )
return item
def get_date( self, item, path):
path = "C:\Program Files"
file = str(path + "/" + item)
date = time.localtime(os.path.getmtime(file))
clean_date = "{0}_{1}_{2} {3}:{4}".format( date[0], date[1], date[2], date[3], str(date[4]).zfill(2) )
return clean_date
############################################
class File_List( QtGui.QTreeWidget ):
''' Create the file filters '''
def __init__( self, parent=get_parent() ):
super( File_List, self ).__init__( parent )
# Setup UI
self.setColumnCount(2)
self.setHeaderLabels(["name","date"])
self.parent = self.invisibleRootItem()
############################################
if __name__ == "__main__":
# Workaround hack for a PySide bug within maya
try:
main_ui.close()
main_ui.deleteLater()
except:
pass
# Show stuff
main_ui = Main_Window()
main_ui.show()
try:
main_ui.show()
except:
main_ui.close()
main_ui.deleteLater()

Here's your QTreeWidget example, simplified:
import sys
from PySide import QtCore, QtGui
class File_List( QtGui.QTreeWidget ):
def __init__( self, parent=None):
super( File_List, self ).__init__( parent )
self.setColumnCount(2)
self.setHeaderLabels(["name","date"])
self.get_contents()
def get_contents( self):
self.clear()
contents = ["path1","path2"]
for path in contents:
date = self.get_date(path)
self.add_item(path,date)
def add_item(self, name, date):
item = QtGui.QTreeWidgetItem(self)
item.setText(0, name)
item.setText(1, date)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled )
return item
def get_date(self, path):
return "a date"
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
win= File_List()
win.show()
sys.exit(app.exec_())
And here's the same thing with a QTreeView and a QStandardItemModel (+ how to add children):
import sys
from PySide import QtCore, QtGui
class MyModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(MyModel, self).__init__(parent)
self.get_contents()
def get_contents(self):
self.clear()
contents=["path1","path2"]
for path in contents:
date = self.get_date(path)
self.add_item(path,date)
def add_item(self,name,date):
item1 = QtGui.QStandardItem(name)
item2 = QtGui.QStandardItem(date)
self.appendRow([item1, item2])
#to append child items
childItem=QtGui.QStandardItem("child")
item1.appendRow(childItem)
def get_date(self, path):
return "a date"
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
model=MyModel()
treeView=QtGui.QTreeView()
treeView.setModel(model)
model.setHorizontalHeaderLabels(["name","date"])
treeView.show()
sys.exit(app.exec_())

Related

Can Qcompleter support two different words list?

class MyCompleter(QCompleter):
insertText = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
keywords_list = ['open browser', 'click', 'input text', 'contain', 'log', 'clear',
'clearcookies', 'doubleclick', 'scrol to view', 'should', 'reload',
'and', 'wait', 'children', 'right click', '.rightclick()', 'read file'
, 'equal']
crntDir = "D:\\log"
image_list = []
philes = os.listdir(crntDir)
for phile in philes:
if phile.endswith(".png"):
image_list.append(phile)
print(image_list)
#image name in image_list
QCompleter.__init__(self,keywords_list, parent)
self.setCompletionMode(QCompleter.PopupCompletion)
QCompleter.__init__(self,image_list, parent)
self.highlighted.connect(self.setHighlighted)
def setHighlighted(self, text):
self.lastSelected = text
def getSelected(self):
return self.lastSelected
I want to show two different word list, keyword_list for simple word complete, image_list is get all image names in assigned folder. If I input image name, then complete show image full name *.png.
import sys
import os
import qdarkstyle
from PyQt5 import QtGui, QtCore,QtWidgets
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
class MyListModel(QtCore.QAbstractListModel):
def __init__(self, datain, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = datain
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
s = QtCore.QSize(250, 200)
if index.isValid() and role == QtCore.Qt.DecorationRole:
return QtGui.QIcon(QtGui.QPixmap(self.listdata[index.row()]))
if index.isValid() and role == QtCore.Qt.DisplayRole:
# print(QtCore.QVariant(self.ListItemData[index.row()]['name']))
return QtCore.QVariant(os.path.splitext(os.path.split(self.listdata[index.row()])[-1])[0])
else:
return QtCore.QVariant()
def getItem(self, index):
if index > -1 and index < len(self.ListItemData):
return self.ListItemData[index]
class MyListView(QtWidgets.QListView):
def __init__(self,parent=None):
super(MyListView, self).__init__(parent)
self.Listview = QListView()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setIconSize(QtCore.QSize(90, 90))
self.setGridSize(QtCore.QSize(110, 110))
crntDir = "D:\\log"
list_data = []
image_list = []
philes = os.listdir(crntDir)
for phile in philes:
if phile.endswith(".png"):
list_data.append(os.path.join(crntDir, phile))
image_list.append(phile)
self.List_data = list_data
lm = MyListModel(list_data)
self.setModel(lm)
self.show()
self.clicked.connect(self.onclicked)
def onclicked(self,item):
image_selected_path = self.List_data[item.row()]
print(image_selected_path)
self.close()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyListView()
window.setWindowOpacity(0.9)
window.show()
window.raise_()
sys.exit(app.exec_())
I want image display in this window to show matched image name. How to join this two function

QSortFilterProxyModel creates blank items

What I'm trying to do: Take items from a model and sort them using a sorting proxy by a different role:
Expected output:
Real output contains blank lines which shouldn't be there:
You can see the empty lines expand the ListView and can even be selected by cursor.
Here's the code that produces this incorrect behaviour:
from PySide2.QtCore import *
from PySide2.QtWidgets import *
import sys
import string
import random
class MyItem:
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
return self.name +" "+ str(self.value)
class MyCustomModel(QAbstractListModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.list = []
def rowCount(self, parent=None):
return len(self.list)
def data(self, index, role):
row = index.row()
if row < 0 or row >= len(self.list):
return None
item = self.list[row]
if role == Qt.DisplayRole:
return str(item)
if role == Qt.UserRole:
return item.value
else:
return None
def add(self, item):
rc = self.rowCount()
self.beginInsertRows(QModelIndex(), rc, rc+1)
self.list.append(item)
self.endInsertRows()
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model = MyCustomModel()
self.listView = QListView(self)
self.sortingProxy = QSortFilterProxyModel()
self.sortingProxy.setSourceModel(self.model)
self.sortingProxy.setSortRole(Qt.UserRole)
self.sortingProxy.sort(0, Qt.AscendingOrder)
self.listView.setModel(self.sortingProxy)
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.listView)
self.setLayout(self.layout)
self.show()
# create some random data for the model
for i in range(10):
randomName = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(8)])
self.model.add(MyItem(randomName, random.randint(0, 30)))
app = QApplication(sys.argv)
widget = MyWidget()
app.exec_()
I've tracked down the issue to QSortFilterProxyModel because when it's removed the problem goes away, but the program no longer sorts the data:
from PySide2.QtCore import *
from PySide2.QtWidgets import *
import sys
import string
import random
class MyItem:
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
return self.name +" "+ str(self.value)
class MyCustomModel(QAbstractListModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.list = []
def rowCount(self, parent=None):
return len(self.list)
def data(self, index, role):
row = index.row()
if row < 0 or row >= len(self.list):
return None
item = self.list[row]
if role == Qt.DisplayRole:
return str(item)
if role == Qt.UserRole:
return item.value
else:
return None
def add(self, item):
rc = self.rowCount()
self.beginInsertRows(QModelIndex(), rc, rc+1)
self.list.append(item)
self.endInsertRows()
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model = MyCustomModel()
self.listView = QListView(self)
self.listView.setModel(self.model)
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.listView)
self.setLayout(self.layout)
self.show()
# create some random data for the model
for i in range(10):
randomName = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(8)])
self.model.add(MyItem(randomName, random.randint(0, 30)))
app = QApplication(sys.argv)
widget = MyWidget()
app.exec_()
Because the problem seems to be caused by Pyside2/Qt5 code it seems I have no idea how to counter it.
The problem is not the proxy, the problem is caused by the method you use to add items, if you review the docs you must pass the row number from and to where it is added, in this case as only 1 is added then both match , in the general case if n-elements are added, the solution is:
rc = self.rowCount()
self.beginInsertRows(QModelIndex(), rc, rc + n - 1)
So in your case the solution is:
def add(self, item):
rc = self.rowCount()
self.beginInsertRows(QModelIndex(), rc, rc)
self.list.append(item)
self.endInsertRows()

collect all items in QTreeview recursively

How can I collect all Qtreeview items so i can then iterate over them and apply necessary changes like display text updates, or color changes?
Is there an easy way to collect all of them using the 'match' method?
def get_checked(self):
model = self.treeview.model()
checked = model.match(
model.index(0, 0), QtCore.Qt.CheckStateRole,
QtCore.Qt.Checked, -1,
QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
for index in checked:
item = model.itemFromIndex(index)
print(item.text())
test code:
from PySide import QtGui, QtCore
from PySide import QtSvg, QtXml
import sys
class Person:
def __init__(self, name="", children=None):
self.name = name
self.children = children if children else []
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(300, 400)
self.init_ui()
def init_ui(self):
# Setup Tabs Widget
# self.treeview = QtGui.QTreeView()
self.treeview = QtGui.QTreeView()
self.treeview.setHeaderHidden(True)
self.treeview.setUniformRowHeights(True)
self.treeview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.model = QtGui.QStandardItemModel()
self.treeview.setModel(self.model)
self.action = QtGui.QAction('Print', self)
self.action.setShortcut('F5')
self.action.triggered.connect(self.get_checked)
fileMenu = QtGui.QMenu("&File", self)
fileMenu.addAction(self.action)
self.menuBar().addMenu(fileMenu)
# Setup central widget
self.setCentralWidget(self.treeview)
# populate data
self.populate_people()
self.treeview.expandAll()
def populate_people(self):
parent = Person("Kevin", [
Person("Tom", [Person("Sally"), Person("Susan")]),
Person("Snappy", [Person("John"), Person("Kimmy"),
Person("Joe")]),
Person("Chester", [Person("Danny"), Person("Colleen")])
]
)
self.create_nodes(parent, self.model)
def create_nodes(self, node, parent):
tnode = QtGui.QStandardItem()
tnode.setCheckable(True)
tnode.setData(QtCore.Qt.Unchecked, role=QtCore.Qt.CheckStateRole)
tnode.setData(node.name , role=QtCore.Qt.DisplayRole)
tnode.setData(node, role=QtCore.Qt.UserRole) # store object on item
parent.appendRow(tnode)
for x in node.children:
self.create_nodes(x, tnode)
def get_checked(self):
print "collecting..."
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is a recursive solution (requires Python >= 3.3):
def iterItems(self, root):
def recurse(parent):
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
yield from recurse(child)
if root is not None:
yield from recurse(root)
Alternative recursive solution (for Python2/Python3):
def iterItems(self, root):
def recurse(parent):
if root is not None:
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
for item in recurse(child):
yield item
return recurse(root)
And here is an iterative solution (for Python2/Python3):
def iterItems(self, root):
if root is not None:
stack = [root]
while stack:
parent = stack.pop(0)
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
stack.append(child)
(NB: this solution gives a more predictable ordering, and is a little faster)
Usage:
root = self.treeview.model().invisibleRootItem()
for item in self.iterItems(root):
print(item.text())
The root argument can be any item in a QStandardItemModel.

Controlling drag-n-drop disable/enable of QTreeWidget items - python

In my example I want users to have the ability to drag and drop children between families of all kinds. However I want to restrict users from having the ability to drag and drop a person onto another person, or dropping a person as a root object which in return would make it become a family.
I've gotten very close. There is a single bug though. If a user drags a person to a family, while hovering over the cursor over the family, if you carefully move the cursor to the top most edge of the family you'll see the indicator allowing you to drop the item so it's above the Family, not inside of Family. The code tests true and allows the user to successfully drop the person. The result places the person outside of the family. The image below shows what i mean.
The main focus on the code is between lines 75 - 125.
# Imports
# ------------------------------------------------------------------------------
import sys
from PySide import QtCore, QtGui
# Class Object
# ------------------------------------------------------------------------------
class Person():
def __init__(self, name="", age=0):
self.name = name
self.age = age
class Family():
def __init__(self, name=""):
self.name = name
DROP_VALIDATION_DICT = {
Person : ( Family, None ),
Family : ( None, ),
}
# Custom QTreeWidgetItem
# ------------------------------------------------------------------------------
class CustomTreeNode( QtGui.QTreeWidgetItem ):
def __init__( self, parent, data ):
super( CustomTreeNode, self ).__init__( parent )
self.data = data
#property
def data(self):
return self._data
#data.setter
def data(self, value):
self._data = value
self.setText( 0, self.data.name )
def update(self):
if self.data:
self.setText(0, self.data.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._dragroot = self.itemRootIndex() # added
# signals
self.itemExpanded.connect(self.on_item_expanded)
self.itemCollapsed.connect(self.on_item_collapsed)
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()
# drag-n-drop with constraints
def dragEnterEvent(self, event):
item = self.itemAt(event.pos())
if item is not None and ( isinstance(item.data, Family) ):
# if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super(CustomTreeWidget, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
print "testing"
print "moving..."
item = self.itemAt(event.pos())
rect = self.visualItemRect(item)
# QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.ForbiddenCursor))
print rect, event.pos()
print "Cursor: ", event.pos().y(), event.pos().y()+2
if event.pos().y() < rect.y()+2:
print "not inside"
else:
print "inside"
super(CustomTreeWidget, self).dragMoveEvent(event)
def dropEvent(self, event):
item = self.itemAt(event.pos())
if item is not None and ( isinstance(item.data, Family) ):
super(CustomTreeWidget,self).dropEvent(event)
else:
print "ignored"
event.setDropAction(QtCore.Qt.IgnoreAction)
# UI
# ------------------------------------------------------------------------------
class ExampleWidget(QtGui.QWidget):
def __init__( self, parent=None ):
super(ExampleWidget, self).__init__()
self.initUI()
def initUI(self):
# widgets
self.treewidget = CustomTreeWidget()
self.treewidget.set_headers( ["items"] )
self.btn_add_family_tree_node = QtGui.QPushButton("Add Family")
self.btn_add_person_tree_node = QtGui.QPushButton("Add Person")
# layout
self.mainLayout = QtGui.QGridLayout(self)
self.mainLayout.addWidget(self.btn_add_family_tree_node, 0,0)
self.mainLayout.addWidget(self.btn_add_person_tree_node, 0,1)
self.mainLayout.addWidget(self.treewidget, 1,0,1,3)
# signals
self.btn_add_family_tree_node.clicked.connect(self.add_family_tree_node_clicked)
self.btn_add_person_tree_node.clicked.connect(self.add_person_tree_node_clicked)
# display
self.resize(300, 400)
self.show()
self.center_window(self, True)
# test data
self.add_test_families("Roberstons", ["Kevin", "Mindy", "Riley"])
self.add_test_families("Rodriguez", ["Matt", "Kim", "Stephanie"])
def add_test_families(self, name, children):
family = Family( name )
node = CustomTreeNode( self.treewidget, family )
node.setExpanded(True)
# # disable drag and drop flags
# # QtCore.Qt.ItemIsDragEnabled
# flags = QtCore.Qt.ItemIsSelectable | \
# QtCore.Qt.ItemIsUserCheckable | \
# QtCore.Qt.ItemIsEnabled | \
# QtCore.Qt.ItemIsDropEnabled
# node.setFlags(flags)
for i in xrange(len(children)):
person = Person( children[i] )
CustomTreeNode( node, person )
# Functions
# --------------------------------------------------------------------------
def add_family_tree_node_clicked(self):
print "Created new family!"
roots = [self.treewidget]
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter family name:')
if ok:
for root in roots:
family = Family( text )
node = CustomTreeNode( root, family )
node.setExpanded(True)
self.treewidget.itemSelectionChanged.emit()
def add_person_tree_node_clicked(self):
print "Created new person!"
roots = self.treewidget.selectedItems()
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
if ok:
for root in roots:
person = Person( text )
node = CustomTreeNode( root, person )
node.setExpanded(True)
self.treewidget.itemSelectionChanged.emit()
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_())

PySide: Instant tooltips (no delay before showing the tooltip)

The tool I'm building uses tooltips to display extra info about a file before you click on it. It would be great if someone could lend some insight into how to accomplish this. I'm about a month into PySide so I'm having trouble deciphering these advanced examples/answers I've found online, so a simple code example with some comments will help me out a lot.
Here's what I have so far. I have no idea what I'm doing when it comes to events, so this is the best I could do with the code examples I have:
from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui
def get_parent():
ptr = mui.MQtUtil.mainWindow()
return wrapInstance( long( ptr ), QtGui.QWidget )
############################################
''' Classes '''
############################################
class Main_Window( QtGui.QDialog ):
def __init__( self, parent=get_parent() ):
super( Main_Window, self ).__init__( parent )
self.setMouseTracking(True) # Set tracking
self.create_gui()
self.create_layout()
self.create_connections()
self.get_contents()
self.shapeItems = []
#-------------------------------------------------------------------- # Mouse things
def mouseMoveEvent(self, event):
if (event.buttons() & QtCore.Qt.LeftButton):
self.moveItemTo(event.pos())
#-------------------------------------------------------------------- # Mouse things
def event(self, event):
if event.type() == QtCore.QEvent.ToolTip:
helpEvent = event
index = self.itemAt(helpEvent.pos())
if index != -1:
QtGui.QToolTip.showText(helpEvent.globalPos(), self.shapeItems[index].toolTip())
else:
QtGui.QToolTip.hideText()
event.ignore()
return True
return super(Main_Window, self).event(event)
#--------------------------------------------------------------------
def create_gui( self ):
self.tv_model=MyModel()
self.tv_file_list = File_List( self )
#--------------------------------------------------------------------
def create_layout( self ):
self.main_layout = QtGui.QVBoxLayout( self )
self.main_layout.addWidget( self.tv_file_list )
self.setLayout( self.main_layout )
#--------------------------------------------------------------------
def get_contents(self):
self.tv_model.clear()
self.tv_model.setHorizontalHeaderLabels(["name","date"])
contents=["path1","path2"]
for path in contents:
date = self.get_date(path)
self.add_file(path,date)
self.tv_file_list.setColumnWidth(0, 150)
#--------------------------------------------------------------------
def add_file(self, name, date):
name = QtGui.QStandardItem(name)
name.setToolTip(name.text())
name.setIcon(self.style().standardIcon(QtGui.QStyle.SP_DirOpenIcon))
date = QtGui.QStandardItem(date)
self.tv_model.appendRow([name, date])
#--------------------------------------------------------------------
def get_date(self, path):
return "a date"
#--------------------------------------------------------------------
def create_connections( self ):
self.tv_file_list.clicked.connect( self.on_click )
# slots --------------------------------------------------------------
def on_click(self, item ):
index = self.tv_file_list.selectedIndexes()[0]
item = self.tv_model.itemFromIndex(index).text()
print item
############################################
class MyModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(MyModel, self).__init__(parent)
#--------------------------------------------------------------------
def flags(self, index):
flag = QtCore.Qt.ItemIsEnabled
if index.isValid():
flag |= QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
return flag
############################################
class File_List( QtGui.QTreeView ):
''' Create the file filters '''
def __init__( self, mainUIWindow, parent=get_parent() ):
super( File_List, self ).__init__( parent )
self.setModel(mainUIWindow.tv_model)
self.setIndentation(0)
self.setColumnWidth(0,500)
self.setFocusPolicy(QtCore.Qt.NoFocus)
self.setStyleSheet("QToolTip { color: rgb(170,170,170); background-color: rgb(20,20,20); border: 1px rgb(20,20,20); }")
############################################
if __name__ == "__main__":
# workaround for a bug in maya
try:
tree_view_ui.close()
tree_view_ui.deleteLater()
except:
pass
tree_view_ui = Main_Window()
tree_view_ui.show()
try:
tree_view_ui.show()
except:
tree_view_ui.close()
tree_view_ui.deleteLater()
HERE is a post describing how to create instant tooltips, but without any code examples I'm at a loss for how to write this. The documentation wasn't really much of a help either (it really should have simple examples for beginners).
HERE is a code that shows how to implement mouse move events, but I haven't been able to get it to work in my own example above. I keep getting errors that say: "TypeError: super(type, obj): obj must be an instance or subtype of type" and "AttributeError: 'Main_Window' object has no attribute 'itemAt'"
Again, any help or thoughts would be great. Thank you
SOLUTION
from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui
def get_parent():
ptr = mui.MQtUtil.mainWindow()
return wrapInstance( long( ptr ), QtGui.QWidget )
############################################
''' Classes '''
############################################
class Main_Window( QtGui.QDialog ):
def __init__( self, parent=get_parent() ):
super( Main_Window, self ).__init__( parent )
self.create_gui()
self.create_layout()
self.create_connections()
self.get_contents()
#--------------------------------------------------------------------
def create_gui( self ):
self.tv_model=MyModel()
self.tv_file_list = File_List( self )
self.tv_file_list.setMouseTracking(True) # Set mouse tracking
#--------------------------------------------------------------------
def create_layout( self ):
self.main_layout = QtGui.QVBoxLayout( self )
self.main_layout.addWidget( self.tv_file_list )
self.setLayout( self.main_layout )
#--------------------------------------------------------------------
def get_contents(self):
self.tv_model.clear()
self.tv_model.setHorizontalHeaderLabels(["name","date"])
contents=["path1","path2"]
for path in contents:
date = self.get_date(path)
self.add_file(path,date)
self.tv_file_list.setColumnWidth(0, 150)
#--------------------------------------------------------------------
def add_file(self, name, date):
name = QtGui.QStandardItem(name)
user = "me"
name.setToolTip("<b>{0}</b><br><b>{1}</b>".format(name.text(), user) ) # Here's where I set the tooltip
name.setIcon(self.style().standardIcon(QtGui.QStyle.SP_DirOpenIcon))
date = QtGui.QStandardItem(date)
self.tv_model.appendRow([name, date])
#--------------------------------------------------------------------
def get_date(self, path):
return "a date"
#--------------------------------------------------------------------
def create_connections( self ):
self.tv_file_list.clicked.connect( self.on_click )
self.tv_file_list.entered.connect( self.handleItemEntered ) # New connection
# slots --------------------------------------------------------------
def on_click(self, item ):
index = self.tv_file_list.selectedIndexes()[0]
item = self.tv_model.itemFromIndex(index).text()
print item
#--------------------------------------------------------------------
def handleItemEntered(self, index): # New slot
if index.isValid():
QtGui.QToolTip.showText(
QtGui.QCursor.pos(),
index.data(),
self.tv_file_list.viewport(),
self.tv_file_list.visualRect(index)
)
############################################
class MyModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(MyModel, self).__init__(parent)
def flags(self, index):
flag = QtCore.Qt.ItemIsEnabled
if index.isValid():
flag |= QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
return flag
############################################
class File_List( QtGui.QTreeView ):
''' Create the file filters '''
def __init__( self, mainUIWindow, parent=get_parent() ):
super( File_List, self ).__init__( parent )
self.setModel(mainUIWindow.tv_model)
self.setIndentation(0)
self.setColumnWidth(0,500)
self.setFocusPolicy(QtCore.Qt.NoFocus)
self.setStyleSheet("QToolTip { color: rgb(170,170,170); background-color: rgb(20,20,20); border: 1px rgb(20,20,20); }")
############################################
if __name__ == "__main__":
# workaround for a bug in maya
try:
tree_view_ui.close()
tree_view_ui.deleteLater()
except:
pass
tree_view_ui = Main_Window()
tree_view_ui.show()
try:
tree_view_ui.show()
except:
tree_view_ui.close()
tree_view_ui.deleteLater()
This is much easier to do using the treeview's entered signal. And if you use the overload of showText that takes a rect argument, QToolTip will automatically do the rest.
Here's a simple demo:
from PySide import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QtGui.QTreeView(self)
self.view.setMouseTracking(True)
self.view.entered.connect(self.handleItemEntered)
model = QtGui.QStandardItemModel(self)
for text in 'One Two Three Four Five'.split():
model.appendRow(QtGui.QStandardItem(text))
self.view.setModel(model)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
def handleItemEntered(self, index):
if index.isValid():
QtGui.QToolTip.showText(
QtGui.QCursor.pos(),
index.data(),
self.view.viewport(),
self.view.visualRect(index)
)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 200)
window.show()
sys.exit(app.exec_())

Categories