ms-components 0.4.0__tar.gz → 0.4.2__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 (137) hide show
  1. {ms_components-0.4.0 → ms_components-0.4.2}/PKG-INFO +1 -1
  2. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_dockwidget/widget.py +10 -1
  3. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_tutorial/controller.py +5 -1
  4. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_tutorial/overlay.py +45 -12
  5. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/PKG-INFO +1 -1
  6. ms_components-0.4.2/src/ms_components.egg-info/scm_version.json +8 -0
  7. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_dock_window.py +20 -0
  8. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_tutorial.py +68 -0
  9. ms_components-0.4.0/src/ms_components.egg-info/scm_version.json +0 -8
  10. {ms_components-0.4.0 → ms_components-0.4.2}/.github/workflows/docs.yml +0 -0
  11. {ms_components-0.4.0 → ms_components-0.4.2}/.github/workflows/publish.yml +0 -0
  12. {ms_components-0.4.0 → ms_components-0.4.2}/.gitignore +0 -0
  13. {ms_components-0.4.0 → ms_components-0.4.2}/LICENSE +0 -0
  14. {ms_components-0.4.0 → ms_components-0.4.2}/MANIFEST.in +0 -0
  15. {ms_components-0.4.0 → ms_components-0.4.2}/README.md +0 -0
  16. {ms_components-0.4.0 → ms_components-0.4.2}/examples/tutorial_demo.py +0 -0
  17. {ms_components-0.4.0 → ms_components-0.4.2}/examples/tutorial_demo.toml +0 -0
  18. {ms_components-0.4.0 → ms_components-0.4.2}/licenses/qt-themes-LICENSE.txt +0 -0
  19. {ms_components-0.4.0 → ms_components-0.4.2}/pyproject.toml +0 -0
  20. {ms_components-0.4.0 → ms_components-0.4.2}/setup.cfg +0 -0
  21. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/__init__.py +0 -0
  22. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/backdrop.py +0 -0
  23. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/base.qss +0 -0
  24. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/chips.py +0 -0
  25. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ketcher.py +0 -0
  26. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/listview.py +0 -0
  27. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_dockwidget/__init__.py +0 -0
  28. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
  29. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
  30. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
  31. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_executors.py +0 -0
  32. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/__init__.py +0 -0
  33. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/bridge.py +0 -0
  34. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/cards.py +0 -0
  35. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/config.py +0 -0
  36. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/detail_panel.py +0 -0
  37. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/formatting.py +0 -0
  38. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/job_cards.py +0 -0
  39. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/models.py +0 -0
  40. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/palette.py +0 -0
  41. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/polling.py +0 -0
  42. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_monitor/widget.py +0 -0
  43. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/__init__.py +0 -0
  44. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/app.py +0 -0
  45. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/backend.py +0 -0
  46. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/browser.py +0 -0
  47. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/scratch.py +0 -0
  48. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_projects/widget.py +0 -0
  49. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/__init__.py +0 -0
  50. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
  51. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/box.svg +0 -0
  52. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/center.svg +0 -0
  53. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/display.svg +0 -0
  54. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
  55. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/hydrogens-polar.svg +0 -0
  56. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/hydrogens.svg +0 -0
  57. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
  58. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
  59. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
  60. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
  61. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
  62. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
  63. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/style.svg +0 -0
  64. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
  65. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/plugins.py +0 -0
  66. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/scene.py +0 -0
  67. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_pymol/widget.py +0 -0
  68. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_settings/__init__.py +0 -0
  69. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_settings/panel.py +0 -0
  70. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_stepper.py +0 -0
  71. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/__init__.py +0 -0
  72. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/action_header.py +0 -0
  73. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/cell_delegate.py +0 -0
  74. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/column_panel.py +0 -0
  75. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/example_usage.py +0 -0
  76. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/filter_panel.py +0 -0
  77. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/columns.svg +0 -0
  78. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/export.svg +0 -0
  79. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/filter.svg +0 -0
  80. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/menu.svg +0 -0
  81. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/reload.svg +0 -0
  82. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/search.svg +0 -0
  83. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/settings.svg +0 -0
  84. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
  85. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
  86. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/icons/sort.svg +0 -0
  87. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/metric_filter_bar.py +0 -0
  88. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/pagination_control.py +0 -0
  89. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/query_builder.py +0 -0
  90. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/session_provider.py +0 -0
  91. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/smart_table_view.py +0 -0
  92. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/sort_panel.py +0 -0
  93. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/table_config.py +0 -0
  94. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/table_interaction.py +0 -0
  95. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/table_model.py +0 -0
  96. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/test_external_filters.py +0 -0
  97. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_table/test_toolbar.py +0 -0
  98. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_tutorial/__init__.py +0 -0
  99. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_tutorial/bindings.py +0 -0
  100. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/ms_tutorial/models.py +0 -0
  101. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/opengl.py +0 -0
  102. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/pagination.py +0 -0
  103. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/palettes.py +0 -0
  104. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/step_dialog.py +0 -0
  105. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/theme.py +0 -0
  106. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/tool_panel.py +0 -0
  107. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components/wheel.py +0 -0
  108. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/SOURCES.txt +0 -0
  109. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/dependency_links.txt +0 -0
  110. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/requires.txt +0 -0
  111. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/scm_file_list.json +0 -0
  112. {ms_components-0.4.0 → ms_components-0.4.2}/src/ms_components.egg-info/top_level.txt +0 -0
  113. {ms_components-0.4.0 → ms_components-0.4.2}/tests/conftest.py +0 -0
  114. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_app_settings.py +0 -0
  115. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_chips.py +0 -0
  116. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_executors_ui.py +0 -0
  117. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_ketcher.py +0 -0
  118. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_molsuite_monitor_bridge.py +0 -0
  119. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_molsuite_monitor_widgets.py +0 -0
  120. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_monitor_config.py +0 -0
  121. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_ms_projects_widget.py +0 -0
  122. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_opengl.py +0 -0
  123. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_progress_column.py +0 -0
  124. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_project_browser.py +0 -0
  125. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_pymol_grid.py +0 -0
  126. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_pymol_scene.py +0 -0
  127. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_pymol_toolbar.py +0 -0
  128. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_query_builder_keyset.py +0 -0
  129. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_shift_hscroll.py +0 -0
  130. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_split_button_style.py +0 -0
  131. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_step_dialog.py +0 -0
  132. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_table_interaction.py +0 -0
  133. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_table_toolbar.py +0 -0
  134. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_table_view_state.py +0 -0
  135. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_theme_contrast.py +0 -0
  136. {ms_components-0.4.0 → ms_components-0.4.2}/tests/test_tool_panel.py +0 -0
  137. {ms_components-0.4.0 → ms_components-0.4.2}/zensical.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ms_components
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -779,6 +779,7 @@ class DockManager:
779
779
  # Standalone action buttons (no dock): a click runs a callback instead of toggling a dock.
