Python qt: widgets

From wikinotes

Buttons

btn =  QtGui.QPushButton('Button', self)                                 ## (label, parent)
btn.setToolTip( 'This is a <b>QPushButton</b> widget')                   ## Tooltip (Annotation) (Can use QSS!!!)
btn.move(50, 50)                                                         ## (x,y) from top-left
btn.clicked.connect(  QtCore.QCoreApplication.instance().quit  )         ## Quit Button Function



StatusBar

A helpline that can show messages. By default, it displays statusTips set on widgets when you mouse over them widget.setStatusTip('help message') on widgets. This is the equivalent to mel's helpLine.

statusbar = QtWidgets.QStatusBar()

mainwindow.setStatusBar(statusbar)
statusbar.showMessage('Ready')

MenuBar

#### Gui Controllers
## Create Menubar, Menus
menubar  = self.menuBar()                     # Create Menubar
fileMenu = menubar.addMenu('File')            # create new dropdownMenu 'File'
prefs    = menubar.addMenu('Preferences')     # create new dropdownMenu 'Preferences'
styles   = menubar.addMenu('Styles')


## Create QActions, (menuItems)
quitMenu = QtGui.QAction( QtGui.QIcon('exit.png'), 'Exit', self)  # New QAction (quit)    (icon, label, parent) 
menuA    = QtGui.QAction( 'menuA', self)                          # New QAction (menuA)    (icon, label, parent) 
menuB    = QtGui.QAction( 'menuB', self)                          # New QAction (menuB)    (icon, label, parent) 

## Assign Commands to Qactions
quitMenu.setShortcut(  'Ctrl+Q')             # assign Hotkey
quitMenu.setStatusTip( 'Exit application')   # Use Statustip
quitMenu.triggered.connect( self.close )     # QAction's command


## Add Actions to MenuBar
fileMenu.addAction( quitMenu )
fileMenu.addAction( menuA )
fileMenu.addAction( menuB )

## StatusBar
self.statusBar()      # Any QActions with statusTips attached to them will display tips here


self.show()

QMenu

Dropdown File Menus.

# QMenus in Qt4 do not support having tooltips.
# (in Qt5 this can be enabled using QMenu.setToolTipsVisible(True) )
# you can hack them in however:

class ToolTipQMenu( QtWidgets.QMenu ):
   def __init__(self,*args,**kwds):
       QtWidgets.QMenu.__init__(self)
       self.hovered.connect( self.handleMenuHovered )
   def handleMenuHovered(self, action):
       action.parent().setToolTip( action.toolTip() )

# tooltips can be added to qmenu actions
menu   = ToolTipQMenu()
action = menu.addAction('do something', do_something )
action.setToolTip('does something that takes a long time.')
menu.addSeparator()

NOTE:

You can colourize qmenu items by providing a QWidgetAction() class (which lets you set the rendered widget).



Toolbar

Toolbars store actions. Think of the tool-buttons on the left-hand side of Photoshop.

## Create Actions (for toolbar)
quitAction = QtGui.QAction( 'Quit',  self )        # Build QtActions (to add to toolbar)
tbAction   = QtGui.QAction( 'toolB', self )
tcAction   = QtGui.QAction( 'toolC', self )
quitAction.triggered.connect(  self.close )

## Create Toolbar
toolbar = QtGui.QToolBar( 'FileToolBar', self )   # Build Toolbar
toolbar.setOrientation( QtCore.Qt.Vertical )      # Make Vertical
toolbar.addAction( quitAction )                   # Add QtActions to toolbar
toolbar.addAction( tbAction )
toolbar.addAction( tcAction )



QLineEdit, QTextEdit (Editable Text)

## QLineEdit produces a textBox you can type into that is only a single line
#
mergeTitle = QtGui.QLineEdit()
mergeTitle.setText('abc')
mergeTitle.setReadOnly(1)
mergeTitle.text()                                       # QLineEdit's Contents


## QTextEdit produces a textbox that you can type in
#
textEdit = QtGui.QTextEdit()
textEdit.setReadOnly(1)
textEdit.setFont( QtGui.QFont('Lucida Console', 9) )
textEdit.setFocus()                                    # Set Mouse Focus in a Particular Control
textEdit.toPlainText()                                 # textEdit's Contents

QTextEdit with modified line-height

#### this must be performed every time after text contents change

textedit = QtWidgets.QTextEdit()

#  set 1.5x line height for readability
cursor   = textedit.textCursor()
blockfmt = cursor.blockFormat()
blockfmt.setLineHeight(25, QtGui.QTextBlockFormat.FixedHeight )
cursor.setBlockFormat(blockfmt)



