hbat 2.2.14__tar.gz → 2.2.14.dev32__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.
- {hbat-2.2.14 → hbat-2.2.14.dev32}/Makefile +7 -1
- {hbat-2.2.14 → hbat-2.2.14.dev32}/PKG-INFO +1 -1
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/_version.py +2 -2
- hbat-2.2.14.dev32/hbat/gui/geometry_cutoffs_dialog.py +475 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/graphviz_preferences_dialog.py +0 -12
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/graphviz_renderer.py +31 -10
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/main_window.py +149 -108
- hbat-2.2.14.dev32/hbat/gui/pdb_fixing_dialog.py +300 -0
- hbat-2.2.14.dev32/hbat/gui/preset_manager_dialog.py +573 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.egg-info/SOURCES.txt +3 -1
- hbat-2.2.14.dev32/tests/e2e/test_gui_workflows.py +495 -0
- hbat-2.2.14.dev32/tests/gui/test_gui_components.py +406 -0
- hbat-2.2.14.dev32/tests/unit/test_gui_components.py +308 -0
- hbat-2.2.14/hbat/gui/parameter_panel.py +0 -859
- hbat-2.2.14/tests/e2e/test_gui_workflows.py +0 -548
- hbat-2.2.14/tests/gui/test_gui_components.py +0 -455
- hbat-2.2.14/tests/unit/test_gui_components.py +0 -406
- {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/cleanup-prereleases.yml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/joss-paper.yml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/release.yml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/test.yml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/CITATION.cff +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/CODE_OF_CONDUCT.md +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/CONTRIBUTING.md +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/LICENSE +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/MANIFEST.in +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/README.md +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone_linux.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone_windows.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/conda/meta.yaml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/Makefile +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/requirements.txt +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/_static/custom.css +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/_static/light-theme.css +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/ccd_analyzer.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/constants_generator.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/generate_ccd_constants.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/cli/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/app.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/atomic_data.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/misc.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/parameters.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/pdb_constants.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/interactions.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/np_analyzer.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/np_vector.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/pdb_fixer.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/pdb_parser.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/structure.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/examples/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/gui/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/atom_utils.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/graphviz_utils.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/cli.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/conf.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/development.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/index.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/installation.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/license.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/logic.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/parameters.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/pdbfixing.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/quickstart.rst +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1bhl.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1gai.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1ubi.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/2izf.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4jsv.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4laz.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4ub7.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4x21.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/6rsa.pdb +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/drug_design_strict.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/high_resolution.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/low_resolution.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/membrane_proteins.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/nmr_structures.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/standard_resolution.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/strong_interactions_only.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/weak_interactions_permissive.hbat +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/ccd_analyzer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/constants_generator.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/generate_ccd_constants.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/cli/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/cli/main.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/app.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/atomic_data.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/misc.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/parameters.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/pdb_constants.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/residue_bonds.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/analysis.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/analyzer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/app_config.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/atom_classifier.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/interactions.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/np_analyzer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/np_vector.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/pdb_fixer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/pdb_parser.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/structure.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/chain_visualization.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/export_manager.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/matplotlib_renderer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/results_panel.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/visualization_renderer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/atom_utils.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/graphviz_utils.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.icns +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.ico +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.png +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.svg +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat_cli.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat_gui.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/paper/paper.bib +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/paper/paper.md +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/pyproject.toml +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/pytest.ini +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/requirements-dev.txt +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/requirements.txt +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/setup.cfg +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/README.md +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/test_cli_main.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/test_cli_output_formats.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/conftest.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_cli_workflows.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_complete_workflows.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_graphviz_workflows.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/gui/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_analyzer_components.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_cli_integration.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_graphviz_renderer.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_molecular_validation.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_pdb_parsing.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/test_ccd_performance.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/test_performance_workflows.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/run_tests.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/__init__.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_cli_parsing.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_graphviz_utils.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_interactions.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_parameters.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_scrollable_canvas.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_structures.py +0 -0
- {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_vector_math.py +0 -0
|
@@ -76,7 +76,13 @@ test-coverage:
|
|
|
76
76
|
|
|
77
77
|
test-gui:
|
|
78
78
|
@echo "Running GUI tests..."
|
|
79
|
-
|
|
79
|
+
@if command -v xvfb-run >/dev/null 2>&1; then \
|
|
80
|
+
echo "Using virtual display (xvfb-run)..."; \
|
|
81
|
+
xvfb-run -a -s "-screen 0 1024x768x24" pytest tests/ -v -m "gui"; \
|
|
82
|
+
else \
|
|
83
|
+
echo "xvfb-run not available, running tests with current display..."; \
|
|
84
|
+
pytest tests/ -v -m "gui"; \
|
|
85
|
+
fi
|
|
80
86
|
|
|
81
87
|
test-unit:
|
|
82
88
|
@echo "Running unit tests..."
|
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '2.2.14'
|
|
21
|
-
__version_tuple__ = version_tuple = (2, 2, 14)
|
|
20
|
+
__version__ = version = '2.2.14.dev32'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 2, 14, 'dev32')
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geometry cutoffs configuration dialog for HBAT GUI.
|
|
3
|
+
|
|
4
|
+
This module provides a dialog for configuring molecular interaction
|
|
5
|
+
analysis parameters (distances, angles, etc.) without PDB fixing options.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import tkinter as tk
|
|
9
|
+
from tkinter import messagebox, ttk
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from ..constants.parameters import (
|
|
13
|
+
AnalysisModes,
|
|
14
|
+
AnalysisParameters,
|
|
15
|
+
ParameterRanges,
|
|
16
|
+
ParametersDefault,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GeometryCutoffsDialog:
|
|
21
|
+
"""Dialog for configuring geometry cutoffs parameters."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, parent: tk.Tk, current_params: Optional[AnalysisParameters] = None):
|
|
24
|
+
"""Initialize geometry cutoffs dialog.
|
|
25
|
+
|
|
26
|
+
:param parent: Parent window
|
|
27
|
+
:type parent: tk.Tk
|
|
28
|
+
:param current_params: Current analysis parameters
|
|
29
|
+
:type current_params: Optional[AnalysisParameters]
|
|
30
|
+
"""
|
|
31
|
+
self.parent = parent
|
|
32
|
+
self.current_params = current_params or AnalysisParameters()
|
|
33
|
+
self.result = None
|
|
34
|
+
|
|
35
|
+
# Create dialog window
|
|
36
|
+
self.dialog = tk.Toplevel(parent)
|
|
37
|
+
self.dialog.title("Geometry Cutoffs")
|
|
38
|
+
self.dialog.geometry("800x600")
|
|
39
|
+
self.dialog.resizable(True, True)
|
|
40
|
+
|
|
41
|
+
# Make dialog modal
|
|
42
|
+
self.dialog.transient(parent)
|
|
43
|
+
self.dialog.grab_set()
|
|
44
|
+
|
|
45
|
+
# Initialize variables
|
|
46
|
+
self._init_variables()
|
|
47
|
+
|
|
48
|
+
# Create widgets
|
|
49
|
+
self._create_widgets()
|
|
50
|
+
|
|
51
|
+
# Center the dialog
|
|
52
|
+
self.dialog.update_idletasks()
|
|
53
|
+
x = (self.dialog.winfo_screenwidth() // 2) - (self.dialog.winfo_width() // 2)
|
|
54
|
+
y = (self.dialog.winfo_screenheight() // 2) - (self.dialog.winfo_height() // 2)
|
|
55
|
+
self.dialog.geometry(f"+{x}+{y}")
|
|
56
|
+
|
|
57
|
+
# Handle window closing
|
|
58
|
+
self.dialog.protocol("WM_DELETE_WINDOW", self._cancel)
|
|
59
|
+
|
|
60
|
+
# Set initial values
|
|
61
|
+
self.set_parameters(self.current_params)
|
|
62
|
+
|
|
63
|
+
def _init_variables(self):
|
|
64
|
+
"""Initialize tkinter variables with default values."""
|
|
65
|
+
self.analysis_mode = tk.StringVar(value=ParametersDefault.ANALYSIS_MODE)
|
|
66
|
+
self.covalent_factor = tk.DoubleVar(value=ParametersDefault.COVALENT_CUTOFF_FACTOR)
|
|
67
|
+
self.hb_distance = tk.DoubleVar(value=ParametersDefault.HB_DISTANCE_CUTOFF)
|
|
68
|
+
self.hb_angle = tk.DoubleVar(value=ParametersDefault.HB_ANGLE_CUTOFF)
|
|
69
|
+
self.da_distance = tk.DoubleVar(value=ParametersDefault.HB_DA_DISTANCE)
|
|
70
|
+
self.xb_distance = tk.DoubleVar(value=ParametersDefault.XB_DISTANCE_CUTOFF)
|
|
71
|
+
self.xb_angle = tk.DoubleVar(value=ParametersDefault.XB_ANGLE_CUTOFF)
|
|
72
|
+
self.pi_distance = tk.DoubleVar(value=ParametersDefault.PI_DISTANCE_CUTOFF)
|
|
73
|
+
self.pi_angle = tk.DoubleVar(value=ParametersDefault.PI_ANGLE_CUTOFF)
|
|
74
|
+
|
|
75
|
+
def _create_widgets(self) -> None:
|
|
76
|
+
"""Create and layout all parameter widgets.
|
|
77
|
+
|
|
78
|
+
:returns: None
|
|
79
|
+
:rtype: None
|
|
80
|
+
"""
|
|
81
|
+
# Main frame with padding
|
|
82
|
+
main_frame = ttk.Frame(self.dialog, padding="20")
|
|
83
|
+
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
84
|
+
|
|
85
|
+
# Create container for scrollable content
|
|
86
|
+
content_frame = ttk.Frame(main_frame)
|
|
87
|
+
content_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
|
|
88
|
+
|
|
89
|
+
# Create scrollable area
|
|
90
|
+
canvas = tk.Canvas(content_frame)
|
|
91
|
+
scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview)
|
|
92
|
+
scrollable_frame = ttk.Frame(canvas)
|
|
93
|
+
|
|
94
|
+
scrollable_frame.bind(
|
|
95
|
+
"<Configure>",
|
|
96
|
+
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
|
100
|
+
canvas.configure(yscrollcommand=scrollbar.set)
|
|
101
|
+
|
|
102
|
+
# Grid layout for canvas and scrollbar
|
|
103
|
+
canvas.grid(row=0, column=0, sticky="nsew")
|
|
104
|
+
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
105
|
+
|
|
106
|
+
content_frame.grid_rowconfigure(0, weight=1)
|
|
107
|
+
content_frame.grid_columnconfigure(0, weight=1)
|
|
108
|
+
|
|
109
|
+
# Create parameter groups in order
|
|
110
|
+
self._create_general_parameters(scrollable_frame)
|
|
111
|
+
self._create_hydrogen_bond_parameters(scrollable_frame)
|
|
112
|
+
self._create_halogen_bond_parameters(scrollable_frame)
|
|
113
|
+
self._create_pi_interaction_parameters(scrollable_frame)
|
|
114
|
+
|
|
115
|
+
# Buttons at bottom - separate from scrollable content
|
|
116
|
+
button_frame = ttk.Frame(main_frame)
|
|
117
|
+
button_frame.pack(fill=tk.X, side=tk.BOTTOM)
|
|
118
|
+
|
|
119
|
+
ttk.Button(
|
|
120
|
+
button_frame,
|
|
121
|
+
text="OK",
|
|
122
|
+
command=self._ok
|
|
123
|
+
).pack(side=tk.RIGHT, padx=(5, 0))
|
|
124
|
+
|
|
125
|
+
ttk.Button(
|
|
126
|
+
button_frame,
|
|
127
|
+
text="Cancel",
|
|
128
|
+
command=self._cancel
|
|
129
|
+
).pack(side=tk.RIGHT)
|
|
130
|
+
|
|
131
|
+
ttk.Button(
|
|
132
|
+
button_frame, text="Reset to Defaults", command=self._set_defaults
|
|
133
|
+
).pack(side=tk.LEFT, padx=(0, 5))
|
|
134
|
+
|
|
135
|
+
ttk.Button(button_frame, text="Manage Presets...", command=self._open_preset_manager).pack(
|
|
136
|
+
side=tk.LEFT, padx=5
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _create_general_parameters(self, parent):
|
|
140
|
+
"""Create general analysis parameters."""
|
|
141
|
+
group = ttk.LabelFrame(parent, text="General Parameters", padding=10)
|
|
142
|
+
group.pack(fill=tk.X, padx=10, pady=5)
|
|
143
|
+
|
|
144
|
+
# Analysis mode
|
|
145
|
+
ttk.Label(group, text="Analysis Mode:").grid(
|
|
146
|
+
row=0, column=0, sticky=tk.W, pady=2
|
|
147
|
+
)
|
|
148
|
+
self.analysis_mode = tk.StringVar(value=ParametersDefault.ANALYSIS_MODE)
|
|
149
|
+
mode_frame = ttk.Frame(group)
|
|
150
|
+
mode_frame.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2)
|
|
151
|
+
|
|
152
|
+
ttk.Radiobutton(
|
|
153
|
+
mode_frame,
|
|
154
|
+
text="Complete PDB Analysis",
|
|
155
|
+
variable=self.analysis_mode,
|
|
156
|
+
value="complete",
|
|
157
|
+
).pack(anchor=tk.W)
|
|
158
|
+
ttk.Radiobutton(
|
|
159
|
+
mode_frame,
|
|
160
|
+
text="Local Interactions Only",
|
|
161
|
+
variable=self.analysis_mode,
|
|
162
|
+
value="local",
|
|
163
|
+
).pack(anchor=tk.W)
|
|
164
|
+
|
|
165
|
+
# Covalent bond cutoff factor
|
|
166
|
+
ttk.Label(group, text="Covalent Bond Factor:").grid(
|
|
167
|
+
row=1, column=0, sticky=tk.W, pady=2
|
|
168
|
+
)
|
|
169
|
+
self.covalent_factor = tk.DoubleVar(
|
|
170
|
+
value=ParametersDefault.COVALENT_CUTOFF_FACTOR
|
|
171
|
+
)
|
|
172
|
+
ttk.Scale(
|
|
173
|
+
group,
|
|
174
|
+
from_=ParameterRanges.MIN_COVALENT_FACTOR,
|
|
175
|
+
to=ParameterRanges.MAX_COVALENT_FACTOR,
|
|
176
|
+
variable=self.covalent_factor,
|
|
177
|
+
orient=tk.HORIZONTAL,
|
|
178
|
+
length=200,
|
|
179
|
+
).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2)
|
|
180
|
+
|
|
181
|
+
# Value display
|
|
182
|
+
factor_label = ttk.Label(group, text="")
|
|
183
|
+
factor_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
|
|
184
|
+
|
|
185
|
+
def update_factor_label(*args):
|
|
186
|
+
factor_label.config(text=f"{self.covalent_factor.get():.2f}")
|
|
187
|
+
|
|
188
|
+
self.covalent_factor.trace("w", update_factor_label)
|
|
189
|
+
update_factor_label()
|
|
190
|
+
|
|
191
|
+
def _create_hydrogen_bond_parameters(self, parent):
|
|
192
|
+
"""Create hydrogen bond parameter controls."""
|
|
193
|
+
group = ttk.LabelFrame(parent, text="Hydrogen Bond Parameters", padding=10)
|
|
194
|
+
group.pack(fill=tk.X, padx=10, pady=5)
|
|
195
|
+
|
|
196
|
+
# H...A distance
|
|
197
|
+
ttk.Label(group, text="H...A Distance (Å):").grid(
|
|
198
|
+
row=0, column=0, sticky=tk.W, pady=2
|
|
199
|
+
)
|
|
200
|
+
self.hb_distance = tk.DoubleVar(value=ParametersDefault.HB_DISTANCE_CUTOFF)
|
|
201
|
+
ttk.Scale(
|
|
202
|
+
group,
|
|
203
|
+
from_=ParameterRanges.MIN_DISTANCE,
|
|
204
|
+
to=ParameterRanges.MAX_DISTANCE,
|
|
205
|
+
variable=self.hb_distance,
|
|
206
|
+
orient=tk.HORIZONTAL,
|
|
207
|
+
length=200,
|
|
208
|
+
).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2)
|
|
209
|
+
|
|
210
|
+
hb_dist_label = ttk.Label(group, text="")
|
|
211
|
+
hb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2)
|
|
212
|
+
|
|
213
|
+
def update_hb_dist(*args):
|
|
214
|
+
hb_dist_label.config(text=f"{self.hb_distance.get():.1f}")
|
|
215
|
+
|
|
216
|
+
self.hb_distance.trace("w", update_hb_dist)
|
|
217
|
+
update_hb_dist()
|
|
218
|
+
|
|
219
|
+
# D-H...A angle
|
|
220
|
+
ttk.Label(group, text="D-H...A Angle (°):").grid(
|
|
221
|
+
row=1, column=0, sticky=tk.W, pady=2
|
|
222
|
+
)
|
|
223
|
+
self.hb_angle = tk.DoubleVar(value=ParametersDefault.HB_ANGLE_CUTOFF)
|
|
224
|
+
ttk.Scale(
|
|
225
|
+
group,
|
|
226
|
+
from_=ParameterRanges.MIN_ANGLE,
|
|
227
|
+
to=ParameterRanges.MAX_ANGLE,
|
|
228
|
+
variable=self.hb_angle,
|
|
229
|
+
orient=tk.HORIZONTAL,
|
|
230
|
+
length=200,
|
|
231
|
+
).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2)
|
|
232
|
+
|
|
233
|
+
hb_angle_label = ttk.Label(group, text="")
|
|
234
|
+
hb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
|
|
235
|
+
|
|
236
|
+
def update_hb_angle(*args):
|
|
237
|
+
hb_angle_label.config(text=f"{self.hb_angle.get():.0f}")
|
|
238
|
+
|
|
239
|
+
self.hb_angle.trace("w", update_hb_angle)
|
|
240
|
+
update_hb_angle()
|
|
241
|
+
|
|
242
|
+
# D...A distance
|
|
243
|
+
ttk.Label(group, text="D...A Distance (Å):").grid(
|
|
244
|
+
row=2, column=0, sticky=tk.W, pady=2
|
|
245
|
+
)
|
|
246
|
+
self.da_distance = tk.DoubleVar(value=ParametersDefault.HB_DA_DISTANCE)
|
|
247
|
+
ttk.Scale(
|
|
248
|
+
group,
|
|
249
|
+
from_=ParameterRanges.MIN_DISTANCE,
|
|
250
|
+
to=ParameterRanges.MAX_DISTANCE,
|
|
251
|
+
variable=self.da_distance,
|
|
252
|
+
orient=tk.HORIZONTAL,
|
|
253
|
+
length=200,
|
|
254
|
+
).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2)
|
|
255
|
+
|
|
256
|
+
da_dist_label = ttk.Label(group, text="")
|
|
257
|
+
da_dist_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2)
|
|
258
|
+
|
|
259
|
+
def update_da_dist(*args):
|
|
260
|
+
da_dist_label.config(text=f"{self.da_distance.get():.1f}")
|
|
261
|
+
|
|
262
|
+
self.da_distance.trace("w", update_da_dist)
|
|
263
|
+
update_da_dist()
|
|
264
|
+
|
|
265
|
+
def _create_halogen_bond_parameters(self, parent):
|
|
266
|
+
"""Create halogen bond parameter controls."""
|
|
267
|
+
group = ttk.LabelFrame(parent, text="Halogen Bond Parameters", padding=10)
|
|
268
|
+
group.pack(fill=tk.X, padx=10, pady=5)
|
|
269
|
+
|
|
270
|
+
# X...A distance
|
|
271
|
+
ttk.Label(group, text="X...A Distance (Å):").grid(
|
|
272
|
+
row=0, column=0, sticky=tk.W, pady=2
|
|
273
|
+
)
|
|
274
|
+
self.xb_distance = tk.DoubleVar(value=ParametersDefault.XB_DISTANCE_CUTOFF)
|
|
275
|
+
ttk.Scale(
|
|
276
|
+
group,
|
|
277
|
+
from_=ParameterRanges.MIN_DISTANCE,
|
|
278
|
+
to=ParameterRanges.MAX_DISTANCE,
|
|
279
|
+
variable=self.xb_distance,
|
|
280
|
+
orient=tk.HORIZONTAL,
|
|
281
|
+
length=200,
|
|
282
|
+
).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2)
|
|
283
|
+
|
|
284
|
+
xb_dist_label = ttk.Label(group, text="")
|
|
285
|
+
xb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2)
|
|
286
|
+
|
|
287
|
+
def update_xb_dist(*args):
|
|
288
|
+
xb_dist_label.config(text=f"{self.xb_distance.get():.1f}")
|
|
289
|
+
|
|
290
|
+
self.xb_distance.trace("w", update_xb_dist)
|
|
291
|
+
update_xb_dist()
|
|
292
|
+
|
|
293
|
+
# C-X...A angle
|
|
294
|
+
ttk.Label(group, text="C-X...A Angle (°):").grid(
|
|
295
|
+
row=1, column=0, sticky=tk.W, pady=2
|
|
296
|
+
)
|
|
297
|
+
self.xb_angle = tk.DoubleVar(value=ParametersDefault.XB_ANGLE_CUTOFF)
|
|
298
|
+
ttk.Scale(
|
|
299
|
+
group,
|
|
300
|
+
from_=ParameterRanges.MIN_ANGLE,
|
|
301
|
+
to=ParameterRanges.MAX_ANGLE,
|
|
302
|
+
variable=self.xb_angle,
|
|
303
|
+
orient=tk.HORIZONTAL,
|
|
304
|
+
length=200,
|
|
305
|
+
).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2)
|
|
306
|
+
|
|
307
|
+
xb_angle_label = ttk.Label(group, text="")
|
|
308
|
+
xb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
|
|
309
|
+
|
|
310
|
+
def update_xb_angle(*args):
|
|
311
|
+
xb_angle_label.config(text=f"{self.xb_angle.get():.0f}")
|
|
312
|
+
|
|
313
|
+
self.xb_angle.trace("w", update_xb_angle)
|
|
314
|
+
update_xb_angle()
|
|
315
|
+
|
|
316
|
+
def _create_pi_interaction_parameters(self, parent):
|
|
317
|
+
"""Create π interaction parameter controls."""
|
|
318
|
+
group = ttk.LabelFrame(parent, text="π Interaction Parameters", padding=10)
|
|
319
|
+
group.pack(fill=tk.X, padx=10, pady=5)
|
|
320
|
+
|
|
321
|
+
# H...π distance
|
|
322
|
+
ttk.Label(group, text="H...π Distance (Å):").grid(
|
|
323
|
+
row=0, column=0, sticky=tk.W, pady=2
|
|
324
|
+
)
|
|
325
|
+
self.pi_distance = tk.DoubleVar(value=ParametersDefault.PI_DISTANCE_CUTOFF)
|
|
326
|
+
ttk.Scale(
|
|
327
|
+
group,
|
|
328
|
+
from_=ParameterRanges.MIN_DISTANCE,
|
|
329
|
+
to=ParameterRanges.MAX_DISTANCE,
|
|
330
|
+
variable=self.pi_distance,
|
|
331
|
+
orient=tk.HORIZONTAL,
|
|
332
|
+
length=200,
|
|
333
|
+
).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2)
|
|
334
|
+
|
|
335
|
+
pi_dist_label = ttk.Label(group, text="")
|
|
336
|
+
pi_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2)
|
|
337
|
+
|
|
338
|
+
def update_pi_dist(*args):
|
|
339
|
+
pi_dist_label.config(text=f"{self.pi_distance.get():.1f}")
|
|
340
|
+
|
|
341
|
+
self.pi_distance.trace("w", update_pi_dist)
|
|
342
|
+
update_pi_dist()
|
|
343
|
+
|
|
344
|
+
# D-H...π angle
|
|
345
|
+
ttk.Label(group, text="D-H...π Angle (°):").grid(
|
|
346
|
+
row=1, column=0, sticky=tk.W, pady=2
|
|
347
|
+
)
|
|
348
|
+
self.pi_angle = tk.DoubleVar(value=ParametersDefault.PI_ANGLE_CUTOFF)
|
|
349
|
+
ttk.Scale(
|
|
350
|
+
group,
|
|
351
|
+
from_=ParameterRanges.MIN_ANGLE,
|
|
352
|
+
to=ParameterRanges.MAX_ANGLE,
|
|
353
|
+
variable=self.pi_angle,
|
|
354
|
+
orient=tk.HORIZONTAL,
|
|
355
|
+
length=200,
|
|
356
|
+
).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2)
|
|
357
|
+
|
|
358
|
+
pi_angle_label = ttk.Label(group, text="")
|
|
359
|
+
pi_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
|
|
360
|
+
|
|
361
|
+
def update_pi_angle(*args):
|
|
362
|
+
pi_angle_label.config(text=f"{self.pi_angle.get():.0f}")
|
|
363
|
+
|
|
364
|
+
self.pi_angle.trace("w", update_pi_angle)
|
|
365
|
+
update_pi_angle()
|
|
366
|
+
|
|
367
|
+
def get_parameters(self) -> AnalysisParameters:
|
|
368
|
+
"""Get current parameter values.
|
|
369
|
+
|
|
370
|
+
:returns: Current analysis parameters
|
|
371
|
+
:rtype: AnalysisParameters
|
|
372
|
+
"""
|
|
373
|
+
return AnalysisParameters(
|
|
374
|
+
hb_distance_cutoff=self.hb_distance.get(),
|
|
375
|
+
hb_angle_cutoff=self.hb_angle.get(),
|
|
376
|
+
hb_donor_acceptor_cutoff=self.da_distance.get(),
|
|
377
|
+
xb_distance_cutoff=self.xb_distance.get(),
|
|
378
|
+
xb_angle_cutoff=self.xb_angle.get(),
|
|
379
|
+
pi_distance_cutoff=self.pi_distance.get(),
|
|
380
|
+
pi_angle_cutoff=self.pi_angle.get(),
|
|
381
|
+
covalent_cutoff_factor=self.covalent_factor.get(),
|
|
382
|
+
analysis_mode=self.analysis_mode.get(),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def set_parameters(self, params: AnalysisParameters) -> None:
|
|
386
|
+
"""Set parameter values from AnalysisParameters object.
|
|
387
|
+
|
|
388
|
+
:param params: Analysis parameters to set
|
|
389
|
+
:type params: AnalysisParameters
|
|
390
|
+
"""
|
|
391
|
+
self.hb_distance.set(params.hb_distance_cutoff)
|
|
392
|
+
self.hb_angle.set(params.hb_angle_cutoff)
|
|
393
|
+
self.da_distance.set(params.hb_donor_acceptor_cutoff)
|
|
394
|
+
self.xb_distance.set(params.xb_distance_cutoff)
|
|
395
|
+
self.xb_angle.set(params.xb_angle_cutoff)
|
|
396
|
+
self.pi_distance.set(params.pi_distance_cutoff)
|
|
397
|
+
self.pi_angle.set(params.pi_angle_cutoff)
|
|
398
|
+
self.covalent_factor.set(params.covalent_cutoff_factor)
|
|
399
|
+
self.analysis_mode.set(params.analysis_mode)
|
|
400
|
+
|
|
401
|
+
def _set_defaults(self):
|
|
402
|
+
"""Reset all parameters to default values."""
|
|
403
|
+
default_params = AnalysisParameters()
|
|
404
|
+
self.set_parameters(default_params)
|
|
405
|
+
|
|
406
|
+
def reset_to_defaults(self) -> None:
|
|
407
|
+
"""Public method to reset parameters to defaults."""
|
|
408
|
+
self._set_defaults()
|
|
409
|
+
|
|
410
|
+
def _open_preset_manager(self):
|
|
411
|
+
"""Open the preset manager dialog."""
|
|
412
|
+
from .preset_manager_dialog import PresetManagerDialog
|
|
413
|
+
|
|
414
|
+
# Get current parameters
|
|
415
|
+
current_params = self.get_parameters()
|
|
416
|
+
|
|
417
|
+
# Open preset manager
|
|
418
|
+
dialog = PresetManagerDialog(self.dialog, current_params)
|
|
419
|
+
result = dialog.get_result()
|
|
420
|
+
|
|
421
|
+
if result:
|
|
422
|
+
# Apply the loaded preset
|
|
423
|
+
self._apply_preset_data(result)
|
|
424
|
+
messagebox.showinfo("Success", "Preset loaded successfully")
|
|
425
|
+
|
|
426
|
+
def _apply_preset_data(self, data: Dict[str, Any]) -> None:
|
|
427
|
+
"""Apply preset data to parameters."""
|
|
428
|
+
if "parameters" not in data:
|
|
429
|
+
raise ValueError("Invalid preset format: missing 'parameters' section")
|
|
430
|
+
|
|
431
|
+
params = data["parameters"]
|
|
432
|
+
|
|
433
|
+
# Apply hydrogen bond parameters
|
|
434
|
+
if "hydrogen_bonds" in params:
|
|
435
|
+
hb = params["hydrogen_bonds"]
|
|
436
|
+
self.hb_distance.set(hb.get("h_a_distance_cutoff", ParametersDefault.HB_DISTANCE_CUTOFF))
|
|
437
|
+
self.hb_angle.set(hb.get("dha_angle_cutoff", ParametersDefault.HB_ANGLE_CUTOFF))
|
|
438
|
+
self.da_distance.set(hb.get("d_a_distance_cutoff", ParametersDefault.HB_DA_DISTANCE))
|
|
439
|
+
|
|
440
|
+
# Apply halogen bond parameters
|
|
441
|
+
if "halogen_bonds" in params:
|
|
442
|
+
xb = params["halogen_bonds"]
|
|
443
|
+
self.xb_distance.set(xb.get("x_a_distance_cutoff", ParametersDefault.XB_DISTANCE_CUTOFF))
|
|
444
|
+
self.xb_angle.set(xb.get("cxa_angle_cutoff", ParametersDefault.XB_ANGLE_CUTOFF))
|
|
445
|
+
|
|
446
|
+
# Apply π interaction parameters
|
|
447
|
+
if "pi_interactions" in params:
|
|
448
|
+
pi = params["pi_interactions"]
|
|
449
|
+
self.pi_distance.set(pi.get("h_pi_distance_cutoff", ParametersDefault.PI_DISTANCE_CUTOFF))
|
|
450
|
+
self.pi_angle.set(pi.get("dh_pi_angle_cutoff", ParametersDefault.PI_ANGLE_CUTOFF))
|
|
451
|
+
|
|
452
|
+
# Apply general parameters
|
|
453
|
+
if "general" in params:
|
|
454
|
+
gen = params["general"]
|
|
455
|
+
self.covalent_factor.set(gen.get("covalent_cutoff_factor", ParametersDefault.COVALENT_CUTOFF_FACTOR))
|
|
456
|
+
self.analysis_mode.set(gen.get("analysis_mode", ParametersDefault.ANALYSIS_MODE))
|
|
457
|
+
|
|
458
|
+
def _ok(self):
|
|
459
|
+
"""Handle OK button - save settings and close."""
|
|
460
|
+
self.result = self.get_parameters()
|
|
461
|
+
self.dialog.destroy()
|
|
462
|
+
|
|
463
|
+
def _cancel(self):
|
|
464
|
+
"""Handle Cancel button - close without saving."""
|
|
465
|
+
self.result = None
|
|
466
|
+
self.dialog.destroy()
|
|
467
|
+
|
|
468
|
+
def get_result(self) -> Optional[AnalysisParameters]:
|
|
469
|
+
"""Get the configured parameters.
|
|
470
|
+
|
|
471
|
+
:returns: Analysis parameters or None if cancelled
|
|
472
|
+
:rtype: Optional[AnalysisParameters]
|
|
473
|
+
"""
|
|
474
|
+
self.dialog.wait_window()
|
|
475
|
+
return self.result
|
|
@@ -16,18 +16,6 @@ from hbat.utilities.graphviz_utils import GraphVizDetector
|
|
|
16
16
|
# Set up logging
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
|
-
# GraphViz engine descriptions
|
|
20
|
-
ENGINE_DESCRIPTIONS = {
|
|
21
|
-
"dot": "Hierarchical layouts for directed graphs",
|
|
22
|
-
"neato": "Spring model layouts (Kamada-Kawai)",
|
|
23
|
-
"fdp": "Force-directed spring model",
|
|
24
|
-
"sfdp": "Scalable force-directed placement",
|
|
25
|
-
"circo": "Circular layout",
|
|
26
|
-
"twopi": "Radial layout",
|
|
27
|
-
"osage": "Array-based layout",
|
|
28
|
-
"patchwork": "Squarified treemap layout",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
19
|
# Background color options
|
|
32
20
|
BACKGROUND_COLORS = [
|
|
33
21
|
("White", "white"),
|
|
@@ -537,6 +537,9 @@ class GraphVizRenderer(BaseVisualizationRenderer):
|
|
|
537
537
|
self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
|
|
538
538
|
self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
539
539
|
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
540
|
+
|
|
541
|
+
# Pack the canvas frame into the parent widget
|
|
542
|
+
self.canvas_frame.pack(fill=tk.BOTH, expand=True)
|
|
540
543
|
|
|
541
544
|
def _display_image(self, image: Image.Image) -> None:
|
|
542
545
|
"""Display PIL image in scrollable canvas.
|
|
@@ -548,29 +551,47 @@ class GraphVizRenderer(BaseVisualizationRenderer):
|
|
|
548
551
|
return
|
|
549
552
|
|
|
550
553
|
try:
|
|
551
|
-
#
|
|
552
|
-
self.current_image = ImageTk.PhotoImage(image)
|
|
553
|
-
|
|
554
|
-
# Clear canvas and display image
|
|
554
|
+
# Clear canvas first to remove any old images
|
|
555
555
|
self.canvas.delete("all")
|
|
556
|
-
|
|
556
|
+
|
|
557
557
|
# Get image dimensions
|
|
558
558
|
img_width = image.width
|
|
559
559
|
img_height = image.height
|
|
560
560
|
|
|
561
|
-
#
|
|
562
|
-
self.canvas.create_image(0, 0, image=self.current_image, anchor=tk.NW)
|
|
563
|
-
|
|
564
|
-
# Configure scroll region to match image size
|
|
561
|
+
# Configure scroll region to match image size first
|
|
565
562
|
self.canvas.configure(scrollregion=(0, 0, img_width, img_height))
|
|
563
|
+
|
|
564
|
+
# Convert to PhotoImage with explicit master to prevent garbage collection
|
|
565
|
+
# Pass the canvas master (root window) to ensure proper ownership
|
|
566
|
+
root_widget = self.canvas.winfo_toplevel()
|
|
567
|
+
self.current_image = ImageTk.PhotoImage(image, master=root_widget)
|
|
568
|
+
|
|
569
|
+
# Ensure canvas_frame is visible
|
|
570
|
+
if hasattr(self, 'canvas_frame') and self.canvas_frame:
|
|
571
|
+
self.canvas_frame.update_idletasks()
|
|
572
|
+
|
|
573
|
+
# Create image at top-left corner (0, 0) for proper scrolling
|
|
574
|
+
image_id = self.canvas.create_image(0, 0, image=self.current_image, anchor=tk.NW)
|
|
575
|
+
|
|
576
|
+
# Store multiple references to prevent garbage collection
|
|
577
|
+
self.canvas.image_ref = self.current_image
|
|
578
|
+
# Also store on the renderer itself
|
|
579
|
+
self._image_reference = self.current_image
|
|
566
580
|
|
|
567
581
|
# Bind mouse wheel scrolling
|
|
568
582
|
self._bind_mouse_scroll()
|
|
569
583
|
|
|
570
|
-
|
|
584
|
+
# Update canvas to ensure everything is rendered
|
|
585
|
+
self.canvas.update_idletasks()
|
|
571
586
|
|
|
572
587
|
except Exception as e:
|
|
573
588
|
logger.error(f"Failed to display image: {e}")
|
|
589
|
+
# Set empty scroll region if image display fails
|
|
590
|
+
if self.canvas:
|
|
591
|
+
try:
|
|
592
|
+
self.canvas.configure(scrollregion=(0, 0, 0, 0))
|
|
593
|
+
except:
|
|
594
|
+
pass
|
|
574
595
|
|
|
575
596
|
def _bind_mouse_scroll(self) -> None:
|
|
576
597
|
"""Bind mouse wheel scrolling to canvas."""
|