780
780
  # action_id -> {"region", "order", "button"}. Re-added on every _build_toolbars().
781
781
  self.action_buttons: Dict[str, dict] = {}
782
+ self.action_separators: Dict[str, dict] = {} # same shape; "button" is a ToolbarSeparator
782
783
  self.region_states: Dict[Region, RegionState] = {
783
784
  region: RegionState(order=[], visible=[]) for region in Region
784
785
  }
@@ -885,6 +886,14 @@ class DockManager:
885
886
  self._build_toolbars()
886
887
  return button
887
888
 
889
+ def add_action_separator(self, separator_id: str, *, region: Region, order: int) -> None:
890
+ """A line in a toolbar region, placed by ``order`` like an action button, so it survives
891
+ the rebuilds a line inserted into the layout would not."""
892
+ if separator_id in self.action_separators:
893
+ raise ValueError(f"Duplicate action separator id: {separator_id}")
894
+ self.action_separators[separator_id] = {"region": region, "order": int(order), "button": ToolbarSeparator()}
895
+ self._build_toolbars()
896
+
888
897
  # ── Build ────────────────────────────────────────────────────
889
898
 
890
899
  def build(self):
@@ -1595,7 +1604,7 @@ class DockManager:
1595
1604
  # dock buttons keep their (possibly drag-set) region order; standalone action
1596
1605
  # buttons are woven in by their `order` relative to the dock entries' orders.
1597
1606
  merged = [(entry.order, self.buttons[entry.id]) for entry in self._region_entries(region)]
1598
- for action in self.action_buttons.values():
1607
+ for action in (*self.action_buttons.values(), *self.action_separators.values()):
1599
1608
  if action["region"] != region:
1600
1609
  continue
1601
1610
  idx = next((i for i, (order, _) in enumerate(merged) if order > action["order"]), len(merged))