QLabel (text, image, movie)

### Text
msg = QtGui.QLabel( 'mylabel' )
msg.setText( '<font color="red">abcd</font> def' )
msg.setIndent(80)
msg.setAlignment( QtCore.Qt.AlignCenter )
msg.setTextInteractionFlags( QtCore.Qt.TextSelectableByMouse )
### Image
img = QtGui.QLabel()
pixmap = QtGui.QPixmap('myfile.png')
pixmap = pixmap.scaled( 10, 10 )          ## resize image
img.setPixmap( pixmap )
### Image from WebServer
def print_reply( label, reply ):
    pixmap = QtGui.QPixmap()
    pixmap.loadFromData( reply.readAll() )
    label.setPixmap( pixmap )

label = QtWidgets.QLabel()
label.show()

manager = QtNetwork.QNetworkAccessManager()
manager.finished.connect( functools.partial(print_reply, label) )
manager.get( QtNetwork.QNetworkRequest('http://mercuryfilmworks.com/wp-content/themes/mercuryfilmworks/library/images/headerlogo.png'))


WARNING:

Qt expects you to use a single QNetworkAccessManager for the entire application

WARNING:

The QNetworkAccessManager's NetworkReply object must be deleted by the user (using reply.deleteLater())



QComboBox (DropdownMenu)

drop     = QtGui.QComboBox()
itemList = ['A','B','C','D']
drop.addItems( itemList )
drop.setCurrentIndex( 2 )

drop.currentIndex()						## Get Current Index
drop.currentText()						## Get Current Text

allDropItems = [drop.itemText(i) for i in range(itemText.count())]

drop.currentIndexChanged.connect( lambda: self.my_function( drop ) )   # connect function when index is changed (index is -1 if no items remain)



QFileDialog

dialog = QtWidgets.QFileDialog()

dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)  # QFileDialog.FileMode
dialog.setViewMode(QtWidgets.QFileDialog.Detail)        # QFileDialog.ViewMode

dialog.setNameFilter('Images (*.png *.jpg *.bmp)')                                           # only files with these extensions will be displayed
dialog.setNameFilter('PNG File (*.png);;TGA File (*.tga *.targa);;JPEG File (*.jpg *.jpeg)')  # multiple filters separated by ';;'

dialog.setDirectory('/path/to/dir')

if dialog.exec_():
    filepaths = dialog.selectedFiles()

QListWidget, QListView

QListWidget

## This is the equivalent to a textScrollList in MEL. 
## individual QListWidget entries cannot be colourized with html

nodesList = QtGui.QListWidget()
nodesList.addItems( ['a_node','b_node','c_node','d_node','e_node','f_node','g_node'] )
nodesList.clear()

sel_items = nodesList.selectedItems()
for item in sel_items:
   print item.text()

sel_indexes = nodesList.selectedIndexes()

nodesList.itemSelectionChanged.connect( self._handle_itemSelectionChanged )

nodesList.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)  ## multiselect


QListView

#### QListView
##


QListWidget with Custom Delegate

class ListWidget_LineEdit_Delegate( QtWidgets.QStyledItemDelegate ):
    def createEditor(self, parent, option, index ):
        editor = QtWidgets.QLineEdit( parent=parent )
        editor.setValidator( QtGui.QIntValidator )
        editor.editingFinished.connect( self.commitAndCloseEditor )

        return editor

    def updateEditorGeometry(self, editor, option, index ):
        editor.setGeometry( option.rect )

    def commitAndCloseEditor(self):
        editor = self.sender()
        self.commitData.emit( editor )


listwid  = QtWidgets.QListWidget()
delegate = ListWidget_LineEdit_Delegate()
listwid.setItemDelegate( delegate )


See Also:

http://falsinsoft.blogspot.ca/2013/11/qlistwidget-and-item-edit-event.html signals from QListWidget while being edited
https://stackoverflow.com/questions/22049129/qt-signal-for-when-qlistwidget-row-is-edited signals from QListWidget while being edited



QTableWidget

table  = QtGui.QTableWidget()
table.setRowCount(0)                 ## row/column count MUST be done before adding widgets/items to the table
table.setColumnCount(2)

## Widget Attributes
columns = OrderedDict([
	## name ##  ## width ##
	('Names'  ,   100 ),
	('Numbers',    80 ),
	('Other'  ,    80 ),
	('Notes'  ,   500 ),
	])

