ms-components 0.5.2__tar.gz → 0.5.3__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.3}/PKG-INFO +1 -1
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/widget.py +40 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/browser.py +21 -11
- ms_components-0.5.3/src/ms_components/ms_pymol/icons/export.svg +5 -0
- ms_components-0.5.3/src/ms_components/ms_pymol/icons/save.svg +5 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/scene.py +1 -10
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/widget.py +99 -5
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/example_usage.py +2 -2
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/metric_filter_bar.py +1 -1
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/palettes.py +3 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/tool_panel.py +2 -2
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/PKG-INFO +1 -1
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/SOURCES.txt +2 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/scm_file_list.json +2 -0
- ms_components-0.5.3/src/ms_components.egg-info/scm_version.json +8 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_molsuite_monitor_widgets.py +116 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_pymol_scene.py +2 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_pymol_toolbar.py +86 -2
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_theme_contrast.py +15 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/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.3}/.github/workflows/docs.yml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/.github/workflows/publish.yml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/.gitignore +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/LICENSE +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/MANIFEST.in +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/README.md +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/examples/tutorial_demo.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/examples/tutorial_demo.toml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/licenses/qt-themes-LICENSE.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/pyproject.toml +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/setup.cfg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/backdrop.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/base.qss +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/chips.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ketcher.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/listview.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_dockwidget/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_dockwidget/widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_executors.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/bridge.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/detail.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/formatting.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/jobs.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/models.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/palette.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/polling.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_monitor/resources.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/app.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/backend.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/scratch.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_projects/widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/command_line.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/box.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/center.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/display.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/hydrogens-polar.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/hydrogens.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/style.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_pymol/plugins.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_settings/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_settings/panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_stepper.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/action_header.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/cell_delegate.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/column_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/filter_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/columns.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/export.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/filter.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/menu.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/reload.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/search.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/settings.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/icons/sort.svg +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/pagination_control.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/query_builder.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/session_provider.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/smart_table_view.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/sort_panel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/table_config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/table_interaction.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/table_model.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/test_external_filters.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_table/test_toolbar.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_tutorial/__init__.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_tutorial/bindings.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_tutorial/controller.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_tutorial/models.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/ms_tutorial/overlay.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/opengl.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/pagination.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/step_dialog.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/theme.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components/wheel.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/dependency_links.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/requires.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/src/ms_components.egg-info/top_level.txt +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/conftest.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_app_settings.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_chips.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_dock_window.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_executors_ui.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_ketcher.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_molsuite_monitor_bridge.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_monitor_config.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_ms_projects_widget.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_opengl.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_optional_integrations.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_progress_column.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_project_browser.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_pymol_command_line.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_pymol_grid.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_query_builder_keyset.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_shift_hscroll.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_split_button_style.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_step_dialog.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_table_interaction.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_table_toolbar.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_table_view_state.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/tests/test_tutorial.py +0 -0
- {ms_components-0.5.2 → ms_components-0.5.3}/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.3
|
|
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
|
|
@@ -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"},
|
|
@@ -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.3
|
|
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
|
|
@@ -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",
|
|
@@ -119,6 +119,122 @@ def test_monitor_widget_lists_filters_and_selects_the_jobs_and_empties_without_a
|
|
|
119
119
|
assert widget._detail._name.text() == "Select a job"
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
def test_opening_monitor_focuses_running_work_but_polling_keeps_manual_selection():
|
|
123
|
+
app = QApplication.instance() or QApplication([])
|
|
124
|
+
widget = MolSuiteMonitorWidget()
|
|
125
|
+
snapshot = ProjectMonitorState(project_id="p", has_project=True, jobs=[
|
|
126
|
+
_job(job_id="waiting", status="waiting"),
|
|
127
|
+
_job(job_id="running", name="P2Rank"),
|
|
128
|
+
_job(job_id="other-running"),
|
|
129
|
+
_job(job_id="completed", status="completed"),
|
|
130
|
+
])
|
|
131
|
+
widget.update_snapshot(snapshot)
|
|
132
|
+
assert widget._selected_job_id() == "running"
|
|
133
|
+
widget.show()
|
|
134
|
+
app.processEvents()
|
|
135
|
+
try:
|
|
136
|
+
widget._jobs_view.select_job("completed")
|
|
137
|
+
widget.update_snapshot(snapshot)
|
|
138
|
+
assert widget._selected_job_id() == "completed"
|
|
139
|
+
widget.hide()
|
|
140
|
+
widget.show()
|
|
141
|
+
app.processEvents()
|
|
142
|
+
assert widget._selected_job_id() == "running"
|
|
143
|
+
assert widget._detail._name.text() == "P2Rank"
|
|
144
|
+
assert widget._cancel_button.isEnabled()
|
|
145
|
+
|
|
146
|
+
widget._jobs_view.select_job("other-running")
|
|
147
|
+
widget.hide()
|
|
148
|
+
widget.show()
|
|
149
|
+
app.processEvents()
|
|
150
|
+
assert widget._selected_job_id() == "other-running"
|
|
151
|
+
finally:
|
|
152
|
+
widget.close()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_opening_monitor_reveals_running_job_hidden_by_old_filters():
|
|
156
|
+
app = QApplication.instance() or QApplication([])
|
|
157
|
+
widget = MolSuiteMonitorWidget()
|
|
158
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[
|
|
159
|
+
_job(job_id="running", name="P2Rank", executor="local"),
|
|
160
|
+
_job(job_id="completed", name="import", status="completed", executor="other"),
|
|
161
|
+
]))
|
|
162
|
+
widget._jobs_view.select_job("completed")
|
|
163
|
+
widget._search_input.setText("import")
|
|
164
|
+
widget._status_filter.setCurrentIndex(widget._status_filter.findData("completed"))
|
|
165
|
+
widget._filter({"other"}, "executor: other", host=False)
|
|
166
|
+
widget.show()
|
|
167
|
+
app.processEvents()
|
|
168
|
+
try:
|
|
169
|
+
assert widget._selected_job_id() == "running"
|
|
170
|
+
assert "running" in widget._jobs_view.visible_job_ids()
|
|
171
|
+
assert widget._search_input.text() == ""
|
|
172
|
+
assert widget._status_filter.currentData() == "all"
|
|
173
|
+
assert widget._filter_chip.isHidden()
|
|
174
|
+
finally:
|
|
175
|
+
widget.close()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_monitor_opened_before_first_snapshot_selects_work_when_it_arrives():
|
|
179
|
+
app = QApplication.instance() or QApplication([])
|
|
180
|
+
widget = MolSuiteMonitorWidget()
|
|
181
|
+
widget.show()
|
|
182
|
+
app.processEvents()
|
|
183
|
+
try:
|
|
184
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[
|
|
185
|
+
_job(job_id="waiting", status="waiting"), _job(job_id="running"),
|
|
186
|
+
]))
|
|
187
|
+
assert widget._selected_job_id() == "running"
|
|
188
|
+
finally:
|
|
189
|
+
widget.close()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_reopening_monitor_selects_running_work_from_the_first_fresh_snapshot():
|
|
193
|
+
app = QApplication.instance() or QApplication([])
|
|
194
|
+
widget = MolSuiteMonitorWidget()
|
|
195
|
+
done = _job(job_id="completed", status="completed")
|
|
196
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[done]))
|
|
197
|
+
widget.show()
|
|
198
|
+
app.processEvents()
|
|
199
|
+
try:
|
|
200
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[_job(), done]))
|
|
201
|
+
assert widget._selected_job_id() == "abc123"
|
|
202
|
+
finally:
|
|
203
|
+
widget.close()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_manual_choice_after_opening_cancels_the_pending_fresh_snapshot_focus():
|
|
207
|
+
app = QApplication.instance() or QApplication([])
|
|
208
|
+
widget = MolSuiteMonitorWidget()
|
|
209
|
+
done = _job(job_id="completed", status="completed")
|
|
210
|
+
failed = _job(job_id="failed", status="failed")
|
|
211
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[done, failed]))
|
|
212
|
+
widget.show()
|
|
213
|
+
app.processEvents()
|
|
214
|
+
try:
|
|
215
|
+
widget._jobs_view.select_job("failed")
|
|
216
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[_job(), done, failed]))
|
|
217
|
+
assert widget._selected_job_id() == "failed"
|
|
218
|
+
finally:
|
|
219
|
+
widget.close()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_monitor_keeps_finished_selection_without_active_work_and_falls_back_to_waiting():
|
|
223
|
+
QApplication.instance() or QApplication([])
|
|
224
|
+
widget = MolSuiteMonitorWidget()
|
|
225
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[
|
|
226
|
+
_job(job_id="completed", status="completed"), _job(job_id="failed", status="failed"),
|
|
227
|
+
]))
|
|
228
|
+
widget._jobs_view.select_job("failed")
|
|
229
|
+
widget.select_active_job()
|
|
230
|
+
assert widget._selected_job_id() == "failed"
|
|
231
|
+
widget.update_snapshot(ProjectMonitorState(project_id="p", has_project=True, jobs=[
|
|
232
|
+
_job(job_id="waiting", status="waiting"), _job(job_id="failed", status="failed"),
|
|
233
|
+
]))
|
|
234
|
+
widget.select_active_job()
|
|
235
|
+
assert widget._selected_job_id() == "waiting"
|
|
236
|
+
|
|
237
|
+
|
|
122
238
|
def test_a_mac_is_read_without_proc(tmp_path):
|
|
123
239
|
"""Run the Mac half of the script with stand-ins for its `sysctl`, `ps` and `vm_stat`."""
|
|
124
240
|
from ms_components.ms_monitor.resources import _MAC, parse
|