@@ -56,12 +56,14 @@ class TutorialController(QObject):
56
56
  events: Mapping[str, TutorialEvent] | None = None,
57
57
  instruction_provider: Callable[[TutorialStep], str] | None = None,
58
58
  dim_background: bool = True,
59
+ close_only: bool = False,
59
60
  parent: QObject | None = None):
60
61
  super().__init__(parent)
61
62
  self.targets = dict(targets)
62
63
  self.events = dict(events or {})
63
64
  self.instruction_provider = instruction_provider
64
65
  self.dim_background = dim_background
66
+ self.close_only = close_only
65
67
  self._tutorial: Tutorial | None = None
66
68
  self._index = -1
67
69
  self._completed: set[str] = set()
@@ -93,6 +95,8 @@ class TutorialController(QObject):
93
95
  def start(self, tutorial: Tutorial):
94
96
  """Validate bindings before replacing the active tour. Must run in the UI thread."""
95
97
  for step in tutorial.steps:
98
+ if self.close_only and step.advance_on is None:
99
+ raise ValueError("Close-only tutorials require an event for every step")
96
100
  if not isinstance(self.targets.get(step.target), TutorialTarget):
97
101
  raise ValueError(f"Unregistered tutorial target: {step.target}")
98
102
  if step.advance_on is not None and not isinstance(self.events.get(step.advance_on), TutorialEvent):
@@ -217,7 +221,7 @@ class TutorialController(QObject):
217
221
  if self.overlay is not None:
218
222
  self.overlay.hide()
219
223
  self.overlay.deleteLater()
220
- self._overlay = TutorialOverlay(host, dim_background=self.dim_background)
224
+ self._overlay = TutorialOverlay(host, dim_background=self.dim_background, close_only=self.close_only)
221
225
  self._overlay.next_requested.connect(self.next)
222
226
  self._overlay.back_requested.connect(self.back)
223
227
  self._overlay.exit_requested.connect(self.stop)
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
  from math import ceil
5
5
 
6
6
  from PySide6.QtCore import QPoint, QRect, QRectF, Qt, Signal