table.setHorizontalHeaderLabels([column for column in columns])

# column titles
column_index = 0
for width in columns.values():
	table.setColumnWidth( column_index, width )
	column_index +=1

table.setAlternatingRowColors(1)
table.setSortingEnabled(1)
table.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows )     ## select entire rows instead of single cell
table.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection )  ## maya-style multi-selection



## Contents
row_num = 0
for item in ('A','B','C','D'):
	widgetA = QtGui.QTableWidgetItem( item )
	table.setItem(       row_num, 0, widgetA)                   ## adds QTableWidgetItem
	table.setCellWidget( row_num, 1, QtGui.QLabel('blah') )     ## adds any Widget

custom sorting

class CustomSortTableItem( QTableWidgetItem ):
	class __lt__(self, other):
		""" returns True if item is less than other """
		return self.sort_key < other.sort_key

sorting QWidgets

## you can sort QWidgets by setting both QTableWidgetItem __AND___ a widget
## to a particular cell. The QTableWidgetItem will be used for sorting.
table = QTableWidget()
table.setSortingEnabled(1)

for item in ('apple','orange','banana','pear'):
	table.setItem( 0,0, QTableWidgetItem( item )  )
	table.setCellWidget( 0,0, QPushButton( item ) )

column stretch/resize

# qt5
table.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents )


# qt4
table = QTableWidget()
table.setHorizontalHeaderLabels( ['long','short'] )
table.horizontalHeader().setResizeMode( 0, QtWidgets.QHeaderView.Stretch )
table.itemSelectionChagned.connect( self._handle_selection )   ## handle selection



QTreeWidget

Working with a QTreeWidget is very similiar to working with a QTableWidget.

#!/usr/bin/env python
from PySide      import QtGui, QtCore
from libpyside   import newUI
from collections import OrderedDict


# the majority of the manipulation of the QTreeWidget
# is done on the QTreeWidgetItems (rather than the tree itself)
#
# QTreeWidgetItem.addChild
# QTreeWidgetItem( parent, [colA,colB,...] )
# ...

class TestTreeWidget( newUI.NewWindow ):
    def __init__(self):
        newUI.NewWindow.__init__(self)
    def main(self):
        layout = QtGui.QVBoxLayout()
        tree   = QtGui.QTreeWidget()

        ## setup columns
        columns = OrderedDict([
            ('A', 200),
            ('B', 120),
            ('C', 120),
            ('D', 120),
        ])
        tree.setColumnCount(len(columns))
        tree.setHeaderLabels([ column for column in columns ])

        ## add `root` items (each item in list is a new column)
        root1 = QtGui.QTreeWidgetItem( tree, ['root1','a'])
        root2 = QtGui.QTreeWidgetItem( tree, ['root2','a'])
        root1.setText(2,'b')
        root1.setText(3,'c')

        ## add `child` items to those root items
        r1_sub1 = QtGui.QTreeWidgetItem( root1, ['r1_sub1','a','b'])
        r1_sub2 = QtGui.QTreeWidgetItem( root1, ['r1_sub2','a','b'])
        r1_sub3 = QtGui.QTreeWidgetItem( ['r1_sub3','a','b'] )
        root1.addChild( r1_sub3 )

        root3 = TestCustomTreeWidgetItem( tree )


        layout.addWidget( tree )
        self.setLayout(layout)


## in order to add custom widgets to a QTreeWidgetItem,
## the widget *MUST* either be an attribute, or have some
## other measure taken so that it is not garbage-collected.
## (otherwise you will get a segmentation fault)
##
## This makes it cleanest to create your own TreeWidgetItem
## subclasses.
class TestCustomTreeWidgetItem( QtGui.QTreeWidgetItem ):
    def __init__(self, treewidget ):
        QtGui.QTreeWidgetItem.__init__(self, treewidget )

        ## column 1
        self.setText( 0, 'column1' )
        self.setText( 1, 'column2' )
        self.button = QtGui.QPushButton("Press Me")
        self.treeWidget().setItemWidget( self, 2, self.button )



with newUI.WithQApp():

    win = TestTreeWidget()
    win.display()


QTreeWidgets can also be nested meaning subtrees can have variable columns.

from mfw2.lib.qt import QApplication, QBaseWindow, QBaseObject
from qtpy import QtWidgets


class Tree( QtWidgets.QTreeWidget ):
    """ single column TreeWidget """
    def __init__(self):
        super( Tree, self ).__init__()
        self.setColumnCount(1)
        self.setHeaderLabels(['A'])

