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.
Files changed (124) hide show
  1. {ms_components-0.2.0 → ms_components-0.3.0}/PKG-INFO +11 -1
  2. {ms_components-0.2.0 → ms_components-0.3.0}/README.md +9 -0
  3. {ms_components-0.2.0 → ms_components-0.3.0}/docs/api_reference.md +8 -0
  4. {ms_components-0.2.0 → ms_components-0.3.0}/docs/examples.md +18 -16
  5. {ms_components-0.2.0 → ms_components-0.3.0}/docs/index.md +3 -1
  6. ms_components-0.3.0/docs/ms_tutorial.md +160 -0
  7. {ms_components-0.2.0 → ms_components-0.3.0}/docs/usage.md +27 -9
  8. ms_components-0.3.0/examples/tutorial_demo.py +74 -0
  9. ms_components-0.3.0/examples/tutorial_demo.toml +21 -0
  10. {ms_components-0.2.0 → ms_components-0.3.0}/pyproject.toml +1 -0
  11. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/base.qss +24 -0
  12. ms_components-0.3.0/src/ms_components/chips.py +279 -0
  13. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/widget.py +9 -35
  14. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/widget.py +11 -0
  15. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/cell_delegate.py +29 -1
  16. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/metric_filter_bar.py +7 -3
  17. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/smart_table_view.py +13 -7
  18. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/table_config.py +2 -0
  19. ms_components-0.3.0/src/ms_components/ms_tutorial/__init__.py +9 -0
  20. ms_components-0.3.0/src/ms_components/ms_tutorial/bindings.py +21 -0
  21. ms_components-0.3.0/src/ms_components/ms_tutorial/controller.py +257 -0
  22. ms_components-0.3.0/src/ms_components/ms_tutorial/models.py +58 -0
  23. ms_components-0.3.0/src/ms_components/ms_tutorial/overlay.py +154 -0
  24. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/PKG-INFO +11 -1
  25. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/SOURCES.txt +14 -1
  26. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/scm_file_list.json +13 -0
  27. ms_components-0.3.0/src/ms_components.egg-info/scm_version.json +8 -0
  28. ms_components-0.3.0/tests/test_chips.py +16 -0
  29. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_dock_window.py +9 -15
  30. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_ms_projects_widget.py +19 -1
  31. ms_components-0.3.0/tests/test_progress_column.py +36 -0
  32. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_project_browser.py +4 -0
  33. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_query_builder_keyset.py +28 -0
  34. ms_components-0.3.0/tests/test_split_button_style.py +21 -0
  35. ms_components-0.3.0/tests/test_tutorial.py +384 -0
  36. ms_components-0.2.0/src/ms_components.egg-info/scm_version.json +0 -8
  37. {ms_components-0.2.0 → ms_components-0.3.0}/.github/workflows/docs.yml +0 -0
  38. {ms_components-0.2.0 → ms_components-0.3.0}/.github/workflows/publish.yml +0 -0
  39. {ms_components-0.2.0 → ms_components-0.3.0}/.gitignore +0 -0
  40. {ms_components-0.2.0 → ms_components-0.3.0}/LICENSE +0 -0
  41. {ms_components-0.2.0 → ms_components-0.3.0}/setup.cfg +0 -0
  42. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/__init__.py +0 -0
  43. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/listview.py +0 -0
  44. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/__init__.py +0 -0
  45. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
  46. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
  47. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
  48. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_executors.py +0 -0
  49. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/__init__.py +0 -0
  50. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/bridge.py +0 -0
  51. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/cards.py +0 -0
  52. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/config.py +0 -0
  53. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/detail_panel.py +0 -0
  54. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/formatting.py +0 -0
  55. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/job_cards.py +0 -0
  56. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/models.py +0 -0
  57. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/palette.py +0 -0
  58. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/polling.py +0 -0
  59. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_monitor/widget.py +0 -0
  60. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/__init__.py +0 -0
  61. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/app.py +0 -0
  62. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/browser.py +0 -0
  63. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_projects/scratch.py +0 -0
  64. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/__init__.py +0 -0
  65. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
  66. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/box.svg +0 -0
  67. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/center.svg +0 -0
  68. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/display.svg +0 -0
  69. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
  70. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
  71. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
  72. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
  73. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
  74. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
  75. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
  76. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
  77. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/plugins.py +0 -0
  78. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_pymol/widget.py +0 -0
  79. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_settings/__init__.py +0 -0
  80. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_settings/panel.py +0 -0
  81. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_stepper.py +0 -0
  82. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/__init__.py +0 -0
  83. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/action_header.py +0 -0
  84. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/column_panel.py +0 -0
  85. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/example_usage.py +0 -0
  86. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/filter_panel.py +0 -0
  87. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/columns.svg +0 -0
  88. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/export.svg +0 -0
  89. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/filter.svg +0 -0
  90. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/menu.svg +0 -0
  91. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/reload.svg +0 -0
  92. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/search.svg +0 -0
  93. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/settings.svg +0 -0
  94. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
  95. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
  96. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/icons/sort.svg +0 -0
  97. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/pagination_control.py +0 -0
  98. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/query_builder.py +0 -0
  99. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/session_provider.py +0 -0
  100. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/sort_panel.py +0 -0
  101. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/table_model.py +0 -0
  102. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/test_external_filters.py +0 -0
  103. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/ms_table/test_toolbar.py +0 -0
  104. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/pagination.py +0 -0
  105. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/palettes.py +0 -0
  106. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/step_dialog.py +0 -0
  107. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/theme.py +0 -0
  108. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/tool_panel.py +0 -0
  109. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components/wheel.py +0 -0
  110. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/dependency_links.txt +0 -0
  111. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/requires.txt +0 -0
  112. {ms_components-0.2.0 → ms_components-0.3.0}/src/ms_components.egg-info/top_level.txt +0 -0
  113. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_app_settings.py +0 -0
  114. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_executors_ui.py +0 -0
  115. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_molsuite_monitor_bridge.py +0 -0
  116. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_molsuite_monitor_widgets.py +0 -0
  117. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_monitor_config.py +0 -0
  118. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_pymol_toolbar.py +0 -0
  119. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_shift_hscroll.py +0 -0
  120. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_step_dialog.py +0 -0
  121. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_table_toolbar.py +0 -0
  122. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_table_view_state.py +0 -0
  123. {ms_components-0.2.0 → ms_components-0.3.0}/tests/test_theme_contrast.py +0 -0
  124. {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.2.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 `MolSuite` runtime and watch a job advance. The worker
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
- def slow_square(payload: dict) -> dict:
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.api import MolSuite
111
- from workers import slow_square
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 = MolSuite(app_id="monitor-demo")
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(molsuite=ms, poll_ms=500)
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 runtime shuts down
123
-
124
- ms.run(
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, executor, and event monitor for a MolSuite runtime |
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 a `MolSuite` runtime and translates its snapshots into Qt
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.api import MolSuite
143
+ from ms_flow import Workspace
144
144
  from ms_components.ms_monitor import MolSuiteMonitorBridge, MolSuiteMonitorWidget
145
145
 
146
- ms = MolSuite(app_id="demo")
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(molsuite=ms, poll_ms=500)
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 *args: print("job finished", args))
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
- supported operator actions are refresh, cancel, and — when the job keeps a
159
- re-buildable source — resubmit.
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 a
168
- MolSuite runtime.
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"],