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.
Files changed (162) hide show
  1. {hbat-2.2.14 → hbat-2.2.14.dev32}/Makefile +7 -1
  2. {hbat-2.2.14 → hbat-2.2.14.dev32}/PKG-INFO +1 -1
  3. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/_version.py +2 -2
  4. hbat-2.2.14.dev32/hbat/gui/geometry_cutoffs_dialog.py +475 -0
  5. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/graphviz_preferences_dialog.py +0 -12
  6. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/graphviz_renderer.py +31 -10
  7. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/main_window.py +149 -108
  8. hbat-2.2.14.dev32/hbat/gui/pdb_fixing_dialog.py +300 -0
  9. hbat-2.2.14.dev32/hbat/gui/preset_manager_dialog.py +573 -0
  10. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.egg-info/SOURCES.txt +3 -1
  11. hbat-2.2.14.dev32/tests/e2e/test_gui_workflows.py +495 -0
  12. hbat-2.2.14.dev32/tests/gui/test_gui_components.py +406 -0
  13. hbat-2.2.14.dev32/tests/unit/test_gui_components.py +308 -0
  14. hbat-2.2.14/hbat/gui/parameter_panel.py +0 -859
  15. hbat-2.2.14/tests/e2e/test_gui_workflows.py +0 -548
  16. hbat-2.2.14/tests/gui/test_gui_components.py +0 -455
  17. hbat-2.2.14/tests/unit/test_gui_components.py +0 -406
  18. {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/cleanup-prereleases.yml +0 -0
  19. {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/joss-paper.yml +0 -0
  20. {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/release.yml +0 -0
  21. {hbat-2.2.14 → hbat-2.2.14.dev32}/.github/workflows/test.yml +0 -0
  22. {hbat-2.2.14 → hbat-2.2.14.dev32}/CITATION.cff +0 -0
  23. {hbat-2.2.14 → hbat-2.2.14.dev32}/CODE_OF_CONDUCT.md +0 -0
  24. {hbat-2.2.14 → hbat-2.2.14.dev32}/CONTRIBUTING.md +0 -0
  25. {hbat-2.2.14 → hbat-2.2.14.dev32}/LICENSE +0 -0
  26. {hbat-2.2.14 → hbat-2.2.14.dev32}/MANIFEST.in +0 -0
  27. {hbat-2.2.14 → hbat-2.2.14.dev32}/README.md +0 -0
  28. {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone.py +0 -0
  29. {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone_linux.py +0 -0
  30. {hbat-2.2.14 → hbat-2.2.14.dev32}/build_standalone_windows.py +0 -0
  31. {hbat-2.2.14 → hbat-2.2.14.dev32}/conda/meta.yaml +0 -0
  32. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/Makefile +0 -0
  33. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/requirements.txt +0 -0
  34. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/_static/custom.css +0 -0
  35. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/_static/light-theme.css +0 -0
  36. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/ccd_analyzer.rst +0 -0
  37. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/constants_generator.rst +0 -0
  38. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/generate_ccd_constants.rst +0 -0
  39. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/ccd/index.rst +0 -0
  40. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/cli/index.rst +0 -0
  41. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/app.rst +0 -0
  42. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/atomic_data.rst +0 -0
  43. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/index.rst +0 -0
  44. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/misc.rst +0 -0
  45. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/parameters.rst +0 -0
  46. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/constants/pdb_constants.rst +0 -0
  47. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/index.rst +0 -0
  48. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/interactions.rst +0 -0
  49. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/np_analyzer.rst +0 -0
  50. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/np_vector.rst +0 -0
  51. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/pdb_fixer.rst +0 -0
  52. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/pdb_parser.rst +0 -0
  53. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/core/structure.rst +0 -0
  54. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/examples/index.rst +0 -0
  55. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/gui/index.rst +0 -0
  56. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/index.rst +0 -0
  57. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/atom_utils.rst +0 -0
  58. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/graphviz_utils.rst +0 -0
  59. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/api/utilities/index.rst +0 -0
  60. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/cli.rst +0 -0
  61. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/conf.py +0 -0
  62. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/development.rst +0 -0
  63. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/index.rst +0 -0
  64. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/installation.rst +0 -0
  65. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/license.rst +0 -0
  66. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/logic.rst +0 -0
  67. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/parameters.rst +0 -0
  68. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/pdbfixing.rst +0 -0
  69. {hbat-2.2.14 → hbat-2.2.14.dev32}/docs/source/quickstart.rst +0 -0
  70. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1bhl.pdb +0 -0
  71. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1gai.pdb +0 -0
  72. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/1ubi.pdb +0 -0
  73. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/2izf.pdb +0 -0
  74. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4jsv.pdb +0 -0
  75. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4laz.pdb +0 -0
  76. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4ub7.pdb +0 -0
  77. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/4x21.pdb +0 -0
  78. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_pdb_files/6rsa.pdb +0 -0
  79. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/drug_design_strict.hbat +0 -0
  80. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/high_resolution.hbat +0 -0
  81. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/low_resolution.hbat +0 -0
  82. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/membrane_proteins.hbat +0 -0
  83. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/nmr_structures.hbat +0 -0
  84. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/standard_resolution.hbat +0 -0
  85. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/strong_interactions_only.hbat +0 -0
  86. {hbat-2.2.14 → hbat-2.2.14.dev32}/example_presets/weak_interactions_permissive.hbat +0 -0
  87. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/__init__.py +0 -0
  88. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/__init__.py +0 -0
  89. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/ccd_analyzer.py +0 -0
  90. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/constants_generator.py +0 -0
  91. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/ccd/generate_ccd_constants.py +0 -0
  92. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/cli/__init__.py +0 -0
  93. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/cli/main.py +0 -0
  94. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/__init__.py +0 -0
  95. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/app.py +0 -0
  96. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/atomic_data.py +0 -0
  97. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/misc.py +0 -0
  98. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/parameters.py +0 -0
  99. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/pdb_constants.py +0 -0
  100. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/constants/residue_bonds.py +0 -0
  101. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/__init__.py +0 -0
  102. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/analysis.py +0 -0
  103. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/analyzer.py +0 -0
  104. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/app_config.py +0 -0
  105. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/atom_classifier.py +0 -0
  106. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/interactions.py +0 -0
  107. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/np_analyzer.py +0 -0
  108. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/np_vector.py +0 -0
  109. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/pdb_fixer.py +0 -0
  110. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/pdb_parser.py +0 -0
  111. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/core/structure.py +0 -0
  112. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/__init__.py +0 -0
  113. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/chain_visualization.py +0 -0
  114. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/export_manager.py +0 -0
  115. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/matplotlib_renderer.py +0 -0
  116. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/results_panel.py +0 -0
  117. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/gui/visualization_renderer.py +0 -0
  118. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/__init__.py +0 -0
  119. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/atom_utils.py +0 -0
  120. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat/utilities/graphviz_utils.py +0 -0
  121. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.icns +0 -0
  122. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.ico +0 -0
  123. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.png +0 -0
  124. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat.svg +0 -0
  125. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat_cli.py +0 -0
  126. {hbat-2.2.14 → hbat-2.2.14.dev32}/hbat_gui.py +0 -0
  127. {hbat-2.2.14 → hbat-2.2.14.dev32}/paper/paper.bib +0 -0
  128. {hbat-2.2.14 → hbat-2.2.14.dev32}/paper/paper.md +0 -0
  129. {hbat-2.2.14 → hbat-2.2.14.dev32}/pyproject.toml +0 -0
  130. {hbat-2.2.14 → hbat-2.2.14.dev32}/pytest.ini +0 -0
  131. {hbat-2.2.14 → hbat-2.2.14.dev32}/requirements-dev.txt +0 -0
  132. {hbat-2.2.14 → hbat-2.2.14.dev32}/requirements.txt +0 -0
  133. {hbat-2.2.14 → hbat-2.2.14.dev32}/setup.cfg +0 -0
  134. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/README.md +0 -0
  135. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/__init__.py +0 -0
  136. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/__init__.py +0 -0
  137. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/test_cli_main.py +0 -0
  138. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/cli/test_cli_output_formats.py +0 -0
  139. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/conftest.py +0 -0
  140. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/__init__.py +0 -0
  141. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_cli_workflows.py +0 -0
  142. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_complete_workflows.py +0 -0
  143. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/e2e/test_graphviz_workflows.py +0 -0
  144. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/gui/__init__.py +0 -0
  145. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/__init__.py +0 -0
  146. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_analyzer_components.py +0 -0
  147. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_cli_integration.py +0 -0
  148. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_graphviz_renderer.py +0 -0
  149. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_molecular_validation.py +0 -0
  150. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/integration/test_pdb_parsing.py +0 -0
  151. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/__init__.py +0 -0
  152. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/test_ccd_performance.py +0 -0
  153. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/performance/test_performance_workflows.py +0 -0
  154. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/run_tests.py +0 -0
  155. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/__init__.py +0 -0
  156. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_cli_parsing.py +0 -0
  157. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_graphviz_utils.py +0 -0
  158. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_interactions.py +0 -0
  159. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_parameters.py +0 -0
  160. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_scrollable_canvas.py +0 -0
  161. {hbat-2.2.14 → hbat-2.2.14.dev32}/tests/unit/test_structures.py +0 -0
  162. {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
- pytest tests/ -v -m "gui"
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..."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hbat
3
- Version: 2.2.14
3
+ Version: 2.2.14.dev32
4
4
  Summary: Hydrogen Bond Analysis Tool for PDB structures
5
5
  Author-email: Abhishek Tiwari <hbat@abhishek-tiwari.com>
6
6
  License-Expression: MIT
@@ -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
- # Convert to PhotoImage
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
- # Create image at top-left corner (0, 0) for proper scrolling
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
- self.canvas.update()
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."""