Task Menu Extension (Designer)¶
This example shows how to add custom widgets to Qt Designer, which can be launched with pyside6-designer, and to extend its built-in context menu.
The main mechanism is based on the QPyDesignerCustomWidgetCollection class that takes care of handling the registration.
More information can be found in Custom Widgets in Qt Designer.
from PySide6.QtCore import Qt, QPoint, QRect, QSize, Property, Slot
from PySide6.QtGui import QMouseEvent, QPainter, QPen
from PySide6.QtWidgets import QWidget
EMPTY = '-'
CROSS = 'X'
NOUGHT = 'O'
DEFAULT_STATE = "---------"
class TicTacToe(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._state = DEFAULT_STATE
self._turn_number = 0
def minimumSizeHint(self):
return QSize(200, 200)
def sizeHint(self):
return QSize(200, 200)
def setState(self, new_state):
self._turn_number = 0
self._state = DEFAULT_STATE
for position in range(min(9, len(new_state))):
mark = new_state[position]
if mark == CROSS or mark == NOUGHT:
self._turn_number += 1
self._change_state_at(position, mark)
position += 1
self.update()
def state(self):
return self._state
@Slot()
def clear_board(self):
self._state = DEFAULT_STATE
self._turn_number = 0
self.update()
def _change_state_at(self, pos, new_state):
self._state = (self._state[:pos] + new_state
+ self._state[pos + 1:])
def mousePressEvent(self, event):
if self._turn_number == 9:
self.clear_board()
return
for position in range(9):
cell = self._cell_rect(position)
if cell.contains(event.position().toPoint()):
if self._state[position] == EMPTY:
new_state = CROSS if self._turn_number % 2 == 0 else NOUGHT
self._change_state_at(position, new_state)
self._turn_number += 1
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.darkGreen, 1))
painter.drawLine(self._cell_width(), 0,
self._cell_width(), self.height())
painter.drawLine(2 * self._cell_width(), 0,
2 * self._cell_width(), self.height())
painter.drawLine(0, self._cell_height(),
self.width(), self._cell_height())
painter.drawLine(0, 2 * self._cell_height(),
self.width(), 2 * self._cell_height())
painter.setPen(QPen(Qt.darkBlue, 2))
for position in range(9):
cell = self._cell_rect(position)
if self._state[position] == CROSS:
painter.drawLine(cell.topLeft(), cell.bottomRight())
painter.drawLine(cell.topRight(), cell.bottomLeft())
elif self._state[position] == NOUGHT:
painter.drawEllipse(cell)
painter.setPen(QPen(Qt.yellow, 3))
for position in range(0, 8, 3):
if (self._state[position] != EMPTY
and self._state[position + 1] == self._state[position]
and self._state[position + 2] == self._state[position]):
y = self._cell_rect(position).center().y()
painter.drawLine(0, y, self.width(), y)
self._turn_number = 9
for position in range(3):
if (self._state[position] != EMPTY
and self._state[position + 3] == self._state[position]
and self._state[position + 6] == self._state[position]):
x = self._cell_rect(position).center().x()
painter.drawLine(x, 0, x, self.height())
self._turn_number = 9
if (self._state[0] != EMPTY and self._state[4] == self._state[0]
and self._state[8] == self._state[0]):
painter.drawLine(0, 0, self.width(), self.height())
self._turn_number = 9
if (self._state[2] != EMPTY and self._state[4] == self._state[2]
and self._state[6] == self._state[2]):
painter.drawLine(0, self.height(), self.width(), 0)
self._turn_number = 9
# QPainter needs an explicit end() in PyPy. This will become a context manager in 6.3.
painter.end()
def _cell_rect(self, position):
h_margin = self.width() / 30
v_margin = self.height() / 30
row = int(position / 3)
column = position - 3 * row
pos = QPoint(column * self._cell_width() + h_margin,
row * self._cell_height() + v_margin)
size = QSize(self._cell_width() - 2 * h_margin,
self._cell_height() - 2 * v_margin)
return QRect(pos, size)
def _cell_width(self):
return self.width() / 3
def _cell_height(self):
return self.height() / 3
state = Property(str, state, setState)
"""PySide6 port of the Qt Designer taskmenuextension example from Qt v6.x"""
import sys
from PySide6.QtWidgets import QApplication
from tictactoe import TicTacToe
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TicTacToe()
window.state = "-X-XO----"
window.show()
sys.exit(app.exec())
from tictactoe import TicTacToe
from tictactoeplugin import TicTacToePlugin
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
if __name__ == '__main__':
QPyDesignerCustomWidgetCollection.addCustomWidget(TicTacToePlugin())
from tictactoe import TicTacToe
from tictactoetaskmenu import TicTacToeTaskMenuFactory
from PySide6.QtGui import QIcon
from PySide6.QtDesigner import (QExtensionManager,
QDesignerCustomWidgetInterface)
DOM_XML = """
<ui language='c++'>
<widget class='TicTacToe' name='ticTacToe'>
<property name='geometry'>
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>200</height>
</rect>
</property>
<property name='state'>
<string>-X-XO----</string>
</property>
</widget>
</ui>
"""
class TicTacToePlugin(QDesignerCustomWidgetInterface):
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = TicTacToe(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return ''
def icon(self):
return QIcon()
def includeFile(self):
return 'tictactoe'
def initialize(self, form_editor):
self._form_editor = form_editor
manager = form_editor.extensionManager()
iid = TicTacToeTaskMenuFactory.task_menu_iid()
manager.registerExtensions(TicTacToeTaskMenuFactory(manager), iid)
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return 'TicTacToe'
def toolTip(self):
return 'Tic Tac Toe Example, demonstrating class QDesignerTaskMenuExtension (Python)'
def whatsThis(self):
return self.toolTip()
from tictactoe import TicTacToe
from PySide6.QtCore import QObject, Slot
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
from PySide6.QtDesigner import (QExtensionFactory, QPyDesignerTaskMenuExtension)
class TicTacToeDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
layout = QVBoxLayout(self)
self._ticTacToe = TicTacToe(self)
layout.addWidget(self._ticTacToe)
button_box = QDialogButtonBox(QDialogButtonBox.Ok
| QDialogButtonBox.Cancel
| QDialogButtonBox.Reset)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
reset_button = button_box.button(QDialogButtonBox.Reset)
reset_button.clicked.connect(self._ticTacToe.clear_board)
layout.addWidget(button_box)
def set_state(self, new_state):
self._ticTacToe.setState(new_state)
def state(self):
return self._ticTacToe.state
class TicTacToeTaskMenu(QPyDesignerTaskMenuExtension):
def __init__(self, ticTacToe, parent):
super().__init__(parent)
self._ticTacToe = ticTacToe
self._edit_state_action = QAction('Edit State...', None)
self._edit_state_action.triggered.connect(self._edit_state)
def taskActions(self):
return [self._edit_state_action]
def preferredEditAction(self):
return self._edit_state_action
@Slot()
def _edit_state(self):
dialog = TicTacToeDialog(self._ticTacToe)
dialog.set_state(self._ticTacToe.state)
if dialog.exec() == QDialog.Accepted:
self._ticTacToe.state = dialog.state()
class TicTacToeTaskMenuFactory(QExtensionFactory):
def __init__(self, extension_manager):
super().__init__(extension_manager)
@staticmethod
def task_menu_iid():
return 'org.qt-project.Qt.Designer.TaskMenu'
def createExtension(self, object, iid, parent):
if iid != TicTacToeTaskMenuFactory.task_menu_iid():
return None
if object.__class__.__name__ != 'TicTacToe':
return None
return TicTacToeTaskMenu(object, parent)
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.