ms-components 0.5.2__tar.gz → 0.5.4__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.5.2 → ms_components-0.5.4}/PKG-INFO +2 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/pyproject.toml +1 -1
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_executors.py +14 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/widget.py +40 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/browser.py +21 -11
- ms_components-0.5.4/src/ms_components/ms_pymol/icons/export.svg +5 -0
- ms_components-0.5.4/src/ms_components/ms_pymol/icons/save.svg +5 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/scene.py +1 -10
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/widget.py +99 -5
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/example_usage.py +2 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/metric_filter_bar.py +1 -1
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/palettes.py +3 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/step_dialog.py +33 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/tool_panel.py +2 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/PKG-INFO +2 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/SOURCES.txt +2 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/requires.txt +1 -1
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/scm_file_list.json +2 -0
- ms_components-0.5.4/src/ms_components.egg-info/scm_version.json +8 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_executors_ui.py +37 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_molsuite_monitor_widgets.py +116 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_pymol_scene.py +2 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_pymol_toolbar.py +86 -2
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_step_dialog.py +22 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_theme_contrast.py +15 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_tool_panel.py +2 -2
- ms_components-0.5.2/src/ms_components.egg-info/scm_version.json +0 -8
- {ms_components-0.5.2 → ms_components-0.5.4}/.github/workflows/docs.yml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/.github/workflows/publish.yml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/.gitignore +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/LICENSE +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/MANIFEST.in +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/README.md +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/examples/tutorial_demo.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/examples/tutorial_demo.toml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/licenses/qt-themes-LICENSE.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/setup.cfg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/backdrop.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/base.qss +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/chips.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ketcher.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/listview.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_dockwidget/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_dockwidget/widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/bridge.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/detail.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/formatting.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/jobs.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/models.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/palette.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/polling.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_monitor/resources.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/app.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/backend.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/scratch.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_projects/widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/command_line.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/box.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/center.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/display.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/hydrogens-polar.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/hydrogens.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/style.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_pymol/plugins.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_settings/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_settings/panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_stepper.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/action_header.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/cell_delegate.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/column_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/filter_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/columns.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/export.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/filter.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/menu.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/reload.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/search.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/settings.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/icons/sort.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/pagination_control.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/query_builder.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/session_provider.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/smart_table_view.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/sort_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/table_config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/table_interaction.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/table_model.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/test_external_filters.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_table/test_toolbar.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_tutorial/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_tutorial/bindings.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_tutorial/controller.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_tutorial/models.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/ms_tutorial/overlay.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/opengl.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/pagination.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/theme.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components/wheel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/dependency_links.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/src/ms_components.egg-info/top_level.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/conftest.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_app_settings.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_chips.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_dock_window.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_ketcher.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_molsuite_monitor_bridge.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_monitor_config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_ms_projects_widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_opengl.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_optional_integrations.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_progress_column.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_project_browser.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_pymol_command_line.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_pymol_grid.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_query_builder_keyset.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_shift_hscroll.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_split_button_style.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_table_interaction.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_table_toolbar.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_table_view_state.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/tests/test_tutorial.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.4}/zensical.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
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
|
|
@@ -25,7 +25,7 @@ Requires-Dist: ms_flow>=0.7.1; extra == "projects"
|
|
|
25
25
|
Provides-Extra: pymol
|
|
26
26
|
Requires-Dist: pymol-open-source>=3.2.0a0; extra == "pymol"
|
|
27
27
|
Provides-Extra: steps
|
|
28
|
-
Requires-Dist: ms_env>=0.
|
|
28
|
+
Requires-Dist: ms_env>=0.4.0; extra == "steps"
|
|
29
29
|
Provides-Extra: table
|
|
30
30
|
Requires-Dist: sqlmodel>=0.0.14; extra == "table"
|
|
31
31
|
Provides-Extra: all
|
|
@@ -33,7 +33,7 @@ dependencies = [
|
|
|
33
33
|
monitor = ["pydantic>=2.0"]
|
|
34
34
|
projects = ["ms_flow>=0.7.1"]
|
|
35
35
|
pymol = ["pymol-open-source>=3.2.0a0"]
|
|
36
|
-
steps = ["ms_env>=0.
|
|
36
|
+
steps = ["ms_env>=0.4.0"]
|
|
37
37
|
table = ["sqlmodel>=0.0.14"]
|
|
38
38
|
all = ["ms_components[monitor,projects,pymol,steps,table]"]
|
|
39
39
|
# Test + development tooling.
|
|
@@ -371,8 +371,9 @@ class KeptPage(QWidget):
|
|
|
371
371
|
|
|
372
372
|
threading.Thread(target=work, name="host-check", daemon=True).start()
|
|
373
373
|
|
|
374
|
-
def _sync(self) -> None:
|
|
375
|
-
"""`Workspace.host_env` for the host as it is saved: what the app prepares there, made as it is here.
|
|
374
|
+
def _sync(self, *_, unlock=None) -> None:
|
|
375
|
+
"""`Workspace.host_env` for the host as it is saved: what the app prepares there, made as it is here.
|
|
376
|
+
`unlock` first removes the lock of an environment there that the user said was left behind."""
|
|
376
377
|
name = self.names.currentItem().text()
|
|
377
378
|
host = self._kept()[name]
|
|
378
379
|
if host.env:
|
|
@@ -388,9 +389,12 @@ class KeptPage(QWidget):
|
|
|
388
389
|
|
|
389
390
|
def work() -> None:
|
|
390
391
|
try:
|
|
392
|
+
if unlock is not None:
|
|
393
|
+
unlock()
|
|
391
394
|
self._workspace.host_env(host.address, None, lambda line: self._synced.emit(line, False))
|
|
392
395
|
said = f"\n{name} is ready."
|
|
393
396
|
except Exception as error: # noqa: BLE001 - said in the page, where it was asked for
|
|
397
|
+
self._sync_error = error
|
|
394
398
|
said = f"\nCould not prepare {name}:\n{error}"
|
|
395
399
|
self._synced.emit(said, True)
|
|
396
400
|
|
|
@@ -402,6 +406,14 @@ class KeptPage(QWidget):
|
|
|
402
406
|
self.sync_button.setEnabled(last)
|
|
403
407
|
if last:
|
|
404
408
|
self.steps.setVisible(False)
|
|
409
|
+
error, self._sync_error = getattr(self, "_sync_error", None), None
|
|
410
|
+
if error is None:
|
|
411
|
+
return
|
|
412
|
+
from ms_components.step_dialog import offer_unlock # ms_env is the `steps` extra; a sync has it
|
|
413
|
+
|
|
414
|
+
forced = offer_unlock(self, error)
|
|
415
|
+
if forced:
|
|
416
|
+
self._sync(unlock=forced)
|
|
405
417
|
|
|
406
418
|
def _show_report(self, text: str) -> None:
|
|
407
419
|
self.report.setPlainText(text)
|
|
@@ -32,6 +32,7 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
32
32
|
super().__init__(parent)
|
|
33
33
|
self._bridge = None
|
|
34
34
|
self._snapshot: ProjectMonitorState | None = None
|
|
35
|
+
self._select_active_on_snapshot = False
|
|
35
36
|
# The failures of the selected job come from the journal: read again only when the job
|
|
36
37
|
# changes or fails more, not each poll, so the list does not jump while it is read.
|
|
37
38
|
self._failures_key = None
|
|
@@ -171,6 +172,39 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
171
172
|
self._restyle()
|
|
172
173
|
super().changeEvent(event)
|
|
173
174
|
|
|
175
|
+
def showEvent(self, event):
|
|
176
|
+
super().showEvent(event)
|
|
177
|
+
self.select_active_job()
|
|
178
|
+
# A hidden monitor can still hold the previous poll. Focus once more when
|
|
179
|
+
# fresh data arrives, unless the user changes selection in the meantime.
|
|
180
|
+
self._select_active_on_snapshot = True
|
|
181
|
+
if self._bridge is not None:
|
|
182
|
+
self._bridge.request_refresh()
|
|
183
|
+
|
|
184
|
+
def select_active_job(self) -> None:
|
|
185
|
+
"""Focus running work when opened, keeping an already selected running job.
|
|
186
|
+
|
|
187
|
+
The poller orders jobs newest first within active and finished groups. Waiting work
|
|
188
|
+
is the fallback when nothing is running; with no active work, keep the selection.
|
|
189
|
+
"""
|
|
190
|
+
jobs = self._jobs_view.jobs()
|
|
191
|
+
candidates = [job for job in jobs if job.status == "running"]
|
|
192
|
+
if not candidates:
|
|
193
|
+
candidates = [job for job in jobs if not job.is_terminal]
|
|
194
|
+
selected = self._selected_job()
|
|
195
|
+
if not candidates:
|
|
196
|
+
if selected is None and jobs:
|
|
197
|
+
self._jobs_view.select_job(jobs[0].job_id)
|
|
198
|
+
return
|
|
199
|
+
job_id = next((job.job_id for job in candidates if selected and job.job_id == selected.job_id),
|
|
200
|
+
candidates[0].job_id)
|
|
201
|
+
if job_id not in self._jobs_view.visible_job_ids():
|
|
202
|
+
self._search_input.clear()
|
|
203
|
+
self._status_filter.setCurrentIndex(0)
|
|
204
|
+
self._filter(None, None, host=False)
|
|
205
|
+
self._jobs_view.select_job(job_id)
|
|
206
|
+
self._jobs_view.scrollToItem(self._jobs_view.currentItem())
|
|
207
|
+
|
|
174
208
|
def attach_bridge(self, bridge) -> None:
|
|
175
209
|
if self._bridge is bridge:
|
|
176
210
|
return
|
|
@@ -198,6 +232,7 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
198
232
|
|
|
199
233
|
def clear_snapshot(self) -> None:
|
|
200
234
|
self._snapshot = None
|
|
235
|
+
self._select_active_on_snapshot = False
|
|
201
236
|
self._title_label.setText("No active project")
|
|
202
237
|
self._summary_label.setText("Open a project to monitor jobs.")
|
|
203
238
|
self._update_sync_state(None)
|
|
@@ -215,6 +250,8 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
215
250
|
self._failures_key = None
|
|
216
251
|
|
|
217
252
|
def update_snapshot(self, snapshot: ProjectMonitorState) -> None:
|
|
253
|
+
focus_active = self._select_active_on_snapshot
|
|
254
|
+
self._select_active_on_snapshot = False
|
|
218
255
|
self._snapshot = snapshot
|
|
219
256
|
if not snapshot.has_project:
|
|
220
257
|
self.clear_snapshot()
|
|
@@ -232,6 +269,8 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
232
269
|
self._jobs_view.set_jobs(snapshot.jobs)
|
|
233
270
|
self._hosts.set_jobs(snapshot.jobs)
|
|
234
271
|
self._rebuild_filters(snapshot)
|
|
272
|
+
if focus_active or self._selected_job() is None:
|
|
273
|
+
self.select_active_job()
|
|
235
274
|
self._show_selected()
|
|
236
275
|
|
|
237
276
|
def _rebuild_filters(self, snapshot: ProjectMonitorState) -> None:
|
|
@@ -298,6 +337,7 @@ class MolSuiteMonitorWidget(QWidget):
|
|
|
298
337
|
self._bridge.resume_job(job_id, executor)
|
|
299
338
|
|
|
300
339
|
def _on_current_job_changed(self, job_id) -> None:
|
|
340
|
+
self._select_active_on_snapshot = False
|
|
301
341
|
if self._bridge is not None:
|
|
302
342
|
self._bridge.select_job(job_id)
|
|
303
343
|
self._show_selected()
|
|
@@ -550,7 +550,7 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
550
550
|
painter.setBrush(QBrush(palette.color(QPalette.ColorRole.Highlight)))
|
|
551
551
|
painter.setPen(Qt.NoPen)
|
|
552
552
|
painter.drawRect(QRect(rect.left(), rect.top()+1, 3, rect.height()-1))
|
|
553
|
-
self._paint_name_col(painter, rect, project, palette)
|
|
553
|
+
self._paint_name_col(painter, rect, project, palette, option.font)
|
|
554
554
|
|
|
555
555
|
elif col in (_COL_META, _COL_PATH, _COL_DATE):
|
|
556
556
|
role = (
|
|
@@ -559,7 +559,7 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
559
559
|
else QPalette.ColorRole.PlaceholderText
|
|
560
560
|
)
|
|
561
561
|
painter.setPen(QPen(palette.color(role)))
|
|
562
|
-
painter.setFont(
|
|
562
|
+
painter.setFont(self._font(option.font, 9 if col == _COL_META else 8))
|
|
563
563
|
text = index.data(Qt.DisplayRole) or ""
|
|
564
564
|
flags = Qt.AlignLeft | Qt.AlignVCenter
|
|
565
565
|
if col == _COL_META:
|
|
@@ -568,10 +568,10 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
568
568
|
flags, text)
|
|
569
569
|
|
|
570
570
|
elif col == _COL_STAR:
|
|
571
|
-
self._paint_star(painter, rect, project, is_hover, palette)
|
|
571
|
+
self._paint_star(painter, rect, project, is_hover, palette, option.font)
|
|
572
572
|
|
|
573
573
|
elif col == _COL_MENU:
|
|
574
|
-
self._paint_menu_btn(painter, rect, is_hover, palette)
|
|
574
|
+
self._paint_menu_btn(painter, rect, is_hover, palette, option.font)
|
|
575
575
|
|
|
576
576
|
painter.restore()
|
|
577
577
|
|
|
@@ -581,6 +581,7 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
581
581
|
rect: QRect,
|
|
582
582
|
project: ProjectItem,
|
|
583
583
|
palette: QPalette,
|
|
584
|
+
base_font: QFont,
|
|
584
585
|
):
|
|
585
586
|
# Avatar
|
|
586
587
|
av_x = rect.left() + 10
|
|
@@ -592,18 +593,18 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
592
593
|
painter.drawEllipse(av_r)
|
|
593
594
|
tc = QColor("#202124") if color.lightness() > 150 else QColor("#ffffff")
|
|
594
595
|
painter.setPen(tc)
|
|
595
|
-
painter.setFont(
|
|
596
|
+
painter.setFont(self._font(base_font, 12, QFont.Bold))
|
|
596
597
|
painter.drawText(av_r, Qt.AlignCenter, (project.name or "?")[:1].upper())
|
|
597
598
|
|
|
598
599
|
# Name
|
|
599
600
|
tx = av_x + self._AVATAR + 10
|
|
600
|
-
painter.setFont(
|
|
601
|
+
painter.setFont(self._font(base_font, 11, QFont.Bold))
|
|
601
602
|
painter.setPen(palette.color(QPalette.ColorRole.Text))
|
|
602
603
|
painter.drawText(QRect(tx, rect.top()+8, rect.width()-tx-8, 20),
|
|
603
604
|
Qt.AlignLeft | Qt.AlignVCenter, project.name or "")
|
|
604
605
|
|
|
605
606
|
# Project id
|
|
606
|
-
painter.setFont(
|
|
607
|
+
painter.setFont(self._font(base_font, 8))
|
|
607
608
|
painter.setPen(palette.color(QPalette.ColorRole.PlaceholderText))
|
|
608
609
|
painter.drawText(
|
|
609
610
|
QRect(tx, rect.top() + 30, rect.width() - tx - 8, 16),
|
|
@@ -611,7 +612,16 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
611
612
|
f"ID: {project.id}",
|
|
612
613
|
)
|
|
613
614
|
|
|
614
|
-
|
|
615
|
+
@staticmethod
|
|
616
|
+
def _font(base_font: QFont, point_size: int, weight: QFont.Weight | None = None) -> QFont:
|
|
617
|
+
"""Keep the application's typeface while adjusting hierarchy in the project rows."""
|
|
618
|
+
font = QFont(base_font)
|
|
619
|
+
font.setPointSize(point_size)
|
|
620
|
+
if weight is not None:
|
|
621
|
+
font.setWeight(weight)
|
|
622
|
+
return font
|
|
623
|
+
|
|
624
|
+
def _paint_star(self, painter, rect, project, is_hover, palette: QPalette, base_font: QFont):
|
|
615
625
|
if project.favorite:
|
|
616
626
|
favorite = theme_color("yellow")
|
|
617
627
|
favorite_bg = QColor(favorite)
|
|
@@ -628,10 +638,10 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
628
638
|
painter.setPen(QPen(palette.color(QPalette.ColorRole.Text)))
|
|
629
639
|
else:
|
|
630
640
|
painter.setPen(QPen(palette.color(QPalette.ColorRole.PlaceholderText)))
|
|
631
|
-
painter.setFont(
|
|
641
|
+
painter.setFont(self._font(base_font, 12))
|
|
632
642
|
painter.drawText(rect, Qt.AlignCenter, "★")
|
|
633
643
|
|
|
634
|
-
def _paint_menu_btn(self, painter, rect, is_hover, palette: QPalette):
|
|
644
|
+
def _paint_menu_btn(self, painter, rect, is_hover, palette: QPalette, base_font: QFont):
|
|
635
645
|
if is_hover:
|
|
636
646
|
bh = self._BTN_SIZE
|
|
637
647
|
r = rect.adjusted(3, (rect.height()-bh)//2, -3, -(rect.height()-bh)//2)
|
|
@@ -643,7 +653,7 @@ class ProjectRowDelegate(QStyledItemDelegate):
|
|
|
643
653
|
if is_hover
|
|
644
654
|
else QPalette.ColorRole.PlaceholderText
|
|
645
655
|
)))
|
|
646
|
-
painter.setFont(
|
|
656
|
+
painter.setFont(self._font(base_font, 13, QFont.Bold))
|
|
647
657
|
painter.drawText(rect, Qt.AlignCenter, "⋯")
|
|
648
658
|
|
|
649
659
|
def editorEvent(self, event, model, option, index):
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
|
3
|
+
<path d="M17 21v-8H7v8"/>
|
|
4
|
+
<path d="M7 3v5h8"/>
|
|
5
|
+
</svg>
|
|
@@ -10,11 +10,10 @@ from __future__ import annotations
|
|
|
10
10
|
import math
|
|
11
11
|
from collections import OrderedDict
|
|
12
12
|
from collections.abc import Callable
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
|
|
15
14
|
from PySide6.QtCore import QEvent, QObject, QTimer
|
|
16
15
|
from PySide6.QtGui import QAction
|
|
17
|
-
from PySide6.QtWidgets import
|
|
16
|
+
from PySide6.QtWidgets import QMenu, QMessageBox, QWidget
|
|
18
17
|
|
|
19
18
|
from .widget import COMPARISON_COLORS
|
|
20
19
|
|
|
@@ -383,13 +382,6 @@ def add_scene_actions(
|
|
|
383
382
|
cmd.set("transparency", 0.55, name)
|
|
384
383
|
cmd.zoom(f"({name} or {ligand})", 4)
|
|
385
384
|
|
|
386
|
-
def save_png(cmd) -> None:
|
|
387
|
-
filename, _filter = QFileDialog.getSaveFileName(
|
|
388
|
-
parent, "Save PyMOL Image", str(Path.home() / "pymol.png"), "PNG Images (*.png)"
|
|
389
|
-
)
|
|
390
|
-
if filename:
|
|
391
|
-
cmd.png(filename, ray=1)
|
|
392
|
-
|
|
393
385
|
background = menu.addMenu("Background")
|
|
394
386
|
for label, color in (("White", "white"), ("Black", "black"), ("Grey", "grey20")):
|
|
395
387
|
menu_action(background, label, run("set the background", lambda cmd, c=color: cmd.bg_color(c)))
|
|
@@ -402,7 +394,6 @@ def add_scene_actions(
|
|
|
402
394
|
menu_action(render, "Shadows On", run("set shadows", lambda cmd: cmd.set("ray_shadows", 1)))
|
|
403
395
|
menu_action(render, "Shadows Off", run("set shadows", lambda cmd: cmd.set("ray_shadows", 0)))
|
|
404
396
|
menu_action(render, "Ray", run("ray-trace", lambda cmd: cmd.ray()))
|
|
405
|
-
menu_action(render, "Save PNG…", run("save the PNG", save_png))
|
|
406
397
|
|
|
407
398
|
surface = menu.addMenu("Surface")
|
|
408
399
|
menu_action(surface, "Vacuum Electrostatics…", lambda: _vacuum_electrostatics(dock, parent),
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from collections.abc import Callable, Mapping
|
|
6
7
|
from dataclasses import dataclass, field, replace
|
|
7
8
|
from pathlib import Path
|
|
@@ -16,6 +17,7 @@ from PySide6.QtCore import QByteArray, QEvent, QSize, Qt, QTimer, Signal
|
|
|
16
17
|
from PySide6.QtGui import QAction, QActionGroup, QGuiApplication, QIcon, QPainter, QPixmap
|
|
17
18
|
from PySide6.QtSvg import QSvgRenderer
|
|
18
19
|
from PySide6.QtWidgets import (
|
|
20
|
+
QFileDialog,
|
|
19
21
|
QFrame,
|
|
20
22
|
QHBoxLayout,
|
|
21
23
|
QLabel,
|
|
@@ -215,7 +217,7 @@ class PymolControlBar(QFrame):
|
|
|
215
217
|
bar.setContentsMargins(0, 0, 0, 0)
|
|
216
218
|
bar.setIconSize(QSize(16, 16))
|
|
217
219
|
bar.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
|
218
|
-
outer.addWidget(bar
|
|
220
|
+
outer.addWidget(bar)
|
|
219
221
|
|
|
220
222
|
selection_menu = QMenu("Selection", bar)
|
|
221
223
|
selection_group = QActionGroup(selection_menu)
|
|
@@ -306,12 +308,30 @@ class PymolControlBar(QFrame):
|
|
|
306
308
|
menu=self.preset_menu,
|
|
307
309
|
)
|
|
308
310
|
|
|
311
|
+
self.export_menu = QMenu("Export", bar)
|
|
312
|
+
self.export_pdb_action = self.export_menu.addAction("Save displayed complex as PDB…")
|
|
313
|
+
self.export_pdb_action.setToolTip("Export the displayed receptor and ligand as a PDB file.")
|
|
314
|
+
self.export_pdb_action.triggered.connect(
|
|
315
|
+
lambda checked=False: self._save_complex_pdb()
|
|
316
|
+
)
|
|
317
|
+
self.export_png_action = self.export_menu.addAction("Save ray-traced PNG…")
|
|
318
|
+
self.export_png_action.setToolTip("Save a ray-traced image at 300 dpi, sized for print.")
|
|
319
|
+
self.export_png_action.triggered.connect(
|
|
320
|
+
lambda checked=False: self._save_ray_traced_png()
|
|
321
|
+
)
|
|
322
|
+
self.export_action = self._add_toolbar_action(
|
|
323
|
+
"Export", "save.svg",
|
|
324
|
+
"Export the displayed structure as PDB or save a print-quality PNG.",
|
|
325
|
+
menu=self.export_menu,
|
|
326
|
+
)
|
|
327
|
+
|
|
309
328
|
self.context_label = _ElidedLabel("Generic", self)
|
|
310
329
|
self.context_label.setObjectName("PymolSceneContext")
|
|
311
330
|
self.context_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
|
312
|
-
|
|
331
|
+
# Ignored: the tools keep their width and the scene name takes only what is left.
|
|
332
|
+
self.context_label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
|
|
313
333
|
self.context_label.setToolTip("Current molecular scene context.")
|
|
314
|
-
outer.addWidget(self.context_label)
|
|
334
|
+
outer.addWidget(self.context_label, 1)
|
|
315
335
|
|
|
316
336
|
self._register_builtin_presets()
|
|
317
337
|
self._sync_selection_mode()
|
|
@@ -322,6 +342,69 @@ class PymolControlBar(QFrame):
|
|
|
322
342
|
if self._cmd is None:
|
|
323
343
|
self.setEnabled(False)
|
|
324
344
|
|
|
345
|
+
def _export_selection(self) -> str:
|
|
346
|
+
"""Prefer the visible complex's named components over an ambiguous `all` target."""
|
|
347
|
+
selections = dict(self._context.selections or {})
|
|
348
|
+
components = [
|
|
349
|
+
selections[role]
|
|
350
|
+
for role in ("receptor", "ligand")
|
|
351
|
+
if selections.get(role)
|
|
352
|
+
]
|
|
353
|
+
if components:
|
|
354
|
+
return " or ".join(f"({selection})" for selection in components)
|
|
355
|
+
return str(self._context.target or "all").strip() or "all"
|
|
356
|
+
|
|
357
|
+
def _export_stem(self) -> str:
|
|
358
|
+
context = self._context
|
|
359
|
+
selections = dict(context.selections or {})
|
|
360
|
+
components = [
|
|
361
|
+
selections[role]
|
|
362
|
+
for role in ("receptor", "ligand")
|
|
363
|
+
if selections.get(role)
|
|
364
|
+
]
|
|
365
|
+
label = (
|
|
366
|
+
"complex"
|
|
367
|
+
if len(components) > 1 or context.kind == "complex"
|
|
368
|
+
else (components[0] if components else context.target)
|
|
369
|
+
)
|
|
370
|
+
stem = re.sub(r"[^A-Za-z0-9._-]+", "_", str(label or context.kind or "pymol")).strip("._-")
|
|
371
|
+
return stem or "pymol"
|
|
372
|
+
|
|
373
|
+
def _save_complex_pdb(self) -> None:
|
|
374
|
+
filename, _selected_filter = QFileDialog.getSaveFileName(
|
|
375
|
+
self,
|
|
376
|
+
"Save displayed complex as PDB",
|
|
377
|
+
str(Path.home() / f"{self._export_stem()}.pdb"),
|
|
378
|
+
"PDB files (*.pdb)",
|
|
379
|
+
)
|
|
380
|
+
if not filename:
|
|
381
|
+
return
|
|
382
|
+
path = Path(filename)
|
|
383
|
+
if path.suffix.lower() != ".pdb":
|
|
384
|
+
path = path.with_suffix(".pdb")
|
|
385
|
+
selection = self._export_selection()
|
|
386
|
+
self._run_command(
|
|
387
|
+
"Export PDB",
|
|
388
|
+
lambda: self._cmd.save(str(path), selection=selection, state=-1, format="pdb"),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def _save_ray_traced_png(self) -> None:
|
|
392
|
+
filename, _selected_filter = QFileDialog.getSaveFileName(
|
|
393
|
+
self,
|
|
394
|
+
"Save ray-traced PyMOL image",
|
|
395
|
+
str(Path.home() / f"{self._export_stem()}.png"),
|
|
396
|
+
"PNG images (*.png)",
|
|
397
|
+
)
|
|
398
|
+
if not filename:
|
|
399
|
+
return
|
|
400
|
+
path = Path(filename)
|
|
401
|
+
if path.suffix.lower() != ".png":
|
|
402
|
+
path = path.with_suffix(".png")
|
|
403
|
+
self._run_command(
|
|
404
|
+
"Save PNG",
|
|
405
|
+
lambda: self._cmd.png(str(path), width="8in", dpi=300, ray=1),
|
|
406
|
+
)
|
|
407
|
+
|
|
325
408
|
def _add_toolbar_action(
|
|
326
409
|
self,
|
|
327
410
|
text: str,
|
|
@@ -342,12 +425,23 @@ class PymolControlBar(QFrame):
|
|
|
342
425
|
self.toolbar.addAction(action)
|
|
343
426
|
self._toolbar_action_icons[action] = icon_name
|
|
344
427
|
|
|
428
|
+
self._configure_action_button(action)
|
|
429
|
+
return action
|
|
430
|
+
|
|
431
|
+
def _configure_action_button(self, action: QAction) -> None:
|
|
345
432
|
button = self.toolbar.widgetForAction(action)
|
|
346
433
|
if isinstance(button, QToolButton):
|
|
347
434
|
button.setObjectName("PymolToolbarButton")
|
|
348
|
-
if menu is not None:
|
|
435
|
+
if action.menu() is not None:
|
|
349
436
|
button.setPopupMode(QToolButton.InstantPopup)
|
|
350
|
-
|
|
437
|
+
|
|
438
|
+
def move_action_to_end(self, action: QAction) -> None:
|
|
439
|
+
"""Move an existing control, preserving its menu-button behavior."""
|
|
440
|
+
self.toolbar.removeAction(action)
|
|
441
|
+
self.toolbar.addAction(action)
|
|
442
|
+
# Qt creates a new QToolButton on reinsertion, losing its popup mode.
|
|
443
|
+
self._configure_action_button(action)
|
|
444
|
+
self._configure_overflow_button()
|
|
351
445
|
|
|
352
446
|
def add_menu_action(
|
|
353
447
|
self,
|
|
@@ -360,12 +360,12 @@ def main():
|
|
|
360
360
|
|
|
361
361
|
table = SmartTableView(db=db, config=config)
|
|
362
362
|
table.row_double_clicked.connect(
|
|
363
|
-
lambda obj: print(f"Double click: Order #{obj.id}
|
|
363
|
+
lambda obj: print(f"Double click: Order #{obj.id} - {obj.product}")
|
|
364
364
|
)
|
|
365
365
|
layout.addWidget(table)
|
|
366
366
|
|
|
367
367
|
window.setCentralWidget(central)
|
|
368
|
-
window.setWindowTitle("SmartTableView
|
|
368
|
+
window.setWindowTitle("SmartTableView - Demo")
|
|
369
369
|
window.resize(1100, 700)
|
|
370
370
|
window.show()
|
|
371
371
|
|
|
@@ -127,7 +127,7 @@ class MetricFilterBar(QWidget):
|
|
|
127
127
|
*,
|
|
128
128
|
decimals: int = 3,
|
|
129
129
|
default_value: float = 0.0,
|
|
130
|
-
empty_text: str = "No filters
|
|
130
|
+
empty_text: str = "No filters - everything is shown.",
|
|
131
131
|
):
|
|
132
132
|
super().__init__(parent)
|
|
133
133
|
self.setStyleSheet(CHIP_STYLE)
|
|
@@ -29,8 +29,11 @@ THEMES: dict[str, dict[str, str]] = {
|
|
|
29
29
|
"catppuccin_macchiato": {"window": "#24273a", "window_text": "#cad3f5", "base": "#1e2030", "alternate_base": "#24273a", "tooltip_base": "#1e2030", "tooltip_text": "#939ab7", "placeholder_text": "#888eaa", "text": "#cad3f5", "button": "#24273a", "button_text": "#cad3f5", "bright_text": "#08090a", "light": "#181926", "midlight": "#1e2030", "mid": "#5b6078", "dark": "#494d64", "shadow": "#6e738d", "highlight": "#c6a0f6", "highlighted_text": "#181926", "link": "#8aadf4", "link_visited": "#8aadf4", "accent": "#8aadf4", "disabled_text": "#61677f", "red": "#ed8796", "orange": "#f5a97f", "yellow": "#eed49f", "green": "#a6da95", "cyan": "#91d7e3", "blue": "#8aadf4", "magenta": "#c6a0f6"},
|
|
30
30
|
"catppuccin_mocha": {"window": "#1e1e2e", "window_text": "#cdd6f4", "base": "#181825", "alternate_base": "#1e1e2e", "tooltip_base": "#181825", "tooltip_text": "#9399b2", "placeholder_text": "#82879f", "text": "#cdd6f4", "button": "#1e1e2e", "button_text": "#cdd6f4", "bright_text": "#090a0b", "light": "#11111b", "midlight": "#181825", "mid": "#585b70", "dark": "#45475a", "shadow": "#6c7086", "highlight": "#cba6f7", "highlighted_text": "#11111b", "link": "#89b4fa", "link_visited": "#89b4fa", "accent": "#89b4fa", "disabled_text": "#5d6075", "red": "#f38ba8", "orange": "#fab387", "yellow": "#f9e2af", "green": "#a6e3a1", "cyan": "#89dceb", "blue": "#89b4fa", "magenta": "#cba6f7"},
|
|
31
31
|
"dracula": {"window": "#2d2f3c", "window_text": "#f8f8f2", "base": "#282a36", "alternate_base": "#2d2f3c", "tooltip_base": "#282a36", "tooltip_text": "#9ea0a6", "placeholder_text": "#97989b", "text": "#f8f8f2", "button": "#2d2f3c", "button_text": "#f8f8f2", "bright_text": "#070707", "light": "#1d1b22", "midlight": "#282a36", "mid": "#44475a", "dark": "#474959", "shadow": "#767985", "highlight": "#bd93f9", "highlighted_text": "#1d1b22", "link": "#ff79c6", "link_visited": "#ff79c6", "accent": "#ff79c6", "disabled_text": "#6e6f76", "red": "#ff6262", "orange": "#ffb86c", "yellow": "#f1fa8c", "green": "#50fa7b", "cyan": "#8be9fd", "blue": "#8a96bb", "magenta": "#bd93f9"},
|
|
32
|
+
"glen_light": {"window": "#f6f8fa", "window_text": "#1f2328", "base": "#ffffff", "alternate_base": "#fbfcfd", "tooltip_base": "#fbfcfd", "tooltip_text": "#444952", "placeholder_text": "#61666e", "text": "#1f2328", "button": "#f6f8fa", "button_text": "#1f2328", "bright_text": "#a7bcd7", "light": "#ffffff", "midlight": "#fbfcfd", "mid": "#9da1a6", "dark": "#bbbec2", "shadow": "#7f838a", "highlight": "#db68aa", "highlighted_text": "#1d1b22", "link": "#8164aa", "link_visited": "#8164aa", "accent": "#8164aa", "disabled_text": "#979a9e", "red": "#cf1d3d", "orange": "#b25600", "yellow": "#776d00", "green": "#1a7f37", "cyan": "#0a7a8a", "blue": "#2f5fd0", "magenta": "#b0337e"},
|
|
32
33
|
"github_dark": {"window": "#262c36", "window_text": "#d1d7e0", "base": "#212830", "alternate_base": "#262c36", "tooltip_base": "#212830", "tooltip_text": "#989ea7", "placeholder_text": "#8d939c", "text": "#d1d7e0", "button": "#262c36", "button_text": "#d1d7e0", "bright_text": "#1d1e1f", "light": "#151b23", "midlight": "#212830", "mid": "#5f656f", "dark": "#4c525c", "shadow": "#727882", "highlight": "#79c0ff", "highlighted_text": "#151b23", "link": "#79c0ff", "link_visited": "#79c0ff", "accent": "#79c0ff", "disabled_text": "#656b75", "red": "#ffa198", "orange": "#ffa657", "yellow": "#e3b341", "green": "#56d364", "cyan": "#68cab2", "blue": "#79c0ff", "magenta": "#d2a8ff"},
|
|
33
34
|
"github_light": {"window": "#f6f8fa", "window_text": "#1f2328", "base": "#ffffff", "alternate_base": "#fbfcfd", "tooltip_base": "#fbfcfd", "tooltip_text": "#444952", "placeholder_text": "#61666e", "text": "#1f2328", "button": "#f6f8fa", "button_text": "#1f2328", "bright_text": "#a7bcd7", "light": "#ffffff", "midlight": "#fbfcfd", "mid": "#9da1a6", "dark": "#bbbec2", "shadow": "#7f838a", "highlight": "#5a94c8", "highlighted_text": "#1f2328", "link": "#0071d5", "link_visited": "#0071d5", "accent": "#0071d5", "disabled_text": "#979a9e", "red": "#e21400", "orange": "#b85600", "yellow": "#8f6b15", "green": "#22822d", "cyan": "#2c7e6a", "blue": "#0071d5", "magenta": "#9838ff"},
|
|
35
|
+
"github_light_frappe": {"window": "#f6f8fa", "window_text": "#1f2328", "base": "#ffffff", "alternate_base": "#fbfcfd", "tooltip_base": "#fbfcfd", "tooltip_text": "#444952", "placeholder_text": "#61666e", "text": "#1f2328", "button": "#f6f8fa", "button_text": "#1f2328", "bright_text": "#a7bcd7", "light": "#ffffff", "midlight": "#fbfcfd", "mid": "#9da1a6", "dark": "#bbbec2", "shadow": "#7f838a", "highlight": "#a682bd", "highlighted_text": "#232634", "link": "#5d719f", "link_visited": "#5d719f", "accent": "#5d719f", "disabled_text": "#979a9e", "red": "#a65d5f", "orange": "#9a664c", "yellow": "#807051", "green": "#607950", "cyan": "#57777d", "blue": "#5d719f", "magenta": "#846797"},
|
|
36
|
+
"LF_light": {"window": "#f6f8fa", "window_text": "#1f2328", "base": "#ffffff", "alternate_base": "#fbfcfd", "tooltip_base": "#fbfcfd", "tooltip_text": "#444952", "placeholder_text": "#61666e", "text": "#1f2328", "button": "#f6f8fa", "button_text": "#1f2328", "bright_text": "#a7bcd7", "light": "#ffffff", "midlight": "#fbfcfd", "mid": "#9da1a6", "dark": "#bbbec2", "shadow": "#7f838a", "highlight": "#9f7aea", "highlighted_text": "#1f2328", "link": "#7c3aed", "link_visited": "#7c3aed", "accent": "#7c3aed", "disabled_text": "#979a9e", "red": "#e21400", "orange": "#b85600", "yellow": "#8f6b15", "green": "#22822d", "cyan": "#2c7e6a", "blue": "#0071d5", "magenta": "#9838ff"},
|
|
34
37
|
"modern_dark": {"window": "#2a2a2a", "window_text": "#c8c8c8", "base": "#1f1f1f", "alternate_base": "#2a2a2a", "tooltip_base": "#1f1f1f", "tooltip_text": "#868686", "placeholder_text": "#919191", "text": "#c8c8c8", "button": "#2a2a2a", "button_text": "#c8c8c8", "bright_text": "#373737", "light": "#141414", "midlight": "#1f1f1f", "mid": "#424242", "dark": "#444444", "shadow": "#737373", "highlight": "#2aa5da", "highlighted_text": "#141414", "link": "#2ad7da", "link_visited": "#2ad7da", "accent": "#2ad7da", "disabled_text": "#696969", "red": "#e16f53", "orange": "#da8a2a", "yellow": "#d7da2a", "green": "#2ada6f", "cyan": "#2ad7da", "blue": "#2aa5da", "magenta": "#c06fe4"},
|
|
35
38
|
"modern_light": {"window": "#e6ebf0", "window_text": "#000000", "base": "#ffffff", "alternate_base": "#eef2f6", "tooltip_base": "#eef2f6", "tooltip_text": "#484b4d", "placeholder_text": "#626568", "text": "#000000", "button": "#e6ebf0", "button_text": "#000000", "bright_text": "#ffffff", "light": "#ffffff", "midlight": "#eef2f6", "mid": "#979b9f", "dark": "#b1b6ba", "shadow": "#7d8084", "highlight": "#2590be", "highlighted_text": "#000000", "link": "#157677", "link_visited": "#157677", "accent": "#157677", "disabled_text": "#8f9295", "red": "#bb3f20", "orange": "#945c1a", "yellow": "#6b6d13", "green": "#157a3d", "cyan": "#157677", "blue": "#1a7196", "magenta": "#a22ad7"},
|
|
36
39
|
"monokai": {"window": "#2d2a2e", "window_text": "#fcfcfa", "base": "#221f22", "alternate_base": "#2d2a2e", "tooltip_base": "#221f22", "tooltip_text": "#939293", "placeholder_text": "#939293", "text": "#fcfcfa", "button": "#2d2a2e", "button_text": "#fcfcfa", "bright_text": "#030303", "light": "#19181a", "midlight": "#221f22", "mid": "#5b595c", "dark": "#4e4c4f", "shadow": "#757375", "highlight": "#ffd866", "highlighted_text": "#19181a", "link": "#ff6188", "link_visited": "#ff6188", "accent": "#ff6188", "disabled_text": "#6b696b", "red": "#ff6188", "orange": "#fc9867", "yellow": "#ffd866", "green": "#a9dc76", "cyan": "#91dcaf", "blue": "#78dce8", "magenta": "#ab9df2"},
|
|
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import (
|
|
|
18
18
|
QDialogButtonBox,
|
|
19
19
|
QLabel,
|
|
20
20
|
QListWidget,
|
|
21
|
+
QMessageBox,
|
|
21
22
|
QPlainTextEdit,
|
|
22
23
|
QProgressBar,
|
|
23
24
|
QVBoxLayout,
|
|
@@ -142,9 +143,16 @@ class StepRunnerDialog(QDialog):
|
|
|
142
143
|
self._on_success()
|
|
143
144
|
self.finished_ok.emit(ok)
|
|
144
145
|
|
|
146
|
+
# The run is not left behind by Esc or the window's close either: a blocking caller would go
|
|
147
|
+
# on (and give up its locks) while the steps still change things.
|
|
148
|
+
def reject(self) -> None:
|
|
149
|
+
if not self._worker.isRunning():
|
|
150
|
+
super().reject()
|
|
151
|
+
|
|
145
152
|
def closeEvent(self, event) -> None: # noqa: N802 - Qt naming
|
|
146
153
|
if self._worker.isRunning():
|
|
147
|
-
|
|
154
|
+
event.ignore()
|
|
155
|
+
return
|
|
148
156
|
super().closeEvent(event)
|
|
149
157
|
|
|
150
158
|
|
|
@@ -168,4 +176,27 @@ def run_steps_dialog(
|
|
|
168
176
|
return dialog
|
|
169
177
|
|
|
170
178
|
|
|
171
|
-
|
|
179
|
+
def offer_unlock(parent: QWidget, error: BaseException | None) -> Callable[[], dict] | None:
|
|
180
|
+
"""When `error` is, or was raised from, an `ms_env.Locked`, ask whether to remove that lock.
|
|
181
|
+
Returns what removes it, to be run off the GUI thread (it may go over ssh) before trying
|
|
182
|
+
again; nothing when the error is another or the user said no."""
|
|
183
|
+
from ms_env import Locked, unlock
|
|
184
|
+
|
|
185
|
+
while error is not None and not isinstance(error, Locked):
|
|
186
|
+
error = error.__cause__ or error.__context__
|
|
187
|
+
if error is None:
|
|
188
|
+
return None
|
|
189
|
+
owner, where = error.owner, error.host.address or "this machine"
|
|
190
|
+
asked = QMessageBox.question(
|
|
191
|
+
parent, "Environment locked",
|
|
192
|
+
f"{error.prefix} on {where} is being changed by {owner.get('client', 'an unknown client')} "
|
|
193
|
+
f"(pid {owner.get('pid', '?')}) since {owner.get('since', '?')}.\n\n"
|
|
194
|
+
"Remove the lock only if that client has stopped: two clients changing one environment break it.\n\n"
|
|
195
|
+
"Remove the lock and try again?",
|
|
196
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
|
|
197
|
+
if asked != QMessageBox.StandardButton.Yes:
|
|
198
|
+
return None
|
|
199
|
+
return lambda: unlock(error.prefix, error.host)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
__all__ = ["StepRunnerDialog", "StepWorker", "offer_unlock", "run_steps_dialog"]
|
|
@@ -338,13 +338,13 @@ class ActionBar(ContentBar):
|
|
|
338
338
|
if total:
|
|
339
339
|
self.progress.setRange(0, int(total))
|
|
340
340
|
self.progress.setValue(min(done, int(total)))
|
|
341
|
-
headline = f"{stage}
|
|
341
|
+
headline = f"{stage} - {format_count(done)} / {format_count(total)} {unit}"
|
|
342
342
|
if eta_seconds is None:
|
|
343
343
|
eta_seconds = self._estimate_eta(done, int(total))
|
|
344
344
|
parts.append("Estimating time…" if eta_seconds is None else format_eta(eta_seconds))
|
|
345
345
|
else:
|
|
346
346
|
self.progress.setRange(0, 0)
|
|
347
|
-
headline = f"{stage}
|
|
347
|
+
headline = f"{stage} - {format_count(done)} {unit}"
|
|
348
348
|
if detail:
|
|
349
349
|
parts.append(detail)
|
|
350
350
|
self._show(headline, " · ".join(parts), failures)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
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
|
|
@@ -25,7 +25,7 @@ Requires-Dist: ms_flow>=0.7.1; extra == "projects"
|
|
|
25
25
|
Provides-Extra: pymol
|
|
26
26
|
Requires-Dist: pymol-open-source>=3.2.0a0; extra == "pymol"
|
|
27
27
|
Provides-Extra: steps
|
|
28
|
-
Requires-Dist: ms_env>=0.
|
|
28
|
+
Requires-Dist: ms_env>=0.4.0; extra == "steps"
|
|
29
29
|
Provides-Extra: table
|
|
30
30
|
Requires-Dist: sqlmodel>=0.0.14; extra == "table"
|
|
31
31
|
Provides-Extra: all
|
|
@@ -62,6 +62,7 @@ src/ms_components/ms_pymol/icons/actions.svg
|
|
|
62
62
|
src/ms_components/ms_pymol/icons/box.svg
|
|
63
63
|
src/ms_components/ms_pymol/icons/center.svg
|
|
64
64
|
src/ms_components/ms_pymol/icons/display.svg
|
|
65
|
+
src/ms_components/ms_pymol/icons/export.svg
|
|
65
66
|
src/ms_components/ms_pymol/icons/hide.svg
|
|
66
67
|
src/ms_components/ms_pymol/icons/hydrogens-polar.svg
|
|
67
68
|
src/ms_components/ms_pymol/icons/hydrogens.svg
|
|
@@ -69,6 +70,7 @@ src/ms_components/ms_pymol/icons/menu.svg
|
|
|
69
70
|
src/ms_components/ms_pymol/icons/orient.svg
|
|
70
71
|
src/ms_components/ms_pymol/icons/presets.svg
|
|
71
72
|
src/ms_components/ms_pymol/icons/rock.svg
|
|
73
|
+
src/ms_components/ms_pymol/icons/save.svg
|
|
72
74
|
src/ms_components/ms_pymol/icons/selection.svg
|
|
73
75
|
src/ms_components/ms_pymol/icons/settings.svg
|
|
74
76
|
src/ms_components/ms_pymol/icons/style.svg
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"src/ms_components/ms_pymol/icons/box.svg",
|
|
55
55
|
"src/ms_components/ms_pymol/icons/center.svg",
|
|
56
56
|
"src/ms_components/ms_pymol/icons/display.svg",
|
|
57
|
+
"src/ms_components/ms_pymol/icons/export.svg",
|
|
57
58
|
"src/ms_components/ms_pymol/icons/hide.svg",
|
|
58
59
|
"src/ms_components/ms_pymol/icons/hydrogens-polar.svg",
|
|
59
60
|
"src/ms_components/ms_pymol/icons/hydrogens.svg",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"src/ms_components/ms_pymol/icons/orient.svg",
|
|
62
63
|
"src/ms_components/ms_pymol/icons/presets.svg",
|
|
63
64
|
"src/ms_components/ms_pymol/icons/rock.svg",
|
|
65
|
+
"src/ms_components/ms_pymol/icons/save.svg",
|
|
64
66
|
"src/ms_components/ms_pymol/icons/selection.svg",
|
|
65
67
|
"src/ms_components/ms_pymol/icons/settings.svg",
|
|
66
68
|
"src/ms_components/ms_pymol/icons/style.svg",
|