7
- from PySide6.QtGui import QPainter, QPainterPath, QPalette, QPen
7
+ from PySide6.QtGui import QColor, QPainter, QPainterPath, QPalette, QPen
8
8
  from PySide6.QtWidgets import (
9
9
  QFrame, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QTextBrowser,
10
10
  QVBoxLayout, QWidget,
@@ -19,9 +19,10 @@ class TutorialOverlay(QWidget):
19
19
  back_requested = Signal()
20
20
  exit_requested = Signal()
21
21
 
22
- def __init__(self, host: QWidget, *, dim_background: bool = True):
22
+ def __init__(self, host: QWidget, *, dim_background: bool = True, close_only: bool = False):
23
23
  super().__init__(host)
24
24
  self.dim_background = dim_background
25
+ self.close_only = close_only
25
26
  self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
26
27
  self.hole = QRect()
27
28
  self.step: TutorialStep | None = None
@@ -48,17 +49,40 @@ class TutorialOverlay(QWidget):
48
49
  self.body.setFrameShape(QFrame.Shape.NoFrame)
49
50
  self.body.setOpenExternalLinks(False)
50
51
  self.body.setFocusPolicy(Qt.FocusPolicy.NoFocus)
51
- for widget in (self.counter, self.title, self.body):
52
- layout.addWidget(widget)
52
+ if close_only:
53
+ self.counter.setMinimumWidth(0)
54
+ self.counter.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
55
+ header = QHBoxLayout()
56
+ header.setContentsMargins(0, 0, 0, 0)
57
+ header.addWidget(self.counter, 1)
58
+ layout.addLayout(header)
59
+ else:
60
+ layout.addWidget(self.counter)
61
+ layout.addWidget(self.title)
62
+ layout.addWidget(self.body)
53
63
  actions = QHBoxLayout()
54
64
  self.exit_button = QPushButton(self.tr("Exit · Esc"))
55
65
  self.back_button = QPushButton(self.tr("Back"))
56
66
  self.next_button = QPushButton(self.tr("Next"))
57
- for button in (self.exit_button, self.back_button, self.next_button):
58
- button.setMinimumWidth(0)
59
- button.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
60
- actions.addWidget(button)
61
- layout.addLayout(actions)
67
+ if close_only:
68
+ self.exit_button.setText("×")
69
+ self.exit_button.setAccessibleName(self.tr("Close tutorial"))
70
+ self.exit_button.setToolTip(self.tr("Close tutorial (Esc)"))
71
+ self.exit_button.setFlat(True)
72
+ self.exit_button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
73
+ self.exit_button.setFixedSize(28, 28)
74
+ self.exit_button.setStyleSheet("font-size: 20px; padding: 0;")
75
+ header.addWidget(self.exit_button, 0, Qt.AlignmentFlag.AlignTop)
76
+ self.back_button.setParent(self.panel)
77
+ self.next_button.setParent(self.panel)
78
+ self.back_button.hide()
79
+ self.next_button.hide()
80
+ else:
81
+ for button in (self.exit_button, self.back_button, self.next_button):
82
+ button.setMinimumWidth(0)
83
+ button.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
84
+ actions.addWidget(button)
85
+ layout.addLayout(actions)
62
86
  self.exit_button.clicked.connect(self.exit_requested)
63
87
  self.back_button.clicked.connect(self.back_requested)
64
88
  self.next_button.clicked.connect(self.next_requested)
@@ -147,7 +171,7 @@ class TutorialOverlay(QWidget):
147
171
  self.update()
148
172
 
149
173
  def focus_guide(self):
150
- (self.next_button if self.next_button.isEnabled() else self.exit_button).setFocus()
174
+ (self.next_button if not self.close_only and self.next_button.isEnabled() else self.exit_button).setFocus()
151
175
 
152
176
  def paintEvent(self, event):
153
177
  painter = QPainter(self)
@@ -160,5 +184,14 @@ class TutorialOverlay(QWidget):
160
184
  opening = QPainterPath()
161
185
  opening.addRect(QRectF(self.hole))
162
186
  painter.fillPath(path.subtracted(opening), shade)
163
- painter.setPen(QPen(self.palette().color(QPalette.ColorRole.Highlight), 3))
164
- painter.drawRoundedRect(QRectF(self.hole.adjusted(-3, -3, 3, 3)), 6, 6)
187
+ # A two-tone ring distinguishes the guide from the theme's selection and
188
+ # button colors. The contrasting outer stroke keeps it visible over mixed
189
+ # surfaces, including selected controls and application artwork.
190
+ dark = self.palette().color(QPalette.ColorRole.Window).lightness() < 128
191
+ halo = QColor("#141a22" if dark else "#ffffff")
192
+ guide = QColor("#ffd166" if dark else "#8c4b00")
193
+ rect = QRectF(self.hole.adjusted(-5, -5, 5, 5))
194
+ painter.setPen(QPen(halo, 7))
195
+ painter.drawRoundedRect(rect, 7, 7)
196
+ painter.setPen(QPen(guide, 3))
197
+ painter.drawRoundedRect(rect, 7, 7)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ms_components
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.2",
3
+ "distance": 0,
4
+ "node": "g154aef5200c38d657805e5c382f0c99b1c15be3d",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-28"
8
+ }
@@ -154,3 +154,23 @@ def test_a_window_stays_a_window_on_double_click_and_drop(app):
154
154
 
155
155
  window.close()
156
156
  app.processEvents()
157
+
158
+
159
+ def test_action_separator_sits_by_order_and_survives_a_rebuild(app):
160
+ from ms_components.ms_dockwidget.widget import ToolbarSeparator
161
+
162
+ window = QMainWindow()
163
+ manager = DockManager(window)
164
+ manager.add_action_button("a", region=Region.BOTTOM_LEFT, order=1, on_click=lambda: None)
165
+ manager.add_action_button("b", region=Region.BOTTOM_LEFT, order=3, on_click=lambda: None)
166
+ manager.add_action_separator("line", region=Region.BOTTOM_LEFT, order=2)
167
+ manager.set_show_tool_names(True) # restyles every button: the separator is not one
168
+ manager._build_toolbars()
169
+
170
+ layout = manager.left_toolbar.layout_for_region(Region.BOTTOM_LEFT)
171
+ widgets = [layout.itemAt(i).widget() for i in range(layout.count())]
172
+ assert widgets[1] is manager.action_separators["line"]["button"]
173
+ assert isinstance(widgets[1], ToolbarSeparator)
174
+ assert widgets[0] is manager.action_buttons["a"]["button"]
175
+ assert widgets[2] is manager.action_buttons["b"]["button"]
176
+ window.close()
@@ -157,6 +157,22 @@ def test_outline_only_presentation_preserves_the_background(ui):
157
157
  assert QApplication.widgetAt(button.mapToGlobal(button.rect().center())) is button
