diff --git a/bec_widgets/plugin/__init__.py b/bec_widgets/plugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/plugin/main.py b/bec_widgets/plugin/main.py new file mode 100644 index 00000000..6709ca30 --- /dev/null +++ b/bec_widgets/plugin/main.py @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt Designer taskmenuextension example from Qt v6.x""" + +import sys + +from bec_ipython_client.main import BECIPythonClient +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()) diff --git a/bec_widgets/plugin/plugin_launch.py b/bec_widgets/plugin/plugin_launch.py new file mode 100644 index 00000000..e356b68e --- /dev/null +++ b/bec_widgets/plugin/plugin_launch.py @@ -0,0 +1,24 @@ +import os +import subprocess +import sys + +from PySide6.scripts.pyside_tool import designer + +import bec_widgets + + +def main(): + os.environ["PYSIDE_DESIGNER_PLUGINS"] = os.path.join( + "/Users/janwyzula/PSI/bec_widgets/bec_widgets/plugin" + ) + # os.environ["PYSIDE_DESIGNER_PLUGINS"] = os.path.join( + # os.path.dirname(bec_widgets.__file__), "widgets/motor_control/selection" + # ) + # os.environ["PYTHONFRAMEWORKPREFIX"] = os.path.join( + # os.path.dirname(bec_widgets.__file__), "widgets/motor_control/selection" + # ) + designer() + + +if __name__ == "__main__": + main() diff --git a/bec_widgets/plugin/registertictactoe.py b/bec_widgets/plugin/registertictactoe.py new file mode 100644 index 00000000..38701935 --- /dev/null +++ b/bec_widgets/plugin/registertictactoe.py @@ -0,0 +1,12 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection +from tictactoe import TicTacToe +from tictactoeplugin import TicTacToePlugin + +# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin + + +if __name__ == "__main__": + QPyDesignerCustomWidgetCollection.addCustomWidget(TicTacToePlugin()) diff --git a/bec_widgets/plugin/taskmenuextension.pyproject b/bec_widgets/plugin/taskmenuextension.pyproject new file mode 100644 index 00000000..29ec5245 --- /dev/null +++ b/bec_widgets/plugin/taskmenuextension.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["tictactoe.py", "main.py", "registertictactoe.py", "tictactoeplugin.py", + "tictactoetaskmenu.py"] +} diff --git a/bec_widgets/plugin/tictactoe.py b/bec_widgets/plugin/tictactoe.py new file mode 100644 index 00000000..6b4972fd --- /dev/null +++ b/bec_widgets/plugin/tictactoe.py @@ -0,0 +1,135 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Property, QPoint, QRect, QSize, Qt, Slot +from PySide6.QtGui import 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): + with QPainter(self) as painter: + 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 + + 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) diff --git a/bec_widgets/plugin/tictactoeplugin.py b/bec_widgets/plugin/tictactoeplugin.py new file mode 100644 index 00000000..9094c501 --- /dev/null +++ b/bec_widgets/plugin/tictactoeplugin.py @@ -0,0 +1,68 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtDesigner import QDesignerCustomWidgetInterface +from PySide6.QtGui import QIcon +from tictactoe import TicTacToe +from tictactoetaskmenu import TicTacToeTaskMenuFactory + +DOM_XML = """ + + + + + 0 + 0 + 200 + 200 + + + + -X-XO---- + + + +""" + + +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() diff --git a/bec_widgets/plugin/tictactoetaskmenu.py b/bec_widgets/plugin/tictactoetaskmenu.py new file mode 100644 index 00000000..c98abb95 --- /dev/null +++ b/bec_widgets/plugin/tictactoetaskmenu.py @@ -0,0 +1,67 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtDesigner import QExtensionFactory, QPyDesignerTaskMenuExtension +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout +from tictactoe import TicTacToe + + +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) diff --git a/bec_widgets/widgets/device_selection/__init__.py b/bec_widgets/widgets/device_selection/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/widgets/device_selection/device_combobox.py b/bec_widgets/widgets/device_selection/device_combobox.py new file mode 100644 index 00000000..5561bec7 --- /dev/null +++ b/bec_widgets/widgets/device_selection/device_combobox.py @@ -0,0 +1,14 @@ +from qtpy.QtWidgets import QComboBox + +from bec_widgets.utils import BECConnector, ConnectionConfig + + +class DeviceCombobox(BECConnector, QComboBox): + def __init__(self, parent=None, client=None, config=None, gui_id=None): + super().__init__(client=client, config=config, gui_id=gui_id) + QComboBox.__init__(self, parent=parent) + + self.get_bec_shortcuts() + + def get_device(self): + return getattr(self.dev, self.text().lower())