Skip to content

Commit 9c8a830

Browse files
authored
Merge pull request #3 from jdranczewski/dev
v0.10.0 #3
2 parents b24194b + 5aea5d5 commit 9c8a830

24 files changed

+931
-347
lines changed

docs/source/conf.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,32 @@
55

66
import os
77
import sys
8-
sys.path.insert(0, os.path.abspath('../..'))
8+
9+
sys.path.insert(0, os.path.abspath("../.."))
910

1011
# -- Project information -----------------------------------------------------
1112
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
1213

13-
project = 'puzzlepiece'
14-
copyright = '2023, Jakub Dranczewski'
15-
author = 'Jakub Dranczewski'
16-
release = '0.1'
14+
project = "puzzlepiece"
15+
copyright = "2024, Jakub Dranczewski"
16+
author = "Jakub Dranczewski"
17+
release = "0.1"
1718

1819
# -- General configuration ---------------------------------------------------
1920
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
2021

21-
extensions = [
22-
'sphinx.ext.autodoc',
23-
'sphinx.ext.viewcode',
24-
'sphinx.ext.napoleon'
25-
]
22+
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon"]
2623

27-
templates_path = ['_templates']
24+
templates_path = ["_templates"]
2825
exclude_patterns = []
2926

30-
autodoc_member_order = 'bysource'
27+
autodoc_member_order = "bysource"
3128

3229
# autodoc_mock_imports = ["pyqtgraph", "QtPy", "numpy"]
3330

3431

3532
# -- Options for HTML output -------------------------------------------------
3633
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
3734

38-
html_theme = 'sphinx_rtd_theme'
39-
html_static_path = ['_static']
35+
html_theme = "sphinx_rtd_theme"
36+
html_static_path = ["_static"]

docs/source/modules.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ API
1111
puzzlepiece.action
1212
puzzlepiece.parse
1313
puzzlepiece.threads
14+
puzzlepiece.extras
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
puzzlepiece.extras.datagrid module
2+
==================================
3+
4+
.. automodule:: puzzlepiece.extras.datagrid
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

docs/source/puzzlepiece.extras.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
puzzlepiece.extras module
2+
=========================
3+
4+
The extras module contains additional features that are useful, but not part of the
5+
core puzzlepiece functionality. Currently this is just the ``datagrid`` - a Widget that
6+
can be added to your Pieces, with multiple Piece-like Rows that use params for data storage
7+
and manipulation.
8+
9+
The extras modules have to be imported explicitly::
10+
11+
# This will import the datagrid module:
12+
from puzzlepiece.extras import datagrid
13+
# The following will not work:
14+
import puzzlepiece.extras as extras
15+
extras.datagrid
16+
17+
.. toctree::
18+
:maxdepth: 2
19+
20+
puzzlepiece.extras.datagrid

examples/basic_example.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import puzzlepiece as pzp
22
from puzzlepiece.pieces import random_number, plotter, scan_value, script
33

4+
45
def main():
56
# Define the containing app
67
app = pzp.QApp([])
@@ -28,5 +29,6 @@ def main():
2829
puzzle.show()
2930
app.exec()
3031

32+
3133
if __name__ == "__main__":
32-
main()
34+
main()

examples/most_basic_example.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Import the automation framework
22
import puzzlepiece as pzp
3+
34
# Import the random number generator Piece
45
from puzzlepiece.pieces import random_number, plotter
56

puzzlepiece/__init__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from . import piece
2-
Piece = piece.Piece
32
from . import puzzle
4-
Puzzle = puzzle.Puzzle
5-
QApp = puzzle.QApp
6-
73
from . import param
84
from . import readout
95
from . import action
106
from . import parse
11-
from . import threads
7+
from . import threads
8+
9+
Piece = piece.Piece
10+
Puzzle = puzzle.Puzzle
11+
QApp = puzzle.QApp
12+
13+
__all__ = [piece, puzzle, param, readout, action, parse, threads, Piece, Puzzle, QApp]

puzzlepiece/action.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from pyqtgraph.Qt import QtCore, QtWidgets
1+
from pyqtgraph.Qt import QtCore
22
from functools import wraps
33

4+
45
class Action(QtCore.QObject):
56
"""
67
An action is a function that a :class:`~puzzlepiece.piece.Piece` object can call.
@@ -19,6 +20,7 @@ class Action(QtCore.QObject):
1920
:param shortcut: A keyboard shortcut for this action, works only when the Piece is visible.
2021
:param visible: Bool flag, whether a button for the action is generated in the GUI.
2122
"""
23+
2224
#: A Qt signal emitted when the action is executed.
2325
called = QtCore.Signal()
2426