158
158
 
159
159
 
160
+ def test_guide_outline_changes_with_theme_and_keeps_target_clickable(ui):
161
+ app, window, button, other, controller = ui
162
+ controller.dim_background = False
163
+ controller.start(tour(interactive=True))
164
+ for theme, expected in (("github_light", "#8c4b00"), ("github_dark", "#ffd166")):
165
+ apply_theme(theme, app)
166
+ app.processEvents()
167
+ image = window.grab().toImage()
168
+ rect = controller.overlay.hole.adjusted(-9, -9, 9, 9).intersected(window.rect())
169
+ colors = {image.pixelColor(x, y).name()
170
+ for x in range(rect.left(), rect.right() + 1)
171
+ for y in range(rect.top(), rect.bottom() + 1)}
172
+ assert expected in colors
173
+ assert QApplication.widgetAt(button.mapToGlobal(button.rect().center())) is button
174
+
175
+
160
176
  def test_accepting_a_dialog_keeps_the_tutorial_active_but_closing_cancels(ui):
161
177
  app, window, button, other, controller = ui
162
178
  dialog = QDialog(window)
@@ -458,3 +474,55 @@ def test_a_still_tour_does_not_repaint_on_every_tracker_tick(ui):
458
474
  QTest.qWait(800) # ten tracker ticks
459
475
  controller.overlay.removeEventFilter(counter)
460
476
  assert len(paints) <= 1
477
+
478
+
479
+ def test_close_only_mode_has_accessible_close_control_and_escape(ui):
480
+ app, _window, button, _other, controller = ui
481
+ controller.close_only = True
482
+ tutorial = Tutorial("interactive", "Interactive", (
483
+ TutorialStep("action", "run", "Action", "Click the action.", "clicked"),
484
+ ))
485
+ controller.start(tutorial)
486
+ overlay = controller.overlay
487
+ assert overlay.exit_button.text() == "×"
488
+ assert overlay.exit_button.accessibleName() == "Close tutorial"
489
+ assert overlay.exit_button.isVisible()
490
+ assert not overlay.back_button.isVisible() and not overlay.next_button.isVisible()
491
+ assert overlay.exit_button.geometry().right() > overlay.counter.geometry().right()
492
+ assert overlay.panel.rect().contains(overlay.exit_button.geometry())
493
+ QTest.mouseClick(overlay.exit_button, Qt.LeftButton)
494
+ assert not controller.is_active
495
+ controller.start(tutorial)
496
+ QTest.keyClick(controller.overlay.exit_button, Qt.Key_Escape)
497
+ assert not controller.is_active
498
+
499
+
500
+ def test_close_only_rejects_steps_that_need_next_button(ui):
501
+ _app, _window, _button, _other, controller = ui
502
+ controller.close_only = True
503
+ with pytest.raises(ValueError, match="require an event"):
504
+ controller.start(tour())
505
+ assert not controller.is_active
506
+
507
+
508
+ def test_close_only_follows_target_into_another_window(ui):
509
+ app, window, button, _other, controller = ui
510
+ dialog = QDialog(window)
511
+ action = QPushButton("Finish", dialog)
512
+ button.clicked.connect(dialog.show)
513
+ controller.targets["dialog"] = TutorialTarget(action)
514
+ controller.events["finish"] = TutorialEvent(action.clicked)
515
+ controller.close_only = True
516
+ controller.start(Tutorial("dialog", "Dialog", (
517
+ TutorialStep("open", "run", "Open", "Open dialog.", "clicked"),
518
+ TutorialStep("finish", "dialog", "Finish", "Finish the task.", "finish"),
519
+ )))
520
+ QTest.mouseClick(button, Qt.LeftButton)
521
+ app.processEvents()
522
+ overlay = controller.overlay
523
+ assert overlay.parentWidget() is dialog
524
+ assert overlay.exit_button.isVisible() and overlay.exit_button.text() == "×"
525
+ assert overlay.panel.rect().contains(overlay.exit_button.geometry())
526
+ QTest.mouseClick(overlay.exit_button, Qt.LeftButton)
527
+ assert not controller.is_active
528
+ dialog.close()
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.4.0",
3
- "distance": 0,
4
- "node": "g67c7e3524755a092256bc5b14b52b34dba401932",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-27"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes