ms-components 0.2.0__tar.gz → 0.3.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.
- {ms_components-0.2.0 → ms_components-0.3.0}/PKG-INFO +11 -1
- {ms_components-0.2.0 → ms_components-0.3.0}/README.md +9 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/docs/api_reference.md +8 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/docs/examples.md +18 -16
- {ms_components-0.2.0 → ms_components-0.3.0}/docs/index.md +3 -1
- ms_components-0.3.0/docs/ms_tutorial.md +160 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/docs/usage.md +27 -9
- ms_components-0.3.0/examples/tutorial_demo.py +74 -0
- ms_components-0.3.0/examples/tutorial_demo.toml +21 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/pyproject.toml +1 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/base.qss +24 -0
- ms_components-0.3.0/src/ms_components/chips.py +279 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/widget.py +9 -35
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/widget.py +11 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/cell_delegate.py +29 -1
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/metric_filter_bar.py +7 -3
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/smart_table_view.py +13 -7
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/table_config.py +2 -0
- ms_components-0.3.0/src/ms_components/ms_tutorial/__init__.py +9 -0
- ms_components-0.3.0/src/ms_components/ms_tutorial/bindings.py +21 -0
- ms_components-0.3.0/src/ms_components/ms_tutorial/controller.py +257 -0
- ms_components-0.3.0/src/ms_components/ms_tutorial/models.py +58 -0
- ms_components-0.3.0/src/ms_components/ms_tutorial/overlay.py +154 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/PKG-INFO +11 -1
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/SOURCES.txt +14 -1
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/scm_file_list.json +13 -0
- ms_components-0.3.0/src/ms_components.egg-info/scm_version.json +8 -0
- ms_components-0.3.0/tests/test_chips.py +16 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_dock_window.py +9 -15
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_ms_projects_widget.py +19 -1
- ms_components-0.3.0/tests/test_progress_column.py +36 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_project_browser.py +4 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_query_builder_keyset.py +28 -0
- ms_components-0.3.0/tests/test_split_button_style.py +21 -0
- ms_components-0.3.0/tests/test_tutorial.py +384 -0
- ms_components-0.2.0/src/ms_components.egg-info/scm_version.json +0 -8
- {ms_components-0.2.0 → ms_components-0.3.0}/.github/workflows/docs.yml +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/.github/workflows/publish.yml +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/.gitignore +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/LICENSE +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/setup.cfg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/listview.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_executors.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/bridge.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/cards.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/config.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/detail_panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/formatting.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/job_cards.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/models.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/palette.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/polling.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/widget.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/app.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/browser.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/scratch.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/box.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/center.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/display.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/plugins.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/widget.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_settings/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_settings/panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_stepper.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/__init__.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/action_header.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/column_panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/example_usage.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/filter_panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/columns.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/export.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/filter.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/menu.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/reload.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/search.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/settings.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort.svg +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/pagination_control.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/query_builder.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/session_provider.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/sort_panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/table_model.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/test_external_filters.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/test_toolbar.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/pagination.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/palettes.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/step_dialog.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/theme.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/tool_panel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/wheel.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/dependency_links.txt +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/requires.txt +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/top_level.txt +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_app_settings.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_executors_ui.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_molsuite_monitor_bridge.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_molsuite_monitor_widgets.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_monitor_config.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_pymol_toolbar.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_shift_hscroll.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_step_dialog.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_table_toolbar.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_table_view_state.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_theme_contrast.py +0 -0
- {ms_components-0.2.0 → ms_components-0.3.0}/zensical.toml +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: PySide6 UI components for MolSuite: smart table, project browser, job monitor, PyMOL dock, steppers.
|
|
5
5
|
Author: Mario S. Valdés-Tresanco, Mario E. Valdés-Tresanco
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/MolSuite/ms_components
|
|
8
|
+
Project-URL: Documentation, https://molsuite.github.io/ms_components/
|
|
8
9
|
Project-URL: Issues, https://github.com/MolSuite/ms_components/issues
|
|
9
10
|
Keywords: pyside6,qt,widgets,molsuite,table,monitor
|
|
10
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -30,6 +31,10 @@ Dynamic: license-file
|
|
|
30
31
|
PySide6 UI components for MolSuite: smart table, project browser, job monitor,
|
|
31
32
|
PyMOL dock, steppers and the shared theming (`themes/` + `base.qss`).
|
|
32
33
|
|
|
34
|
+
[`ms_tutorial`](docs/ms_tutorial.md) provides informative and interactive tours:
|
|
35
|
+
Python binds existing widgets/signals, while content can be defined in Python or
|
|
36
|
+
TOML. Try `PYTHONPATH=src python examples/tutorial_demo.py` from a source checkout.
|
|
37
|
+
|
|
33
38
|
Part of the [MolSuite](https://molsuite.github.io/) stack. Built on top of
|
|
34
39
|
[`ms_flow`](https://github.com/MolSuite/ms_flow).
|
|
35
40
|
|
|
@@ -42,6 +47,11 @@ PyPI (`>=3.2.0a0`):
|
|
|
42
47
|
pip install ms_components
|
|
43
48
|
```
|
|
44
49
|
|
|
50
|
+
## Documentation
|
|
51
|
+
|
|
52
|
+
<https://molsuite.github.io/ms_components/>: a guide to each component, examples,
|
|
53
|
+
and the API reference.
|
|
54
|
+
|
|
45
55
|
## License
|
|
46
56
|
|
|
47
57
|
MIT — see [LICENSE](LICENSE). Third-party icon/asset licenses live in `licenses/`.
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
PySide6 UI components for MolSuite: smart table, project browser, job monitor,
|
|
4
4
|
PyMOL dock, steppers and the shared theming (`themes/` + `base.qss`).
|
|
5
5
|
|
|
6
|
+
[`ms_tutorial`](docs/ms_tutorial.md) provides informative and interactive tours:
|
|
7
|
+
Python binds existing widgets/signals, while content can be defined in Python or
|
|
8
|
+
TOML. Try `PYTHONPATH=src python examples/tutorial_demo.py` from a source checkout.
|
|
9
|
+
|
|
6
10
|
Part of the [MolSuite](https://molsuite.github.io/) stack. Built on top of
|
|
7
11
|
[`ms_flow`](https://github.com/MolSuite/ms_flow).
|
|
8
12
|
|
|
@@ -15,6 +19,11 @@ PyPI (`>=3.2.0a0`):
|
|
|
15
19
|
pip install ms_components
|
|
16
20
|
```
|
|
17
21
|
|
|
22
|
+
## Documentation
|
|
23
|
+
|
|
24
|
+
<https://molsuite.github.io/ms_components/>: a guide to each component, examples,
|
|
25
|
+
and the API reference.
|
|
26
|
+
|
|
18
27
|
## License
|
|
19
28
|
|
|
20
29
|
MIT — see [LICENSE](LICENSE). Third-party icon/asset licenses live in `licenses/`.
|
|
@@ -11,6 +11,10 @@ Names beginning with an underscore are implementation details.
|
|
|
11
11
|
|
|
12
12
|
::: ms_components.ms_monitor
|
|
13
13
|
|
|
14
|
+
## Hosts and executors
|
|
15
|
+
|
|
16
|
+
::: ms_components.ms_executors
|
|
17
|
+
|
|
14
18
|
## Projects
|
|
15
19
|
|
|
16
20
|
::: ms_components.ms_projects
|
|
@@ -27,6 +31,10 @@ Names beginning with an underscore are implementation details.
|
|
|
27
31
|
|
|
28
32
|
::: ms_components.ms_stepper
|
|
29
33
|
|
|
34
|
+
## Guided tutorials
|
|
35
|
+
|
|
36
|
+
::: ms_components.ms_tutorial
|
|
37
|
+
|
|
30
38
|
## Tool panel
|
|
31
39
|
|
|
32
40
|
::: ms_components.tool_panel
|
|
@@ -88,18 +88,25 @@ app.exec()
|
|
|
88
88
|
|
|
89
89
|
## Job monitor for a running job
|
|
90
90
|
|
|
91
|
-
Attach the monitor to a `
|
|
92
|
-
must live in an importable module:
|
|
91
|
+
Attach the monitor to a `Workspace` and watch a job advance. The task and the
|
|
92
|
+
job must live in an importable module:
|
|
93
93
|
|
|
94
94
|
```python
|
|
95
95
|
# workers.py
|
|
96
96
|
import time
|
|
97
97
|
|
|
98
|
+
from ms_flow import file_sink, job, task
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
|
|
101
|
+
@task()
|
|
102
|
+
def slow_square(value: int) -> dict:
|
|
100
103
|
time.sleep(0.1)
|
|
101
|
-
value = int(payload["value"])
|
|
102
104
|
return {"value": value, "square": value * value}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@job(task=slow_square, output=file_sink("squares.json"))
|
|
108
|
+
def slow_squares(params, project):
|
|
109
|
+
yield from range(100)
|
|
103
110
|
```
|
|
104
111
|
|
|
105
112
|
```python
|
|
@@ -107,26 +114,21 @@ from PySide6.QtWidgets import QApplication
|
|
|
107
114
|
|
|
108
115
|
from ms_components.ms_monitor import MolSuiteMonitorBridge, MolSuiteMonitorWidget
|
|
109
116
|
from ms_components.theme import apply_theme
|
|
110
|
-
from ms_flow
|
|
111
|
-
from workers import
|
|
117
|
+
from ms_flow import Workspace
|
|
118
|
+
from workers import slow_squares
|
|
112
119
|
|
|
113
120
|
app = QApplication([])
|
|
114
121
|
apply_theme("auto", app)
|
|
115
122
|
|
|
116
|
-
ms =
|
|
123
|
+
ms = Workspace(app_id="monitor-demo")
|
|
117
124
|
ms.create_or_open_project(name="monitor-demo", folder="./monitor-demo", activate=True)
|
|
118
125
|
|
|
119
|
-
bridge = MolSuiteMonitorBridge(
|
|
126
|
+
bridge = MolSuiteMonitorBridge(workspace=ms, poll_ms=500)
|
|
120
127
|
monitor = MolSuiteMonitorWidget(bridge=bridge)
|
|
121
128
|
bridge.start()
|
|
122
|
-
app.aboutToQuit.connect(bridge.stop) # stop polling before the
|
|
123
|
-
|
|
124
|
-
ms.
|
|
125
|
-
name="slow-squares",
|
|
126
|
-
input=[{"value": value} for value in range(100)],
|
|
127
|
-
process=slow_square,
|
|
128
|
-
executor="thread",
|
|
129
|
-
)
|
|
129
|
+
app.aboutToQuit.connect(bridge.stop) # stop polling before the workspace shuts down
|
|
130
|
+
|
|
131
|
+
ms.submit(slow_squares, executor="thread")
|
|
130
132
|
|
|
131
133
|
monitor.resize(1100, 650)
|
|
132
134
|
monitor.show()
|
|
@@ -19,11 +19,13 @@ pip install ms_components
|
|
|
19
19
|
| Module | Responsibility |
|
|
20
20
|
| --- | --- |
|
|
21
21
|
| `ms_components.ms_table` | Declarative, paginated table over SQLModel |
|
|
22
|
-
| `ms_components.ms_monitor` | Job
|
|
22
|
+
| `ms_components.ms_monitor` | Job monitor for an `ms_flow` workspace: cancel, resume, move |
|
|
23
|
+
| `ms_components.ms_executors` | Hosts and executors: Settings pages and the panel that starts them |
|
|
23
24
|
| `ms_components.ms_projects` | Project browser and project menu |
|
|
24
25
|
| `ms_components.ms_settings` | Typed editor for application settings |
|
|
25
26
|
| `ms_components.ms_pymol` | PyMOL dock and control bar |
|
|
26
27
|
| `ms_components.ms_stepper` | Step-by-step wizard widget |
|
|
28
|
+
| [`ms_components.ms_tutorial`](ms_tutorial.md) | Informative and interactive tours, with Python bindings and optional TOML content |
|
|
27
29
|
| `ms_components.tool_panel` | Configure-then-run frame shared by tools |
|
|
28
30
|
| `ms_components.step_dialog` | Dialog that runs provisioning steps off the GUI thread |
|
|
29
31
|
| `ms_components.theme` | Themes, palette roles, and fonts |
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Guided tutorials
|
|
2
|
+
|
|
3
|
+
`ms_components.ms_tutorial` supplies informative and interactive tours for any
|
|
4
|
+
Qt Widgets application. It imports no application, project catalog, or orchestrator.
|
|
5
|
+
No existing application slot needs a tutorial call.
|
|
6
|
+
|
|
7
|
+
## Python bindings, optional TOML content
|
|
8
|
+
|
|
9
|
+
Use **Python for widget/signal bindings**. They are live Qt objects and may need
|
|
10
|
+
application logic to reveal a panel or verify a result. Use either **Python or
|
|
11
|
+
TOML for tutorial content**. Both produce the same `Tutorial` model.
|
|
12
|
+
|
|
13
|
+
TOML is useful when editing explanations or translating a tour without touching
|
|
14
|
+
bindings. It uses Python 3.12's standard-library `tomllib`, so no YAML dependency
|
|
15
|
+
is needed. Files contain symbolic IDs, never Python expressions, imports, widget
|
|
16
|
+
attribute paths, or executable callbacks. Unknown keys and invalid definitions
|
|
17
|
+
raise `ValueError`; TOML syntax and filesystem errors propagate to the caller.
|
|
18
|
+
|
|
19
|
+
```toml
|
|
20
|
+
id = "first-import"
|
|
21
|
+
title = "Import your data"
|
|
22
|
+
|
|
23
|
+
[[steps]]
|
|
24
|
+
id = "overview"
|
|
25
|
+
target = "input.panel"
|
|
26
|
+
title = "Input panel"
|
|
27
|
+
text = "Configure your input here, then review the resulting records."
|
|
28
|
+
|
|
29
|
+
[[steps]]
|
|
30
|
+
id = "import"
|
|
31
|
+
target = "input.run"
|
|
32
|
+
title = "Import the example"
|
|
33
|
+
text = "Run the import. This step advances only after the expected job succeeds."
|
|
34
|
+
advance_on = "input.imported"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from ms_components.ms_tutorial import (
|
|
39
|
+
TutorialController, TutorialEvent, TutorialTarget, load_tutorial,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
window.tutorials = TutorialController(
|
|
43
|
+
targets={
|
|
44
|
+
"input.panel": TutorialTarget(lambda: window.input_panel,
|
|
45
|
+
prepare=window.show_input_panel),
|
|
46
|
+
"input.run": TutorialTarget(lambda: window.input_panel.run_button),
|
|
47
|
+
},
|
|
48
|
+
events={
|
|
49
|
+
"input.imported": TutorialEvent(
|
|
50
|
+
window.monitor_bridge.job_finished,
|
|
51
|
+
accept=lambda job_id, status: job_id == expected_job_id() and status == "completed",
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
parent=window,
|
|
55
|
+
)
|
|
56
|
+
window.tutorials.start(load_tutorial("first_import.toml"))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Names such as `input.panel` are plain dictionary keys. The application supplies
|
|
60
|
+
their meaning; `ms_tutorial` knows nothing about AMDockVS or Molsuite's job statuses.
|
|
61
|
+
The example's `expected_job_id()` and `show_input_panel` are application-provided
|
|
62
|
+
functions, not methods required by this component. Real operations still go
|
|
63
|
+
through the application's existing APIs.
|
|
64
|
+
|
|
65
|
+
For a small tour, define the content in Python instead:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ms_components.ms_tutorial import Tutorial, TutorialStep
|
|
69
|
+
|
|
70
|
+
overview = Tutorial("overview", "Interface overview", (
|
|
71
|
+
TutorialStep("input", "input.panel", "Input", "Configure your data here."),
|
|
72
|
+
TutorialStep("run", "input.run", "Run", "This submits the configured operation."),
|
|
73
|
+
))
|
|
74
|
+
window.tutorials.start(overview)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Informative and interactive steps
|
|
78
|
+
|
|
79
|
+
- Without `advance_on`, a step is **informative**: Next/Back control the tour.
|
|
80
|
+
The spotlight remains visible but blocks clicks and keyboard actions on the host
|
|
81
|
+
window. The component never clicks controls or executes application actions.
|
|
82
|
+
- With `advance_on`, a step is **interactive**: the spotlight admits real input to
|
|
83
|
+
its target, and Next stays disabled until the named event is accepted. Completion
|
|
84
|
+
advances automatically. Other application slots retain their existing connections.
|
|
85
|
+
- `TutorialEvent.accept` receives the signal's arguments. Use it to match the job,
|
|
86
|
+
project, selection, or success state that the step actually expects. A request or
|
|
87
|
+
button click does not in general prove that an asynchronous operation succeeded.
|
|
88
|
+
- Events are observed only while their step is active. Already completed steps may
|
|
89
|
+
be revisited with Back and left with Next. A new run resets tutorial completion,
|
|
90
|
+
without resetting application data. Multiple queued copies of an old event do
|
|
91
|
+
not complete a later step, even if both steps use the same signal.
|
|
92
|
+
|
|
93
|
+
`TutorialEvent.signal` also accepts a zero-argument resolver for signals belonging
|
|
94
|
+
to dialogs or controls that are recreated. It is resolved when entering the step.
|
|
95
|
+
|
|
96
|
+
The controller queues event validation to the UI thread, including signals emitted
|
|
97
|
+
by background workers. Validators must be fast and must not mutate the UI or start
|
|
98
|
+
another tutorial. Signal argument types must be supported by Qt's queued connections
|
|
99
|
+
(Python objects and the ordinary Qt types are suitable).
|
|
100
|
+
|
|
101
|
+
## Dynamic panels and multiple windows
|
|
102
|
+
|
|
103
|
+
`TutorialTarget.widget` is a `QWidget` or a zero-argument resolver returning one.
|
|
104
|
+
Optional `TutorialTarget.rect` resolves a rectangle in that widget's coordinates,
|
|
105
|
+
for example `rect=lambda: menu_bar.actionGeometry(help_action)` to highlight just
|
|
106
|
+
the Help menu item. It is recomputed as the layout changes; the widget is unchanged.
|
|
107
|
+
The controller resolves it again as the UI changes. `prepare`, when provided,
|
|
108
|
+
runs once on entering the step and should only reveal the needed UI, through its
|
|
109
|
+
existing public navigation API. Do not submit jobs or change scientific data there.
|
|
110
|
+
Navigation callbacks must also account for Back, for example hiding a modal dialog
|
|
111
|
+
when returning to its parent window; the controller does not close application dialogs.
|
|
112
|
+
|
|
113
|
+
An overlay belongs to the target's top-level window. On the next step the controller
|
|
114
|
+
can move to another window, a floating dock, or a modal dialog, without application
|
|
115
|
+
slots knowing about it. It follows resizing, moving, and ancestor viewport clipping.
|
|
116
|
+
Use `prepare` to scroll offscreen controls into view; the component does not scroll
|
|
117
|
+
the application automatically. Hidden targets temporarily hide the overlay; an
|
|
118
|
+
initial hidden target without an existing overlay fails with a diagnostic. Missing
|
|
119
|
+
or deleted targets and callback failures stop the tour and emit `failed`.
|
|
120
|
+
|
|
121
|
+
Lifecycle signals:
|
|
122
|
+
|
|
123
|
+
| Signal | Arguments |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `started` | tutorial ID |
|
|
126
|
+
| `step_changed` | step ID, zero-based index |
|
|
127
|
+
| `finished` | tutorial ID |
|
|
128
|
+
| `cancelled` | tutorial ID |
|
|
129
|
+
| `failed` | tutorial ID, diagnostic message |
|
|
130
|
+
|
|
131
|
+
Call `start(tutorial)`, `next()`, `back()`, or `stop()` on the UI thread. Keep the
|
|
132
|
+
controller alive, normally as an attribute of the main window, and give it a Qt
|
|
133
|
+
parent. Escape/Exit cancel; closing the current host also cancels. Stopping a tour
|
|
134
|
+
removes its signal connection and event filter and restores the earlier focus
|
|
135
|
+
when that widget is still visible. Switching tutorials cancels the previous one.
|
|
136
|
+
Invalid target/event registry keys are rejected before cancelling an active tour.
|
|
137
|
+
The component never creates `QApplication` or changes the application's theme.
|
|
138
|
+
|
|
139
|
+
## Demo and checks
|
|
140
|
+
|
|
141
|
+
From the source checkout, in `MSDD_312`:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
PYTHONPATH=src python examples/tutorial_demo.py
|
|
145
|
+
QT_QPA_PLATFORM=offscreen PYTHONPATH=src python -m pytest -q tests/test_tutorial.py
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The demo has two buttons: an informative tour defined in Python, and a mixed
|
|
149
|
+
interactive/informative tour loaded from `examples/tutorial_demo.toml`. Its
|
|
150
|
+
application slot has no tutorial calls. Add the sibling `ms_flow/src` and
|
|
151
|
+
`ms_env/src` to `PYTHONPATH` if those packages are not installed in the environment.
|
|
152
|
+
|
|
153
|
+
## Scope
|
|
154
|
+
|
|
155
|
+
This component guides an existing UI within one process. Continuing in a new process,
|
|
156
|
+
saving progress, translation catalogs, and the application's Help menu remain the
|
|
157
|
+
application's responsibility. A spotlight over native platform dialogs or embedded
|
|
158
|
+
OpenGL renderers is platform-dependent; use a Qt Widgets practice view if needed.
|
|
159
|
+
On a host almost entirely occupied by the target, the callout may overlap part of
|
|
160
|
+
the spotlight. Expose a smaller target in the binding to leave room for the callout.
|
|
@@ -135,28 +135,46 @@ action bar state.
|
|
|
135
135
|
|
|
136
136
|
## Job monitor
|
|
137
137
|
|
|
138
|
-
The bridge polls
|
|
138
|
+
The bridge polls an `ms_flow` `Workspace` and translates its snapshots into Qt
|
|
139
139
|
signals and models; the widget only presents them. You can embed the whole
|
|
140
140
|
monitor or consume only the bridge signals.
|
|
141
141
|
|
|
142
142
|
```python
|
|
143
|
-
from ms_flow
|
|
143
|
+
from ms_flow import Workspace
|
|
144
144
|
from ms_components.ms_monitor import MolSuiteMonitorBridge, MolSuiteMonitorWidget
|
|
145
145
|
|
|
146
|
-
ms =
|
|
146
|
+
ms = Workspace(app_id="demo")
|
|
147
147
|
ms.create_or_open_project(name="demo", folder="./demo-project", activate=True)
|
|
148
148
|
|
|
149
|
-
bridge = MolSuiteMonitorBridge(
|
|
149
|
+
bridge = MolSuiteMonitorBridge(workspace=ms, poll_ms=500)
|
|
150
150
|
monitor = MolSuiteMonitorWidget(bridge=bridge)
|
|
151
151
|
bridge.bridge_error.connect(print)
|
|
152
|
-
bridge.job_finished.connect(lambda
|
|
152
|
+
bridge.job_finished.connect(lambda job_id, status: print("job finished", job_id, status))
|
|
153
153
|
bridge.start()
|
|
154
154
|
monitor.show()
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
When closing, call `bridge.stop()` first and `ms.shutdown()` afterwards. The
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
operator can cancel jobs, **Resume** a job that was interrupted or canceled,
|
|
159
|
+
and **Move** a job to another active executor. An application with its own
|
|
160
|
+
rules of where a job may go passes `resume(job_id, executor)` and
|
|
161
|
+
`destinations(job_id)` to the bridge.
|
|
162
|
+
|
|
163
|
+
## Where jobs run
|
|
164
|
+
|
|
165
|
+
`ms_components.ms_executors` edits the hosts and executors kept by `ms_flow`.
|
|
166
|
+
Both widgets go through the `Workspace`, which refuses what cannot be done now
|
|
167
|
+
(changing an executor while it is active, for example) and says why.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from ms_components.ms_executors import ExecutorsPanel, KeptPage
|
|
171
|
+
|
|
172
|
+
hosts = KeptPage(ms, "hosts") # a Settings page: the machines
|
|
173
|
+
executors = KeptPage(ms, "executors") # a Settings page: who runs jobs on them
|
|
174
|
+
panel = ExecutorsPanel(ms) # the quick window that starts and stops executors
|
|
175
|
+
panel.settings_requested.connect(lambda: print("open the Settings page"))
|
|
176
|
+
panel.show()
|
|
177
|
+
```
|
|
160
178
|
|
|
161
179
|
## Projects, settings, and PyMOL
|
|
162
180
|
|
|
@@ -164,7 +182,7 @@ re-buildable source — resubmit.
|
|
|
164
182
|
and create projects through the public `ms_flow` project catalog. Connect
|
|
165
183
|
`project_requested` to open the chosen project; do not read the MolSuite
|
|
166
184
|
databases from the UI.
|
|
167
|
-
- `AppSettingsDialog(runtime=...)` edits the layered configuration of
|
|
168
|
-
|
|
185
|
+
- `AppSettingsDialog(runtime=...)` edits the layered configuration of an
|
|
186
|
+
application.
|
|
169
187
|
- `PymolDockWidget(title, manager)` needs the PyMOL `cmd`/manager object of the
|
|
170
188
|
application's PyMOL integration.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Run in MSDD_312: PYTHONPATH=src python examples/tutorial_demo.py."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
from PySide6.QtWidgets import QApplication, QDialog, QLabel, QPushButton, QVBoxLayout, QWidget
|
|
7
|
+
|
|
8
|
+
from ms_components.ms_tutorial import (
|
|
9
|
+
Tutorial, TutorialController, TutorialEvent, TutorialStep, TutorialTarget, load_tutorial,
|
|
10
|
+
)
|
|
11
|
+
from ms_components.theme import apply_theme
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DemoWindow(QWidget):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.setWindowTitle("ms_tutorial · Python + TOML")
|
|
18
|
+
self.resize(900, 640)
|
|
19
|
+
layout = QVBoxLayout(self)
|
|
20
|
+
layout.setContentsMargins(28, 28, 28, 28)
|
|
21
|
+
layout.setSpacing(18)
|
|
22
|
+
layout.addWidget(QLabel("Tutoriales independientes de la aplicación"))
|
|
23
|
+
self.load_button = QPushButton("Cargar ejemplo")
|
|
24
|
+
self.status = QLabel("Sin datos de muestra")
|
|
25
|
+
layout.addWidget(self.load_button, alignment=Qt.AlignmentFlag.AlignLeft)
|
|
26
|
+
layout.addWidget(self.status)
|
|
27
|
+
layout.addStretch()
|
|
28
|
+
self.info_button = QPushButton("Recorrido informativo · Python")
|
|
29
|
+
self.interactive_button = QPushButton("Recorrido interactivo · TOML")
|
|
30
|
+
layout.addWidget(self.info_button)
|
|
31
|
+
layout.addWidget(self.interactive_button)
|
|
32
|
+
self.details = QDialog(self)
|
|
33
|
+
self.details.setWindowTitle("Detalles")
|
|
34
|
+
self.details.resize(600, 420)
|
|
35
|
+
details_layout = QVBoxLayout(self.details)
|
|
36
|
+
self.details_text = QLabel("Este panel pertenece a otro diálogo.")
|
|
37
|
+
details_layout.addWidget(self.details_text, alignment=Qt.AlignmentFlag.AlignTop)
|
|
38
|
+
details_layout.addStretch()
|
|
39
|
+
|
|
40
|
+
# Existing application behavior: no tutorial calls in this slot.
|
|
41
|
+
self.load_button.clicked.connect(self.load_sample)
|
|
42
|
+
self.tutorials = TutorialController(
|
|
43
|
+
targets={
|
|
44
|
+
"input.load": TutorialTarget(self.load_button, prepare=self.details.hide),
|
|
45
|
+
"input.status": TutorialTarget(self.status, prepare=self.details.hide),
|
|
46
|
+
"details.text": TutorialTarget(lambda: self.details_text, prepare=self.details.show),
|
|
47
|
+
},
|
|
48
|
+
events={"input.loaded": TutorialEvent(self.load_button.clicked)},
|
|
49
|
+
parent=self,
|
|
50
|
+
)
|
|
51
|
+
self.informative = Tutorial("overview", "Conoce la interfaz", (
|
|
52
|
+
TutorialStep("input", "input.load", "Entrada", "Este botón carga los datos de muestra. "
|
|
53
|
+
"En este recorrido solo lo explicamos: no se puede activar."),
|
|
54
|
+
TutorialStep("status", "input.status", "Estado", "Aquí aparece el estado de los datos. "
|
|
55
|
+
"No ha hecho falta ejecutar ninguna acción para terminar el recorrido."),
|
|
56
|
+
))
|
|
57
|
+
self.interactive = load_tutorial(Path(__file__).with_suffix(".toml"))
|
|
58
|
+
self.info_button.clicked.connect(lambda: self.tutorials.start(self.informative))
|
|
59
|
+
self.interactive_button.clicked.connect(lambda: self.tutorials.start(self.interactive))
|
|
60
|
+
|
|
61
|
+
def load_sample(self):
|
|
62
|
+
self.status.setText("Ejemplo cargado")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main():
|
|
66
|
+
app = QApplication(sys.argv)
|
|
67
|
+
apply_theme("auto", app)
|
|
68
|
+
window = DemoWindow()
|
|
69
|
+
window.show()
|
|
70
|
+
return app.exec()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
sys.exit(main())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id = "interactive-demo"
|
|
2
|
+
title = "Tutorial interactivo"
|
|
3
|
+
|
|
4
|
+
[[steps]]
|
|
5
|
+
id = "load"
|
|
6
|
+
target = "input.load"
|
|
7
|
+
title = "Carga el ejemplo"
|
|
8
|
+
text = "Pulsa Cargar ejemplo. La guía escucha la señal existente desde fuera del componente."
|
|
9
|
+
advance_on = "input.loaded"
|
|
10
|
+
|
|
11
|
+
[[steps]]
|
|
12
|
+
id = "details"
|
|
13
|
+
target = "details.text"
|
|
14
|
+
title = "Otra ventana"
|
|
15
|
+
text = "El controlador ha abierto un diálogo mediante una función de navegación. Este paso es informativo y avanza con Next."
|
|
16
|
+
|
|
17
|
+
[[steps]]
|
|
18
|
+
id = "summary"
|
|
19
|
+
target = "input.status"
|
|
20
|
+
title = "Terminado"
|
|
21
|
+
text = "La aplicación conserva sus propios slots. El tutorial solo añade sus conexiones mientras están activas."
|
|
@@ -39,6 +39,7 @@ dev = ["pytest>=7.0"]
|
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
41
41
|
Homepage = "https://github.com/MolSuite/ms_components"
|
|
42
|
+
Documentation = "https://molsuite.github.io/ms_components/"
|
|
42
43
|
Issues = "https://github.com/MolSuite/ms_components/issues"
|
|
43
44
|
|
|
44
45
|
# The release tag is the only version there is: nothing to bump by hand, and
|
|
@@ -4,6 +4,17 @@ QMainWindow::separator {
|
|
|
4
4
|
height: 1px;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
QFrame#TutorialCallout {
|
|
8
|
+
background: palette(window);
|
|
9
|
+
border: 1px solid palette(mid);
|
|
10
|
+
border-radius: 8px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
QTextBrowser#TutorialText {
|
|
14
|
+
background: transparent;
|
|
15
|
+
border: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
QToolBar {
|
|
8
19
|
border: none;
|
|
9
20
|
padding: 3px;
|
|
@@ -130,6 +141,19 @@ QPushButton:default {
|
|
|
130
141
|
border-color: palette(highlight);
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
/* A split button (QToolButton with a menu arrow) is an action button, not a toolbar icon:
|
|
145
|
+
framed like QPushButton, the arrow in its own segment. */
|
|
146
|
+
QToolButton[popupMode="MenuButtonPopup"] {
|
|
147
|
+
border: 1px solid palette(mid);
|
|
148
|
+
padding: 5px 14px;
|
|
149
|
+
padding-right: 24px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
QToolButton[popupMode="MenuButtonPopup"]::menu-button {
|
|
153
|
+
border-left: 1px solid palette(mid);
|
|
154
|
+
width: 18px;
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
/* Card = content surface. Nav trees/lists stay flat unless opted in. */
|
|
134
158
|
QTableView,
|
|
135
159
|
QTreeView[card="true"],
|