@@ -44,6 +46,7 @@ def visible(self):
4446
"""
4547
return self._visible
4648

49+
4750
def define(piece, name, shortcut=None, visible=True):
4851
"""
4952
A decorator generator for registering a :class:`~puzzlepiece.action.Action` in a Piece's
@@ -62,13 +65,16 @@ def action(self):
6265
Example: ``QtCore.Qt.Key.Key_F1``
6366
:param visible: bool flag, determined if a GUI button will be shown for this param.
6467
"""
68+
6569
def decorator(action):
6670
@wraps(action)
6771
def wrapper(*args, **kwargs):
6872
return action(piece, *args, **kwargs)
73+
6974
action_object = Action(wrapper, piece, shortcut, visible)
7075
piece.actions[name] = action_object
7176
if shortcut:
7277
piece.shortcuts[shortcut] = action_object
7378
return action_object
74-
return decorator
79+
80+
return decorator

puzzlepiece/extras/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
This sub-module contains some features that extend the base functionality of puzzlepiece.
3+
"""

puzzlepiece/extras/datagrid.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
from qtpy import QtWidgets, QtCore
2+
3+
import puzzlepiece as pzp
4+
5+
6+
class DataGrid(QtWidgets.QWidget):
7+
"""
8+
A table containing multiple :class:`~puzzlepiece.extras.datagrid.Row` objects, each acting kind of like
9+
a :class:`~puzzlepiece.piece.Piece` object, in that it has params and actions
10+
(see :class:`~puzzlepiece.param.BaseParam` and :class:`~puzzlepiece.action.Action`).
11+
12+
This is a QWidget, co it can be added to your Piece's :func:`~puzzlepiece.piece.Piece.custom_layout`
13+
or used as a standalone Widget if you know what you're doing.
14+
15+
**This is not very performant, very large numbers of Rows should be avoided!** Consider using
16+
Qt's QTableWidget instead.
17+
18+
:param row_class: The :class:`~puzzlepiece.extras.datagrid.Row` class that will be used to construct Rows.
19+
:param puzzle: (optional) The parent :class:`~puzzlepiece.puzzle.Puzzle`.
20+
"""
21+
22+
#: A Qt signal emitted when a row is added or removed.
23+
rows_changed = QtCore.Signal()
24+
#: A Qt signal emitted when any data in the DataGrid changes (including when rows are added/removed).
25+
data_changed = QtCore.Signal()
26+
27+
def __init__(self, row_class, puzzle=None):
28+
super().__init__()
29+
#: Reference to the parent :class:`~puzzlepiece.puzzle.Puzzle`.
30+
self.puzzle = puzzle or pzp.puzzle.PretendPuzzle()
31+
self._row_class = row_class
32+
row_example = row_class(self.puzzle)
33+
self.param_names = row_example.params.keys()
34+
35+
self._tree = QtWidgets.QTreeWidget()
36+
self._tree.setHeaderLabels(("ID", *row_example.params.keys(), "actions"))
37+
38+
layout = QtWidgets.QVBoxLayout()
39+
layout.addWidget(self._tree)
40+
self.setLayout(layout)
41+
42+
#: A list of Rows.
43+
self.rows = []
44+
self._items = []
45+
self._root = self._tree.invisibleRootItem()
46+
self._slots = {}
47+
self.rows_changed.connect(self.data_changed.emit)
48+
49+
@property
50+
def values(self):
51+
"""
52+
The current values for all params in the Rows (this does not invoke their getters).
53+
"""
54+
return [{key: x[key].value for key in x.params} for x in self.rows]
55+
56+
def add_row(self, **kwargs):
57+
"""
58+
Add a Row with default param values.
59+
60+
:param kwargs: keyword arguments matching param names can be passed
61+
to set param values in the new row
62+
"""
63+
item = QtWidgets.QTreeWidgetItem(self._tree, (str(len(self.rows)),))
64+
row = self._row_class(self, self.puzzle)
65+
row._populate_item(self._tree, item)
66+
self.rows.append(row)
67+
self._items.append(item)
68+
for key in kwargs:
69+
row.params[key].set_value(kwargs[key])
70+
for param_name in self.param_names:
71+
if param_name in self._slots:
72+
for slot in self._slots[param_name]:
73+
row.params[param_name].changed.connect(slot)
74+
row.params[param_name].changed.connect(self.data_changed.emit)
75+
self.rows_changed.emit()
76+
return row
77+
78+
def remove_row(self, id):
79+
"""
80+
Remove the row with the given id.
81+
82+
:param id: id of the row to remove.
83+
"""
84+
self._root.removeChild(self._items[id])
85+
del self._items[id]
86+
del self.rows[id]
87+
for i in range(len(self.rows)):
88+
self._items[i].setText(0, str(i))
89+
self.rows_changed.emit()
90+
91+
def get_index(self, row):
92+
"""
93+
Get the current index of a given :class:`~puzzlepiece.extras.datagrid.Row` object.
94+
95+
:param row: the row object.
96+
:rtype: int
97+
"""
98+
return self.rows.index(row)
99+
100+
def clear(self):
101+
"""
102+
Remove all rows.
103+
"""
104+
self._tree.clear()
105+
self.rows = []
106+
self._items = []
107+
self.rows_changed.emit()
108+
109+
def add_changed_slot(self, param_name, function):
110+
"""
111+
Connect a Slot (usually a method) to the :attr:`~puzzlepiece.param.BaseParam.changed`
112+
Signal of the given param in all the rows (including Rows added in the future).
113+
114+
:param param_name: The name of the param whose changed Signal we're connecting to.
115+
:param function: any method or other Qt Slot to connect.
116+
"""
117+
if param_name in self._slots:
118+
self._slots[param_name].append(function)
119+
else:
120+
self._slots[param_name] = [function]
121+
for row in self.rows:
122+
row.params[param_name].changed.connect(function)
123+
124+
125+
class Row:
126+
"""
127+
A Row is a template for a :class:`~puzzlepiece.extras.datagrid.DataGrid` object, holding the data (params)
128+
and actions that the DataGrid displays.
129+
130+
It acts kind of like a :class:`~puzzlepiece.piece.Piece` object, in that it has params and actions
131+
(see :class:`~puzzlepiece.param.BaseParam` and :class:`~puzzlepiece.action.Action`).
132+
133+
:param parent: (optional) The parent DataGrid.
134+
:parem puzzle: (optional) The parent Puzzle.
135+
"""
136+
137+
def __init__(self, parent=None, puzzle=None):
138+
self.puzzle = puzzle or pzp.puzzle.PretendPuzzle()
139+
self.parent = parent
140+
#: dict: A dictionary of this Row's params (see :class:`~puzzlepiece.param.BaseParam`). You can also directly index the Row object with the param name.
141+
self.params = {}
142+
#: dict: A dictionary of this Row's actions (see :class:`~puzzlepiece.action.Action`).
143+
self.actions = {}
144+
self.define_params()
145+
self.define_actions()
146+
for key in self.params:
147+
self.params[key]._main_layout.removeWidget(self.params[key].label)
148+
149+
def define_params(self):
150+
"""
151+
Override to define params using decorators from :mod:`puzzlepiece.param`.
152+
"""
153+
pass
154+
155+
def define_actions(self):
156+
"""
157+
Override to define actions using decorators from :mod:`puzzlepiece.action`.
158+
"""
159+
pass
160+
161+
def elevate(self):
162+
"""
163+
For compatibility with the Piece's API.
164+
165+
:meta private:
166+
"""
167+
pass
168+
169+
def _populate_item(self, tree, item):
170+
for i, key in enumerate(self.params):
171+
tree.setItemWidget(item, i + 1, self.params[key])
172+
tree.setItemWidget(item, i + 2, self._action_buttons())
173+
174+
def _action_buttons(self):
175+
widget = QtWidgets.QWidget()
176+
layout = QtWidgets.QHBoxLayout()
177+
widget.setLayout(layout)
178+
179+
visible_actions = [key for key in self.actions if self.actions[key].visible]
180+
for i, key in enumerate(visible_actions):
181+
button = QtWidgets.QPushButton(key)
182+
button.clicked.connect(lambda x=False, _key=key: self.actions[_key]())
183+
layout.addWidget(button)
184+
return widget
185+
186+
def __iter__(self):
187+
for key in self.params:
188+
yield key
189+
190+
def __getitem__(self, key):
191+
return self.params[key]
192+
193+
def __contains__(self, item):
194+
return item in self.params

0 commit comments

Comments
 (0)