pyside6-datatable-widget 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyside6_datatable_widget-1.0.0/PKG-INFO +237 -0
- pyside6_datatable_widget-1.0.0/README.md +209 -0
- pyside6_datatable_widget-1.0.0/datatable/__init__.py +30 -0
- pyside6_datatable_widget-1.0.0/datatable/core/__init__.py +18 -0
- pyside6_datatable_widget-1.0.0/datatable/core/base_controller.py +116 -0
- pyside6_datatable_widget-1.0.0/datatable/core/observer.py +152 -0
- pyside6_datatable_widget-1.0.0/datatable/core/widget_manager.py +121 -0
- pyside6_datatable_widget-1.0.0/datatable/models/__init__.py +26 -0
- pyside6_datatable_widget-1.0.0/datatable/models/datatable_model.py +563 -0
- pyside6_datatable_widget-1.0.0/datatable/models/delegates.py +222 -0
- pyside6_datatable_widget-1.0.0/datatable/widgets/__init__.py +17 -0
- pyside6_datatable_widget-1.0.0/datatable/widgets/datatable.py +512 -0
- pyside6_datatable_widget-1.0.0/datatable/widgets/handlers/DataTableHandler.py +263 -0
- pyside6_datatable_widget-1.0.0/datatable/widgets/handlers/__init__.py +17 -0
- pyside6_datatable_widget-1.0.0/pyside6_datatable_widget.egg-info/PKG-INFO +237 -0
- pyside6_datatable_widget-1.0.0/pyside6_datatable_widget.egg-info/SOURCES.txt +31 -0
- pyside6_datatable_widget-1.0.0/pyside6_datatable_widget.egg-info/dependency_links.txt +1 -0
- pyside6_datatable_widget-1.0.0/pyside6_datatable_widget.egg-info/requires.txt +1 -0
- pyside6_datatable_widget-1.0.0/pyside6_datatable_widget.egg-info/top_level.txt +1 -0
- pyside6_datatable_widget-1.0.0/setup.cfg +4 -0
- pyside6_datatable_widget-1.0.0/setup.py +47 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyside6-datatable-widget
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A PySide6 DataTable widget with jQuery DataTable-like functionality
|
|
5
|
+
Home-page: https://github.com/ultra-bugs/pyside6-datatable-widget
|
|
6
|
+
Author: Zuko
|
|
7
|
+
Author-email: tansautn@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/ultra-bugs/pyside6-datatable-widget/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: PySide6>=6.1.0
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: project-url
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# PySide6 DataTable
|
|
30
|
+
|
|
31
|
+
A powerful DataTable widget for PySide6 applications with functionality similar to jQuery DataTable.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Customizable Table**: Easily configure columns, types, and formatting
|
|
36
|
+
- **Data Type Detection**: Automatically detect and handle different data types
|
|
37
|
+
- **Type-based Sorting**: Different column types sort appropriately
|
|
38
|
+
- **Search Functionality**: Global and column-specific search
|
|
39
|
+
- **Row Collapsing**: Support for expandable/collapsible rows
|
|
40
|
+
- **Pagination**: Built-in pagination with configurable page sizes
|
|
41
|
+
- **Column Visibility**: Show/hide columns easily
|
|
42
|
+
- **Aggregation Functions**: Calculate sums, averages, percentages, etc.
|
|
43
|
+
- **Custom Formatting**: Format data display for different column types
|
|
44
|
+
- **Observer Pattern**: Event-driven architecture for clear code organization
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install pyside6-datatable-widget
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Basic Usage
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import sys
|
|
56
|
+
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
|
57
|
+
from datatable import DataTable, DataType
|
|
58
|
+
|
|
59
|
+
class MainWindow(QMainWindow):
|
|
60
|
+
def __init__(self):
|
|
61
|
+
super().__init__()
|
|
62
|
+
self.setWindowTitle("DataTable Example")
|
|
63
|
+
self.resize(800, 600)
|
|
64
|
+
|
|
65
|
+
# Create central widget and layout
|
|
66
|
+
central_widget = QWidget()
|
|
67
|
+
self.setCentralWidget(central_widget)
|
|
68
|
+
layout = QVBoxLayout(central_widget)
|
|
69
|
+
|
|
70
|
+
# Create DataTable
|
|
71
|
+
self.data_table = DataTable()
|
|
72
|
+
layout.addWidget(self.data_table)
|
|
73
|
+
|
|
74
|
+
# Set up columns (key, header, data_type)
|
|
75
|
+
columns = [
|
|
76
|
+
("id", "ID", DataType.NUMERIC),
|
|
77
|
+
("name", "Name", DataType.STRING),
|
|
78
|
+
("age", "Age", DataType.NUMERIC),
|
|
79
|
+
("active", "Active", DataType.BOOLEAN)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# Set up data
|
|
83
|
+
data = [
|
|
84
|
+
{"id": 1, "name": "John", "age": 30, "active": True},
|
|
85
|
+
{"id": 2, "name": "Jane", "age": 25, "active": False},
|
|
86
|
+
{"id": 3, "name": "Bob", "age": 40, "active": True}
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
# Apply to table
|
|
90
|
+
self.data_table.setColumns(columns)
|
|
91
|
+
self.data_table.setData(data)
|
|
92
|
+
|
|
93
|
+
# Connect signals
|
|
94
|
+
self.data_table.rowSelected.connect(self.on_row_selected)
|
|
95
|
+
|
|
96
|
+
def on_row_selected(self, row, row_data):
|
|
97
|
+
print(f"Row {row} selected: {row_data}")
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
app = QApplication(sys.argv)
|
|
101
|
+
window = MainWindow()
|
|
102
|
+
window.show()
|
|
103
|
+
sys.exit(app.exec())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Advanced Features
|
|
107
|
+
|
|
108
|
+
### Custom Column Formatting
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from datatable import DataTableModel
|
|
112
|
+
|
|
113
|
+
# Create model with custom formatting
|
|
114
|
+
model = DataTableModel()
|
|
115
|
+
model.setFormattingFunction("price", lambda value: f"${value:.2f}")
|
|
116
|
+
model.setFormattingFunction("percentage", lambda value: f"{value:.1f}%")
|
|
117
|
+
|
|
118
|
+
# Set model to table
|
|
119
|
+
data_table.setModel(model)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Row Collapsing
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# Enable row collapsing
|
|
126
|
+
data_table.enableRowCollapsing(True, "subrows")
|
|
127
|
+
|
|
128
|
+
# Example data with subrows
|
|
129
|
+
data = [
|
|
130
|
+
{
|
|
131
|
+
"id": 1,
|
|
132
|
+
"name": "Category A",
|
|
133
|
+
"total": 1000,
|
|
134
|
+
"subrows": [
|
|
135
|
+
{"id": 101, "name": "Item A1", "total": 500},
|
|
136
|
+
{"id": 102, "name": "Item A2", "total": 500}
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": 2,
|
|
141
|
+
"name": "Category B",
|
|
142
|
+
"total": 2000,
|
|
143
|
+
"subrows": [
|
|
144
|
+
{"id": 201, "name": "Item B1", "total": 1200},
|
|
145
|
+
{"id": 202, "name": "Item B2", "total": 800}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
data_table.setData(data)
|
|
151
|
+
|
|
152
|
+
# Connect expansion signals
|
|
153
|
+
data_table.rowExpanded.connect(lambda row, data: print(f"Row {row} expanded"))
|
|
154
|
+
data_table.rowCollapsed.connect(lambda row, data: print(f"Row {row} collapsed"))
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Aggregation Functions
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# Get aggregate values
|
|
161
|
+
total = data_table.getAggregateValue("amount", "sum")
|
|
162
|
+
average = data_table.getAggregateValue("amount", "avg")
|
|
163
|
+
count = data_table.getAggregateValue("id", "count")
|
|
164
|
+
|
|
165
|
+
# Calculate percentage of a row value relative to total
|
|
166
|
+
row_data = data_table.getSelectedRow()
|
|
167
|
+
if row_data:
|
|
168
|
+
amount = row_data["amount"]
|
|
169
|
+
total = data_table.getAggregateValue("amount", "sum")
|
|
170
|
+
percentage = data_table.calculateRowPercentage(row_index, "amount")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Custom Search Functions
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Set custom search function for a column
|
|
177
|
+
model.setSearchFunction("complex_data", lambda value, term: term in str(value["name"]))
|
|
178
|
+
|
|
179
|
+
# Search in table
|
|
180
|
+
data_table.search("search term")
|
|
181
|
+
|
|
182
|
+
# Search specific column
|
|
183
|
+
matching_rows = model.searchColumn("name", "John")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## API Reference
|
|
187
|
+
|
|
188
|
+
### DataTable
|
|
189
|
+
|
|
190
|
+
Main widget class that provides the UI and functionality.
|
|
191
|
+
|
|
192
|
+
#### Methods
|
|
193
|
+
|
|
194
|
+
- `setData(data)`: Set table data
|
|
195
|
+
- `setColumns(columns)`: Set table columns
|
|
196
|
+
- `setVisibleColumns(columns)`: Set which columns are visible
|
|
197
|
+
- `enableRowCollapsing(enabled, child_row_key)`: Enable/disable row collapsing
|
|
198
|
+
- `search(term)`: Search the table
|
|
199
|
+
- `sort(column_key, order)`: Sort the table
|
|
200
|
+
- `setPage(page)`: Set current page
|
|
201
|
+
- `setRowsPerPage(rows)`: Set rows per page
|
|
202
|
+
- `getData()`: Get current table data
|
|
203
|
+
- `getSelectedRow()`: Get selected row data
|
|
204
|
+
- `getAggregateValue(column_key, agg_type)`: Get aggregate value for column
|
|
205
|
+
|
|
206
|
+
#### Signals
|
|
207
|
+
|
|
208
|
+
- `pageChanged(page)`: Emitted when page changes
|
|
209
|
+
- `rowSelected(row, row_data)`: Emitted when row is selected
|
|
210
|
+
- `rowExpanded(row, row_data)`: Emitted when row is expanded
|
|
211
|
+
- `rowCollapsed(row, row_data)`: Emitted when row is collapsed
|
|
212
|
+
- `dataFiltered(rows)`: Emitted when data is filtered
|
|
213
|
+
- `sortChanged(column, order)`: Emitted when sort order changes
|
|
214
|
+
|
|
215
|
+
### DataTableModel
|
|
216
|
+
|
|
217
|
+
Model class that manages data and operations.
|
|
218
|
+
|
|
219
|
+
#### Methods
|
|
220
|
+
|
|
221
|
+
- `setData(data)`: Set model data
|
|
222
|
+
- `setColumns(columns)`: Set model columns
|
|
223
|
+
- `setFormattingFunction(column_key, func)`: Set formatting function
|
|
224
|
+
- `setEditableColumns(editable_columns)`: Set which columns are editable
|
|
225
|
+
- `setVisibleColumns(visible_columns)`: Set which columns are visible
|
|
226
|
+
- `setSearchFunction(column_key, func)`: Set search function
|
|
227
|
+
- `setSortFunction(column_key, func)`: Set sort function
|
|
228
|
+
- `setAggregationFunction(column_key, agg_type, func)`: Set aggregation function
|
|
229
|
+
- `enableRowCollapsing(enabled, child_row_key)`: Enable row collapsing
|
|
230
|
+
- `search(term)`: Search all rows
|
|
231
|
+
- `searchColumn(column_key, term)`: Search specific column
|
|
232
|
+
- `aggregate(column_key, agg_type)`: Aggregate column values
|
|
233
|
+
- `calculateRowPercentage(row_index, column_key)`: Calculate row percentage
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# PySide6 DataTable
|
|
2
|
+
|
|
3
|
+
A powerful DataTable widget for PySide6 applications with functionality similar to jQuery DataTable.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Customizable Table**: Easily configure columns, types, and formatting
|
|
8
|
+
- **Data Type Detection**: Automatically detect and handle different data types
|
|
9
|
+
- **Type-based Sorting**: Different column types sort appropriately
|
|
10
|
+
- **Search Functionality**: Global and column-specific search
|
|
11
|
+
- **Row Collapsing**: Support for expandable/collapsible rows
|
|
12
|
+
- **Pagination**: Built-in pagination with configurable page sizes
|
|
13
|
+
- **Column Visibility**: Show/hide columns easily
|
|
14
|
+
- **Aggregation Functions**: Calculate sums, averages, percentages, etc.
|
|
15
|
+
- **Custom Formatting**: Format data display for different column types
|
|
16
|
+
- **Observer Pattern**: Event-driven architecture for clear code organization
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install pyside6-datatable-widget
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Basic Usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import sys
|
|
28
|
+
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
|
29
|
+
from datatable import DataTable, DataType
|
|
30
|
+
|
|
31
|
+
class MainWindow(QMainWindow):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.setWindowTitle("DataTable Example")
|
|
35
|
+
self.resize(800, 600)
|
|
36
|
+
|
|
37
|
+
# Create central widget and layout
|
|
38
|
+
central_widget = QWidget()
|
|
39
|
+
self.setCentralWidget(central_widget)
|
|
40
|
+
layout = QVBoxLayout(central_widget)
|
|
41
|
+
|
|
42
|
+
# Create DataTable
|
|
43
|
+
self.data_table = DataTable()
|
|
44
|
+
layout.addWidget(self.data_table)
|
|
45
|
+
|
|
46
|
+
# Set up columns (key, header, data_type)
|
|
47
|
+
columns = [
|
|
48
|
+
("id", "ID", DataType.NUMERIC),
|
|
49
|
+
("name", "Name", DataType.STRING),
|
|
50
|
+
("age", "Age", DataType.NUMERIC),
|
|
51
|
+
("active", "Active", DataType.BOOLEAN)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Set up data
|
|
55
|
+
data = [
|
|
56
|
+
{"id": 1, "name": "John", "age": 30, "active": True},
|
|
57
|
+
{"id": 2, "name": "Jane", "age": 25, "active": False},
|
|
58
|
+
{"id": 3, "name": "Bob", "age": 40, "active": True}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Apply to table
|
|
62
|
+
self.data_table.setColumns(columns)
|
|
63
|
+
self.data_table.setData(data)
|
|
64
|
+
|
|
65
|
+
# Connect signals
|
|
66
|
+
self.data_table.rowSelected.connect(self.on_row_selected)
|
|
67
|
+
|
|
68
|
+
def on_row_selected(self, row, row_data):
|
|
69
|
+
print(f"Row {row} selected: {row_data}")
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
app = QApplication(sys.argv)
|
|
73
|
+
window = MainWindow()
|
|
74
|
+
window.show()
|
|
75
|
+
sys.exit(app.exec())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Advanced Features
|
|
79
|
+
|
|
80
|
+
### Custom Column Formatting
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from datatable import DataTableModel
|
|
84
|
+
|
|
85
|
+
# Create model with custom formatting
|
|
86
|
+
model = DataTableModel()
|
|
87
|
+
model.setFormattingFunction("price", lambda value: f"${value:.2f}")
|
|
88
|
+
model.setFormattingFunction("percentage", lambda value: f"{value:.1f}%")
|
|
89
|
+
|
|
90
|
+
# Set model to table
|
|
91
|
+
data_table.setModel(model)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Row Collapsing
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Enable row collapsing
|
|
98
|
+
data_table.enableRowCollapsing(True, "subrows")
|
|
99
|
+
|
|
100
|
+
# Example data with subrows
|
|
101
|
+
data = [
|
|
102
|
+
{
|
|
103
|
+
"id": 1,
|
|
104
|
+
"name": "Category A",
|
|
105
|
+
"total": 1000,
|
|
106
|
+
"subrows": [
|
|
107
|
+
{"id": 101, "name": "Item A1", "total": 500},
|
|
108
|
+
{"id": 102, "name": "Item A2", "total": 500}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": 2,
|
|
113
|
+
"name": "Category B",
|
|
114
|
+
"total": 2000,
|
|
115
|
+
"subrows": [
|
|
116
|
+
{"id": 201, "name": "Item B1", "total": 1200},
|
|
117
|
+
{"id": 202, "name": "Item B2", "total": 800}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
data_table.setData(data)
|
|
123
|
+
|
|
124
|
+
# Connect expansion signals
|
|
125
|
+
data_table.rowExpanded.connect(lambda row, data: print(f"Row {row} expanded"))
|
|
126
|
+
data_table.rowCollapsed.connect(lambda row, data: print(f"Row {row} collapsed"))
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Aggregation Functions
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Get aggregate values
|
|
133
|
+
total = data_table.getAggregateValue("amount", "sum")
|
|
134
|
+
average = data_table.getAggregateValue("amount", "avg")
|
|
135
|
+
count = data_table.getAggregateValue("id", "count")
|
|
136
|
+
|
|
137
|
+
# Calculate percentage of a row value relative to total
|
|
138
|
+
row_data = data_table.getSelectedRow()
|
|
139
|
+
if row_data:
|
|
140
|
+
amount = row_data["amount"]
|
|
141
|
+
total = data_table.getAggregateValue("amount", "sum")
|
|
142
|
+
percentage = data_table.calculateRowPercentage(row_index, "amount")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Search Functions
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Set custom search function for a column
|
|
149
|
+
model.setSearchFunction("complex_data", lambda value, term: term in str(value["name"]))
|
|
150
|
+
|
|
151
|
+
# Search in table
|
|
152
|
+
data_table.search("search term")
|
|
153
|
+
|
|
154
|
+
# Search specific column
|
|
155
|
+
matching_rows = model.searchColumn("name", "John")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## API Reference
|
|
159
|
+
|
|
160
|
+
### DataTable
|
|
161
|
+
|
|
162
|
+
Main widget class that provides the UI and functionality.
|
|
163
|
+
|
|
164
|
+
#### Methods
|
|
165
|
+
|
|
166
|
+
- `setData(data)`: Set table data
|
|
167
|
+
- `setColumns(columns)`: Set table columns
|
|
168
|
+
- `setVisibleColumns(columns)`: Set which columns are visible
|
|
169
|
+
- `enableRowCollapsing(enabled, child_row_key)`: Enable/disable row collapsing
|
|
170
|
+
- `search(term)`: Search the table
|
|
171
|
+
- `sort(column_key, order)`: Sort the table
|
|
172
|
+
- `setPage(page)`: Set current page
|
|
173
|
+
- `setRowsPerPage(rows)`: Set rows per page
|
|
174
|
+
- `getData()`: Get current table data
|
|
175
|
+
- `getSelectedRow()`: Get selected row data
|
|
176
|
+
- `getAggregateValue(column_key, agg_type)`: Get aggregate value for column
|
|
177
|
+
|
|
178
|
+
#### Signals
|
|
179
|
+
|
|
180
|
+
- `pageChanged(page)`: Emitted when page changes
|
|
181
|
+
- `rowSelected(row, row_data)`: Emitted when row is selected
|
|
182
|
+
- `rowExpanded(row, row_data)`: Emitted when row is expanded
|
|
183
|
+
- `rowCollapsed(row, row_data)`: Emitted when row is collapsed
|
|
184
|
+
- `dataFiltered(rows)`: Emitted when data is filtered
|
|
185
|
+
- `sortChanged(column, order)`: Emitted when sort order changes
|
|
186
|
+
|
|
187
|
+
### DataTableModel
|
|
188
|
+
|
|
189
|
+
Model class that manages data and operations.
|
|
190
|
+
|
|
191
|
+
#### Methods
|
|
192
|
+
|
|
193
|
+
- `setData(data)`: Set model data
|
|
194
|
+
- `setColumns(columns)`: Set model columns
|
|
195
|
+
- `setFormattingFunction(column_key, func)`: Set formatting function
|
|
196
|
+
- `setEditableColumns(editable_columns)`: Set which columns are editable
|
|
197
|
+
- `setVisibleColumns(visible_columns)`: Set which columns are visible
|
|
198
|
+
- `setSearchFunction(column_key, func)`: Set search function
|
|
199
|
+
- `setSortFunction(column_key, func)`: Set sort function
|
|
200
|
+
- `setAggregationFunction(column_key, agg_type, func)`: Set aggregation function
|
|
201
|
+
- `enableRowCollapsing(enabled, child_row_key)`: Enable row collapsing
|
|
202
|
+
- `search(term)`: Search all rows
|
|
203
|
+
- `searchColumn(column_key, term)`: Search specific column
|
|
204
|
+
- `aggregate(column_key, agg_type)`: Aggregate column values
|
|
205
|
+
- `calculateRowPercentage(row_index, column_key)`: Calculate row percentage
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# M""""""""`M dP
|
|
2
|
+
# Mmmmmm .M 88
|
|
3
|
+
# MMMMP .MMM dP dP 88 .dP .d8888b.
|
|
4
|
+
# MMP .MMMMM 88 88 88888" 88' `88
|
|
5
|
+
# M' .MMMMMMM 88. .88 88 `8b. 88. .88
|
|
6
|
+
# M M `88888P' dP `YP `88888P'
|
|
7
|
+
# MMMMMMMMMMM -*- Created by Zuko -*-
|
|
8
|
+
#
|
|
9
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
10
|
+
# * - - - F.R.E.E.M.I.N.D - - - *
|
|
11
|
+
# * - Copyright © 2025 (Z) Programing - *
|
|
12
|
+
# * - - All Rights Reserved - - *
|
|
13
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
14
|
+
|
|
15
|
+
from .widgets.datatable import DataTable
|
|
16
|
+
from .models.datatable_model import DataTableModel, DataType, SortOrder
|
|
17
|
+
from .models.delegates import CellDelegate, NumericDelegate, DateDelegate, BooleanDelegate
|
|
18
|
+
|
|
19
|
+
__version__ = '1.0.0'
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'DataTable',
|
|
23
|
+
'DataTableModel',
|
|
24
|
+
'DataType',
|
|
25
|
+
'SortOrder',
|
|
26
|
+
'CellDelegate',
|
|
27
|
+
'NumericDelegate',
|
|
28
|
+
'DateDelegate',
|
|
29
|
+
'BooleanDelegate',
|
|
30
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# M""""""""`M dP
|
|
2
|
+
# Mmmmmm .M 88
|
|
3
|
+
# MMMMP .MMM dP dP 88 .dP .d8888b.
|
|
4
|
+
# MMP .MMMMM 88 88 88888" 88' `88
|
|
5
|
+
# M' .MMMMMMM 88. .88 88 `8b. 88. .88
|
|
6
|
+
# M M `88888P' dP `YP `88888P'
|
|
7
|
+
# MMMMMMMMMMM -*- Created by Zuko -*-
|
|
8
|
+
#
|
|
9
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
10
|
+
# * - - - F.R.E.E.M.I.N.D - - - *
|
|
11
|
+
# * - Copyright © 2025 (Z) Programing - *
|
|
12
|
+
# * - - All Rights Reserved - - *
|
|
13
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
14
|
+
|
|
15
|
+
from .observer import Publisher, Subscriber, singleton
|
|
16
|
+
from .widget_manager import WidgetManager
|
|
17
|
+
|
|
18
|
+
__all__ = ['Publisher', 'Subscriber', 'singleton', 'WidgetManager']
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# M""""""""`M dP
|
|
2
|
+
# Mmmmmm .M 88
|
|
3
|
+
# MMMMP .MMM dP dP 88 .dP .d8888b.
|
|
4
|
+
# MMP .MMMMM 88 88 88888" 88' `88
|
|
5
|
+
# M' .MMMMMMM 88. .88 88 `8b. 88. .88
|
|
6
|
+
# M M `88888P' dP `YP `88888P'
|
|
7
|
+
# MMMMMMMMMMM -*- Created by Zuko -*-
|
|
8
|
+
#
|
|
9
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
10
|
+
# * - - - F.R.E.E.M.I.N.D - - - *
|
|
11
|
+
# * - Copyright © 2025 (Z) Programing - *
|
|
12
|
+
# * - - All Rights Reserved - - *
|
|
13
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from typing import Dict, List, Any
|
|
18
|
+
|
|
19
|
+
from PySide6.QtWidgets import QWidget
|
|
20
|
+
|
|
21
|
+
from .observer import Publisher
|
|
22
|
+
from .widget_manager import WidgetManager
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ControllerMeta(type(QWidget), type(ABC)):
|
|
26
|
+
required_attrs = ['slot_map']
|
|
27
|
+
|
|
28
|
+
def __new__(cls, name, bases, dct):
|
|
29
|
+
required_attrs = cls.required_attrs
|
|
30
|
+
for attr in required_attrs:
|
|
31
|
+
if attr not in dct:
|
|
32
|
+
raise ValueError(f'Attribute: {attr} is required but not defined in {name}')
|
|
33
|
+
return super().__new__(cls, name, bases, dct)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseController(QWidget, ABC, metaclass=ControllerMeta):
|
|
37
|
+
"""Base class for all controllers in datatable package"""
|
|
38
|
+
|
|
39
|
+
slot_map: Dict[str, List[str]] = {}
|
|
40
|
+
signal_connected = False
|
|
41
|
+
is_auto_connect_signal = True
|
|
42
|
+
|
|
43
|
+
def __init__(self, parent=None):
|
|
44
|
+
super().__init__(parent)
|
|
45
|
+
self.widget_manager = WidgetManager(self)
|
|
46
|
+
self.controller_name = self.__class__.__name__
|
|
47
|
+
self.publisher = Publisher()
|
|
48
|
+
self.handler = None
|
|
49
|
+
|
|
50
|
+
# Setup UI
|
|
51
|
+
self.setupUi(self)
|
|
52
|
+
|
|
53
|
+
if not self.is_auto_connect_signal:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# Auto-loading handler
|
|
57
|
+
module_path = self.__module__
|
|
58
|
+
module_parts = module_path.split('.')
|
|
59
|
+
if len(module_parts) > 1:
|
|
60
|
+
# Try to find handler in the same package
|
|
61
|
+
base_module = '.'.join(module_parts[:-1])
|
|
62
|
+
handler_module_name = f'{base_module}.handlers.{self.controller_name}Handler'
|
|
63
|
+
print(handler_module_name)
|
|
64
|
+
try:
|
|
65
|
+
handler_module = importlib.import_module(handler_module_name)
|
|
66
|
+
handler_class = getattr(handler_module, f'{self.controller_name}Handler')
|
|
67
|
+
self.handler = handler_class(
|
|
68
|
+
widget_manager=self.widget_manager, events=list(self.slot_map.keys())
|
|
69
|
+
)
|
|
70
|
+
except (ImportError, AttributeError):
|
|
71
|
+
# If not found, try alternative locations
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
if self.is_auto_connect_signal and self.handler:
|
|
75
|
+
self._connect_signals()
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def setupUi(self, widget):
|
|
79
|
+
"""Set up the UI components"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def _connect_signals(self):
|
|
83
|
+
"""Connect signals to slots based on slot_map"""
|
|
84
|
+
if not hasattr(self, 'slot_map'):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f'{self.__class__.__name__} must define slot_map to use auto connect signals'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
subscriber = self.handler
|
|
90
|
+
for event in self.handler.events:
|
|
91
|
+
if event in self.slot_map:
|
|
92
|
+
signal_info = self.slot_map.get(event)
|
|
93
|
+
if signal_info is None:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
if callable(signal_info):
|
|
97
|
+
signal_info(self.handler, self.publisher)
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
widget = self.widget_manager.get(signal_info[0])
|
|
102
|
+
self.publisher.connect(
|
|
103
|
+
widget,
|
|
104
|
+
signal_info[1],
|
|
105
|
+
event,
|
|
106
|
+
data={'widget': widget},
|
|
107
|
+
)
|
|
108
|
+
print(f'Connected {signal_info[1]} signal to {event} event')
|
|
109
|
+
print(widget, event, signal_info)
|
|
110
|
+
except (AttributeError, Exception) as e:
|
|
111
|
+
print(f'Error connecting signal: {e}')
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
self.publisher.subscribe(subscriber=subscriber, event=event)
|
|
115
|
+
|
|
116
|
+
self.signal_connected = True
|