class TreeNested_TreeWidgetItem( QtWidgets.QTreeWidget ):
    """ 5x column TreeWidget (will be nested into `Tree`) """
    def __init__(self):
        super( TreeNested_TreeWidgetItem, self ).__init__()
        self.setColumnCount(5)
        self.setHeaderLabels(['1','2','3','4','5'])

class TreeNested_TreeWidgetItem( QtWidgets.QTreeWidgetItem ):
    """ Widget containing `TreeNested_TreeWidgetItem`, and gets added to `Tree` """
    def __init__(self, treewidget ):
        super( TreeNested_TreeWidgetItem, self ).__init__(treewidget)
        self.subtree = TreeNested_TreeWidgetItem()
        QtWidgets.QTreeWidgetItem( self.subtree, ['a','b','c','d','e'] )
        self.treeWidget().setItemWidget( self, 0, self.subtree )



with QApplication():

    win    = QBaseWindow()
    tree_A = Tree()
    win.layout().addWidget( tree_A )

    root1 = QtWidgets.QTreeWidgetItem( tree_A, ['test_1'] )
    root2 = QtWidgets.QTreeWidgetItem( tree_A, ['test_2'] )
    root3 = QtWidgets.QTreeWidgetItem( tree_A, ['expand me'] )
    root4 = QtWidgets.QTreeWidgetItem( tree_A, ['expand me'] )

    sub1  = TreeNested_TreeWidgetItem(root3)
    sub2  = TreeNested_TreeWidgetItem(root4)

    win.show()


QTreeWidgetItem

QTreeWidgetItems are special, and they do not inherit from QWidgets. This unfortunately makes them a gigantic pain in the ass to work with if you are trying to do anything outside of their vanilla implementation. Here are some of the strategies I have figured out for dealing with their abysmal retardation.

resizing

# set a predetermined size on your treeWidget's stylesheet 
# so that you can predictably handle the sizes of your dynamically
# sized TreeWidgetItems

_TREEWIDGETITEM_HEIGHT = 23

class MyTree( QtWidgets.QTreeWidget ):
	def __init__(self):
		QtWidgets.QTreeWidget.__init__(self)
		self.setStyleSheet('QTreeView::item { height:%s; }' % _TREEWIDGETITEM_HEIGHT )



# use a custom class for sub-treewidgets, or their items.
# instead of using setSizeHint() directly, create your
# own custom setFixedHeight(), and in addition to resizing
# the widget,
#
# ALSO RESIZE THE WINDOW TO FORCE IT TO RECALCULATE ALL WIDGET SIZES
#
class SubTreeWidgetItem( QtWidgets.QTreeWidgetItem ):
    def __init__(self, subtree_class, parent, parent_index=0, *args, **kwds ):
        QtWidgets.QTreeWidgetItem.__init__(self, parent)
        self.subtree = subtree_class( *args, **kwds )
        self.treeWidget().setItemWidget( self, parent_index, self.subtree )

        # disable selection of this widget... seems impractical
        self.setFlags( QtCore.Qt.ItemIsEnabled )


    def setFixedHeight(self, height):
        self.setSizeHint( 0, QtCore.QSize(-1, height) )
        if self.treeWidget().window():
            size = self.treeWidget().window().size()
            height = size.height()
            width  = size.width()
            self.treeWidget().window().resize( width, height-1 )
            self.treeWidget().window().resize( width, height )


Tips/Tricks

Custom Popup Widget

See https://forum.qt.io/topic/85056/custom-popup-widget/6

Position of QCalendarWidget will be determined by parent within popup method.

class _PopupCalendarWidget(QtWidgets.QCalendarWidget):
    date_set = QtCore.Signal(QtCore.QDate)

    def __init__(self, parent):
        super(_PopupCalendarWidget, self).__init__(parent)

        # Widget Attrs
        self.setWindowFlags(QtCore.Qt.Popup)

        # Connections
        self.activated.connect(self._handle_activated)

    def popup(self):
        parent = self.parent()
        parent_global_pos = parent.mapToGlobal(parent.rect().topLeft())

        global_pos = QtCore.QPoint(
            parent_global_pos.x(),
            parent_global_pos.y() + parent.rect().height()
        )
        local_pos = self.mapFromGlobal(global_pos)
        size = QtCore.QSize(350, 200)
        rect = QtCore.QRect(local_pos, size)
        self.setGeometry(rect)
        self.show()

    def _handle_activated(self, qdate):
        self.date_set.emit(qdate)
        self.deleteLater()