Source code for hbat.gui.geometry_cutoffs_dialog

"""
Geometry cutoffs configuration dialog for HBAT GUI.

This module provides a dialog for configuring molecular interaction 
analysis parameters (distances, angles, etc.) without PDB fixing options.
"""

import tkinter as tk
from tkinter import messagebox, ttk
from typing import Any, Dict, Optional

from ..constants.parameters import (
    AnalysisModes,
    AnalysisParameters,
    ParameterRanges,
    ParametersDefault,
)


[docs] class GeometryCutoffsDialog: """Dialog for configuring comprehensive molecular interaction parameters. Provides GUI interface for setting parameters for multiple interaction types: - **Hydrogen Bonds:** Classical strong interactions (N/O-H···O/N) - **Weak Hydrogen Bonds:** C-H···O interactions (important for binding) - **Halogen Bonds:** C-X···A interactions (X = Cl, Br, I) with 150° default - **π Interactions:** Multiple subtypes including: - Hydrogen-π: C-H···π, N-H···π, O-H···π, S-H···π - Halogen-π: C-Cl···π, C-Br···π, C-I···π Uses tabbed interface to organize parameters by interaction type. """
[docs] def __init__(self, parent: tk.Tk, current_params: Optional[AnalysisParameters] = None): """Initialize geometry cutoffs dialog. :param parent: Parent window :type parent: tk.Tk :param current_params: Current analysis parameters :type current_params: Optional[AnalysisParameters] """ self.parent = parent self.current_params = current_params or AnalysisParameters() self.result = None # Create dialog window self.dialog = tk.Toplevel(parent) self.dialog.title("Geometry Cutoffs") self.dialog.geometry("1200x600") self.dialog.resizable(True, True) # Make dialog modal self.dialog.transient(parent) self.dialog.grab_set() # Initialize variables self._init_variables() # Create widgets self._create_widgets() # Center the dialog self.dialog.update_idletasks() x = (self.dialog.winfo_screenwidth() // 2) - (self.dialog.winfo_width() // 2) y = (self.dialog.winfo_screenheight() // 2) - (self.dialog.winfo_height() // 2) self.dialog.geometry(f"+{x}+{y}") # Handle window closing self.dialog.protocol("WM_DELETE_WINDOW", self._cancel) # Set initial values self.set_parameters(self.current_params)
def _init_variables(self): """Initialize tkinter variables with default values.""" self.analysis_mode = tk.StringVar(value=ParametersDefault.ANALYSIS_MODE) self.covalent_factor = tk.DoubleVar(value=ParametersDefault.COVALENT_CUTOFF_FACTOR) self.hb_distance = tk.DoubleVar(value=ParametersDefault.HB_DISTANCE_CUTOFF) self.hb_angle = tk.DoubleVar(value=ParametersDefault.HB_ANGLE_CUTOFF) self.da_distance = tk.DoubleVar(value=ParametersDefault.HB_DA_DISTANCE) self.whb_distance = tk.DoubleVar(value=ParametersDefault.WHB_DISTANCE_CUTOFF) self.whb_angle = tk.DoubleVar(value=ParametersDefault.WHB_ANGLE_CUTOFF) self.whb_da_distance = tk.DoubleVar(value=ParametersDefault.WHB_DA_DISTANCE) self.xb_distance = tk.DoubleVar(value=ParametersDefault.XB_DISTANCE_CUTOFF) self.xb_angle = tk.DoubleVar(value=ParametersDefault.XB_ANGLE_CUTOFF) self.pi_distance = tk.DoubleVar(value=ParametersDefault.PI_DISTANCE_CUTOFF) self.pi_angle = tk.DoubleVar(value=ParametersDefault.PI_ANGLE_CUTOFF) # Initialize π interaction subtype variables (needed for set_parameters) self.pi_ccl_distance = None self.pi_ccl_angle = None self.pi_cbr_distance = None self.pi_cbr_angle = None self.pi_ci_distance = None self.pi_ci_angle = None self.pi_ch_distance = None self.pi_ch_angle = None self.pi_nh_distance = None self.pi_nh_angle = None self.pi_oh_distance = None self.pi_oh_angle = None self.pi_sh_distance = None self.pi_sh_angle = None # Store parameter values directly self._param_values = {} def _store_current_values(self): """Store current parameter values before switching categories.""" try: self._param_values.update({ 'hb_distance': self.hb_distance.get() if hasattr(self, 'hb_distance') else ParametersDefault.HB_DISTANCE_CUTOFF, 'hb_angle': self.hb_angle.get() if hasattr(self, 'hb_angle') else ParametersDefault.HB_ANGLE_CUTOFF, 'da_distance': self.da_distance.get() if hasattr(self, 'da_distance') else ParametersDefault.HB_DA_DISTANCE, 'whb_distance': self.whb_distance.get() if hasattr(self, 'whb_distance') else ParametersDefault.WHB_DISTANCE_CUTOFF, 'whb_angle': self.whb_angle.get() if hasattr(self, 'whb_angle') else ParametersDefault.WHB_ANGLE_CUTOFF, 'whb_da_distance': self.whb_da_distance.get() if hasattr(self, 'whb_da_distance') else ParametersDefault.WHB_DA_DISTANCE, 'xb_distance': self.xb_distance.get() if hasattr(self, 'xb_distance') else ParametersDefault.XB_DISTANCE_CUTOFF, 'xb_angle': self.xb_angle.get() if hasattr(self, 'xb_angle') else ParametersDefault.XB_ANGLE_CUTOFF, 'pi_distance': self.pi_distance.get() if hasattr(self, 'pi_distance') else ParametersDefault.PI_DISTANCE_CUTOFF, 'pi_angle': self.pi_angle.get() if hasattr(self, 'pi_angle') else ParametersDefault.PI_ANGLE_CUTOFF, 'covalent_factor': self.covalent_factor.get() if hasattr(self, 'covalent_factor') else ParametersDefault.COVALENT_CUTOFF_FACTOR, 'analysis_mode': self.analysis_mode.get() if hasattr(self, 'analysis_mode') else ParametersDefault.ANALYSIS_MODE, }) # Store π interaction subtype values if they exist pi_vars = ['pi_ccl_distance', 'pi_ccl_angle', 'pi_cbr_distance', 'pi_cbr_angle', 'pi_ci_distance', 'pi_ci_angle', 'pi_ch_distance', 'pi_ch_angle', 'pi_nh_distance', 'pi_nh_angle', 'pi_oh_distance', 'pi_oh_angle', 'pi_sh_distance', 'pi_sh_angle'] for var_name in pi_vars: if hasattr(self, var_name): var = getattr(self, var_name) if var: self._param_values[var_name] = var.get() except tk.TclError: pass # Variables destroyed, ignore def _create_widgets(self) -> None: """Create and layout all parameter widgets with list selection interface. :returns: None :rtype: None """ # Main frame with padding main_frame = ttk.Frame(self.dialog, padding="20") main_frame.pack(fill=tk.BOTH, expand=True) # Create container for content content_frame = ttk.Frame(main_frame) content_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) # Create paned window for list and content paned = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL) paned.pack(fill=tk.BOTH, expand=True) # Left side - Category list list_frame = ttk.Frame(paned, relief=tk.GROOVE, borderwidth=1) paned.add(list_frame, weight=1) # List label ttk.Label(list_frame, text="Parameter Categories", font=('TkDefaultFont', 10, 'bold')).pack(pady=(10, 5)) # Create listbox for categories self.category_listbox = tk.Listbox(list_frame, selectmode=tk.SINGLE, height=10) self.category_listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # Add categories categories = [ "General Parameters", "Hydrogen Bonds", "Weak Hydrogen Bonds", "Halogen Bonds", "π Interactions" ] for cat in categories: self.category_listbox.insert(tk.END, cat) # Bind selection event self.category_listbox.bind('<<ListboxSelect>>', self._on_category_selected) # Right side - Content area self.content_container = ttk.Frame(paned) paned.add(self.content_container, weight=3) # Create scrollable area for content self.content_canvas = tk.Canvas(self.content_container) scrollbar = ttk.Scrollbar(self.content_container, orient="vertical", command=self.content_canvas.yview) self.content_frame = ttk.Frame(self.content_canvas) self.content_frame.bind( "<Configure>", lambda e: self.content_canvas.configure(scrollregion=self.content_canvas.bbox("all")) ) self.content_canvas.create_window((0, 0), window=self.content_frame, anchor="nw") self.content_canvas.configure(yscrollcommand=scrollbar.set) # Enable mouse wheel scrolling def _on_mousewheel(event): self.content_canvas.yview_scroll(int(-1*(event.delta/120)), "units") self.content_canvas.bind("<MouseWheel>", _on_mousewheel) # Windows self.content_canvas.bind("<Button-4>", lambda e: self.content_canvas.yview_scroll(-1, "units")) # Linux self.content_canvas.bind("<Button-5>", lambda e: self.content_canvas.yview_scroll(1, "units")) # Linux # Pack canvas and scrollbar self.content_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Store current content widget self.current_content = None # Select first category by default self.category_listbox.selection_set(0) self._on_category_selected(None) # Buttons at bottom button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, side=tk.BOTTOM) ttk.Button( button_frame, text="OK", command=self._ok ).pack(side=tk.RIGHT, padx=(5, 0)) ttk.Button( button_frame, text="Cancel", command=self._cancel ).pack(side=tk.RIGHT) ttk.Button( button_frame, text="Reset to Defaults", command=self._set_defaults ).pack(side=tk.LEFT, padx=(0, 5)) ttk.Button(button_frame, text="Manage Presets...", command=self._open_preset_manager).pack( side=tk.LEFT, padx=5 ) def _create_general_parameters(self, parent): """Create general analysis parameters.""" group = ttk.LabelFrame(parent, text="General Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # Analysis mode ttk.Label(group, text="Analysis Mode:").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_mode = self._param_values.get('analysis_mode', ParametersDefault.ANALYSIS_MODE) self.analysis_mode = tk.StringVar(value=stored_mode) mode_frame = ttk.Frame(group) mode_frame.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) ttk.Radiobutton( mode_frame, text="Complete PDB Analysis", variable=self.analysis_mode, value="complete", ).pack(anchor=tk.W) ttk.Radiobutton( mode_frame, text="Local Interactions Only", variable=self.analysis_mode, value="local", ).pack(anchor=tk.W) # Covalent bond cutoff factor ttk.Label(group, text="Covalent Bond Factor:").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_factor = self._param_values.get('covalent_factor', ParametersDefault.COVALENT_CUTOFF_FACTOR) self.covalent_factor = tk.DoubleVar(value=stored_factor) ttk.Scale( group, from_=ParameterRanges.MIN_COVALENT_FACTOR, to=ParameterRanges.MAX_COVALENT_FACTOR, variable=self.covalent_factor, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) # Value display factor_label = ttk.Label(group, text="") factor_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_factor_label(*args): try: factor_label.config(text=f"{self.covalent_factor.get():.2f}") except tk.TclError: pass # Widget destroyed, ignore self.covalent_factor.trace("w", update_factor_label) update_factor_label() def _on_category_selected(self, event): """Handle category selection from list.""" selection = self.category_listbox.curselection() if not selection: return # Store current values before switching self._store_current_values() # Clear current content if self.current_content: self.current_content.destroy() # Create new content frame self.current_content = ttk.Frame(self.content_frame, padding="20") self.current_content.pack(fill=tk.BOTH, expand=True) # Show appropriate content based on selection category_index = selection[0] if category_index == 0: # General Parameters self._create_general_parameters(self.current_content) elif category_index == 1: # Hydrogen Bonds self._create_hydrogen_bond_parameters(self.current_content) elif category_index == 2: # Weak Hydrogen Bonds self._create_weak_hydrogen_bond_parameters(self.current_content) elif category_index == 3: # Halogen Bonds self._create_halogen_bond_parameters(self.current_content) elif category_index == 4: # π Interactions self._create_pi_interaction_parameters(self.current_content) # Reset scroll position self.content_canvas.yview_moveto(0) def _create_hydrogen_bond_parameters(self, parent): """Create hydrogen bond parameter controls.""" group = ttk.LabelFrame(parent, text="Hydrogen Bond Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # H...A distance ttk.Label(group, text="H...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_dist = self._param_values.get('hb_distance', ParametersDefault.HB_DISTANCE_CUTOFF) self.hb_distance = tk.DoubleVar(value=stored_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.hb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) hb_dist_label = ttk.Label(group, text="") hb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_hb_dist(*args): try: hb_dist_label.config(text=f"{self.hb_distance.get():.1f}") except tk.TclError: pass self.hb_distance.trace("w", update_hb_dist) update_hb_dist() # D-H...A angle ttk.Label(group, text="D-H...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_angle = self._param_values.get('hb_angle', ParametersDefault.HB_ANGLE_CUTOFF) self.hb_angle = tk.DoubleVar(value=stored_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.hb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) hb_angle_label = ttk.Label(group, text="") hb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_hb_angle(*args): try: hb_angle_label.config(text=f"{self.hb_angle.get():.0f}") except tk.TclError: pass self.hb_angle.trace("w", update_hb_angle) update_hb_angle() # D...A distance ttk.Label(group, text="D...A Distance (Å):").grid( row=2, column=0, sticky=tk.W, pady=2 ) stored_da = self._param_values.get('da_distance', ParametersDefault.HB_DA_DISTANCE) self.da_distance = tk.DoubleVar(value=stored_da) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.da_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) da_dist_label = ttk.Label(group, text="") da_dist_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_da_dist(*args): try: da_dist_label.config(text=f"{self.da_distance.get():.1f}") except tk.TclError: pass self.da_distance.trace("w", update_da_dist) update_da_dist() def _create_weak_hydrogen_bond_parameters(self, parent): """Create weak hydrogen bond parameter controls for carbon donors.""" group = ttk.LabelFrame(parent, text="Weak Hydrogen Bond Parameters (Carbon Donors)", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # WHB H...A distance ttk.Label(group, text="H...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_whb_dist = self._param_values.get('whb_distance', ParametersDefault.WHB_DISTANCE_CUTOFF) self.whb_distance = tk.DoubleVar(value=stored_whb_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.whb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) whb_dist_label = ttk.Label(group, text="") whb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_dist(*args): try: whb_dist_label.config(text=f"{self.whb_distance.get():.1f}") except tk.TclError: pass self.whb_distance.trace("w", update_whb_dist) update_whb_dist() # WHB D-H...A angle ttk.Label(group, text="D-H...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_whb_angle = self._param_values.get('whb_angle', ParametersDefault.WHB_ANGLE_CUTOFF) self.whb_angle = tk.DoubleVar(value=stored_whb_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.whb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) whb_angle_label = ttk.Label(group, text="") whb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_angle(*args): try: whb_angle_label.config(text=f"{self.whb_angle.get():.0f}") except tk.TclError: pass self.whb_angle.trace("w", update_whb_angle) update_whb_angle() # WHB D...A distance ttk.Label(group, text="D...A Distance (Å):").grid( row=2, column=0, sticky=tk.W, pady=2 ) stored_whb_da = self._param_values.get('whb_da_distance', ParametersDefault.WHB_DA_DISTANCE) self.whb_da_distance = tk.DoubleVar(value=stored_whb_da) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.whb_da_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) whb_da_dist_label = ttk.Label(group, text="") whb_da_dist_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_da_dist(*args): try: whb_da_dist_label.config(text=f"{self.whb_da_distance.get():.1f}") except tk.TclError: pass self.whb_da_distance.trace("w", update_whb_da_dist) update_whb_da_dist() def _create_halogen_bond_parameters(self, parent): """Create halogen bond parameter controls.""" group = ttk.LabelFrame(parent, text="Halogen Bond Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # X...A distance ttk.Label(group, text="X...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_xb_dist = self._param_values.get('xb_distance', ParametersDefault.XB_DISTANCE_CUTOFF) self.xb_distance = tk.DoubleVar(value=stored_xb_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.xb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) xb_dist_label = ttk.Label(group, text="") xb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_xb_dist(*args): try: xb_dist_label.config(text=f"{self.xb_distance.get():.1f}") except tk.TclError: pass self.xb_distance.trace("w", update_xb_dist) update_xb_dist() # C-X...A angle ttk.Label(group, text="C-X...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_xb_angle = self._param_values.get('xb_angle', ParametersDefault.XB_ANGLE_CUTOFF) self.xb_angle = tk.DoubleVar(value=stored_xb_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.xb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) xb_angle_label = ttk.Label(group, text="") xb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_xb_angle(*args): try: xb_angle_label.config(text=f"{self.xb_angle.get():.0f}") except tk.TclError: pass self.xb_angle.trace("w", update_xb_angle) update_xb_angle() def _create_pi_interaction_parameters(self, parent): """Create π interaction parameter controls.""" # General π interaction parameters general_group = ttk.LabelFrame(parent, text="General π Interaction Parameters", padding=10) general_group.pack(fill=tk.X, padx=10, pady=5) # H...π distance ttk.Label(general_group, text="H...π Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_pi_dist = self._param_values.get('pi_distance', ParametersDefault.PI_DISTANCE_CUTOFF) self.pi_distance = tk.DoubleVar(value=stored_pi_dist) ttk.Scale( general_group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.pi_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) pi_dist_label = ttk.Label(general_group, text="") pi_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_dist(*args): try: pi_dist_label.config(text=f"{self.pi_distance.get():.1f}") except tk.TclError: pass self.pi_distance.trace("w", update_pi_dist) update_pi_dist() # D-H...π angle ttk.Label(general_group, text="D-H...π Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_pi_angle = self._param_values.get('pi_angle', ParametersDefault.PI_ANGLE_CUTOFF) self.pi_angle = tk.DoubleVar(value=stored_pi_angle) ttk.Scale( general_group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.pi_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) pi_angle_label = ttk.Label(general_group, text="") pi_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_angle(*args): try: pi_angle_label.config(text=f"{self.pi_angle.get():.0f}") except tk.TclError: pass self.pi_angle.trace("w", update_pi_angle) update_pi_angle() # π interaction subtype parameters subtypes_group = ttk.LabelFrame(parent, text="π Interaction Subtype Parameters", padding=10) subtypes_group.pack(fill=tk.X, padx=10, pady=5) # Helper function to create parameter pair def create_parameter_pair(parent_frame, row, label_text, dist_var_name, angle_var_name, dist_default, angle_default): # Get stored values or use defaults stored_dist = self._param_values.get(dist_var_name, dist_default) stored_angle = self._param_values.get(angle_var_name, angle_default) # Distance parameter ttk.Label(parent_frame, text=f"{label_text} Distance (Å):").grid( row=row, column=0, sticky=tk.W, pady=2 ) dist_var = tk.DoubleVar(value=stored_dist) setattr(self, dist_var_name, dist_var) ttk.Scale( parent_frame, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=dist_var, orient=tk.HORIZONTAL, length=150, ).grid(row=row, column=1, sticky=tk.W, padx=5, pady=2) dist_label = ttk.Label(parent_frame, text="") dist_label.grid(row=row, column=2, sticky=tk.W, padx=5, pady=2) # Angle parameter ttk.Label(parent_frame, text=f"{label_text} Angle (°):").grid( row=row, column=3, sticky=tk.W, pady=2, padx=(20,0) ) angle_var = tk.DoubleVar(value=stored_angle) setattr(self, angle_var_name, angle_var) ttk.Scale( parent_frame, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=angle_var, orient=tk.HORIZONTAL, length=150, ).grid(row=row, column=4, sticky=tk.W, padx=5, pady=2) angle_label = ttk.Label(parent_frame, text="") angle_label.grid(row=row, column=5, sticky=tk.W, padx=5, pady=2) # Update functions def update_dist(*args): try: dist_label.config(text=f"{dist_var.get():.1f}") except tk.TclError: pass def update_angle(*args): try: angle_label.config(text=f"{angle_var.get():.0f}") except tk.TclError: pass dist_var.trace("w", update_dist) angle_var.trace("w", update_angle) update_dist() update_angle() # Create all subtype parameters create_parameter_pair(subtypes_group, 0, "C-Cl...π", "pi_ccl_distance", "pi_ccl_angle", ParametersDefault.PI_CCL_DISTANCE_CUTOFF, ParametersDefault.PI_CCL_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 1, "C-Br...π", "pi_cbr_distance", "pi_cbr_angle", ParametersDefault.PI_CBR_DISTANCE_CUTOFF, ParametersDefault.PI_CBR_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 2, "C-I...π", "pi_ci_distance", "pi_ci_angle", ParametersDefault.PI_CI_DISTANCE_CUTOFF, ParametersDefault.PI_CI_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 3, "C-H...π", "pi_ch_distance", "pi_ch_angle", ParametersDefault.PI_CH_DISTANCE_CUTOFF, ParametersDefault.PI_CH_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 4, "N-H...π", "pi_nh_distance", "pi_nh_angle", ParametersDefault.PI_NH_DISTANCE_CUTOFF, ParametersDefault.PI_NH_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 5, "O-H...π", "pi_oh_distance", "pi_oh_angle", ParametersDefault.PI_OH_DISTANCE_CUTOFF, ParametersDefault.PI_OH_ANGLE_CUTOFF) create_parameter_pair(subtypes_group, 6, "S-H...π", "pi_sh_distance", "pi_sh_angle", ParametersDefault.PI_SH_DISTANCE_CUTOFF, ParametersDefault.PI_SH_ANGLE_CUTOFF)
[docs] def get_parameters(self) -> AnalysisParameters: """Get current parameter values. :returns: Current analysis parameters :rtype: AnalysisParameters """ # Store current values first self._store_current_values() # Get values from stored parameters or current variables def get_value(var_name, default_value): if var_name in self._param_values: return self._param_values[var_name] elif hasattr(self, var_name): var = getattr(self, var_name) if var: try: return var.get() except tk.TclError: pass return default_value return AnalysisParameters( hb_distance_cutoff=get_value('hb_distance', ParametersDefault.HB_DISTANCE_CUTOFF), hb_angle_cutoff=get_value('hb_angle', ParametersDefault.HB_ANGLE_CUTOFF), hb_donor_acceptor_cutoff=get_value('da_distance', ParametersDefault.HB_DA_DISTANCE), whb_distance_cutoff=get_value('whb_distance', ParametersDefault.WHB_DISTANCE_CUTOFF), whb_angle_cutoff=get_value('whb_angle', ParametersDefault.WHB_ANGLE_CUTOFF), whb_donor_acceptor_cutoff=get_value('whb_da_distance', ParametersDefault.WHB_DA_DISTANCE), xb_distance_cutoff=get_value('xb_distance', ParametersDefault.XB_DISTANCE_CUTOFF), xb_angle_cutoff=get_value('xb_angle', ParametersDefault.XB_ANGLE_CUTOFF), pi_distance_cutoff=get_value('pi_distance', ParametersDefault.PI_DISTANCE_CUTOFF), pi_angle_cutoff=get_value('pi_angle', ParametersDefault.PI_ANGLE_CUTOFF), # π interaction subtype parameters pi_ccl_distance_cutoff=get_value('pi_ccl_distance', ParametersDefault.PI_CCL_DISTANCE_CUTOFF), pi_ccl_angle_cutoff=get_value('pi_ccl_angle', ParametersDefault.PI_CCL_ANGLE_CUTOFF), pi_cbr_distance_cutoff=get_value('pi_cbr_distance', ParametersDefault.PI_CBR_DISTANCE_CUTOFF), pi_cbr_angle_cutoff=get_value('pi_cbr_angle', ParametersDefault.PI_CBR_ANGLE_CUTOFF), pi_ci_distance_cutoff=get_value('pi_ci_distance', ParametersDefault.PI_CI_DISTANCE_CUTOFF), pi_ci_angle_cutoff=get_value('pi_ci_angle', ParametersDefault.PI_CI_ANGLE_CUTOFF), pi_ch_distance_cutoff=get_value('pi_ch_distance', ParametersDefault.PI_CH_DISTANCE_CUTOFF), pi_ch_angle_cutoff=get_value('pi_ch_angle', ParametersDefault.PI_CH_ANGLE_CUTOFF), pi_nh_distance_cutoff=get_value('pi_nh_distance', ParametersDefault.PI_NH_DISTANCE_CUTOFF), pi_nh_angle_cutoff=get_value('pi_nh_angle', ParametersDefault.PI_NH_ANGLE_CUTOFF), pi_oh_distance_cutoff=get_value('pi_oh_distance', ParametersDefault.PI_OH_DISTANCE_CUTOFF), pi_oh_angle_cutoff=get_value('pi_oh_angle', ParametersDefault.PI_OH_ANGLE_CUTOFF), pi_sh_distance_cutoff=get_value('pi_sh_distance', ParametersDefault.PI_SH_DISTANCE_CUTOFF), pi_sh_angle_cutoff=get_value('pi_sh_angle', ParametersDefault.PI_SH_ANGLE_CUTOFF), covalent_cutoff_factor=get_value('covalent_factor', ParametersDefault.COVALENT_CUTOFF_FACTOR), analysis_mode=get_value('analysis_mode', ParametersDefault.ANALYSIS_MODE), )
[docs] def set_parameters(self, params: AnalysisParameters) -> None: """Set parameter values from AnalysisParameters object. :param params: Analysis parameters to set :type params: AnalysisParameters """ # Store values directly in our parameter storage self._param_values.update({ 'hb_distance': params.hb_distance_cutoff, 'hb_angle': params.hb_angle_cutoff, 'da_distance': params.hb_donor_acceptor_cutoff, 'whb_distance': params.whb_distance_cutoff, 'whb_angle': params.whb_angle_cutoff, 'whb_da_distance': params.whb_donor_acceptor_cutoff, 'xb_distance': params.xb_distance_cutoff, 'xb_angle': params.xb_angle_cutoff, 'pi_distance': params.pi_distance_cutoff, 'pi_angle': params.pi_angle_cutoff, 'pi_ccl_distance': params.pi_ccl_distance_cutoff, 'pi_ccl_angle': params.pi_ccl_angle_cutoff, 'pi_cbr_distance': params.pi_cbr_distance_cutoff, 'pi_cbr_angle': params.pi_cbr_angle_cutoff, 'pi_ci_distance': params.pi_ci_distance_cutoff, 'pi_ci_angle': params.pi_ci_angle_cutoff, 'pi_ch_distance': params.pi_ch_distance_cutoff, 'pi_ch_angle': params.pi_ch_angle_cutoff, 'pi_nh_distance': params.pi_nh_distance_cutoff, 'pi_nh_angle': params.pi_nh_angle_cutoff, 'pi_oh_distance': params.pi_oh_distance_cutoff, 'pi_oh_angle': params.pi_oh_angle_cutoff, 'pi_sh_distance': params.pi_sh_distance_cutoff, 'pi_sh_angle': params.pi_sh_angle_cutoff, 'covalent_factor': params.covalent_cutoff_factor, 'analysis_mode': params.analysis_mode, }) # Set values on existing variables if they exist def safe_set(var_name, value): if hasattr(self, var_name): var = getattr(self, var_name) if var: try: var.set(value) except tk.TclError: pass # Variable destroyed, ignore safe_set('hb_distance', params.hb_distance_cutoff) safe_set('hb_angle', params.hb_angle_cutoff) safe_set('da_distance', params.hb_donor_acceptor_cutoff) safe_set('whb_distance', params.whb_distance_cutoff) safe_set('whb_angle', params.whb_angle_cutoff) safe_set('whb_da_distance', params.whb_donor_acceptor_cutoff) safe_set('xb_distance', params.xb_distance_cutoff) safe_set('xb_angle', params.xb_angle_cutoff) safe_set('pi_distance', params.pi_distance_cutoff) safe_set('pi_angle', params.pi_angle_cutoff) safe_set('pi_ccl_distance', params.pi_ccl_distance_cutoff) safe_set('pi_ccl_angle', params.pi_ccl_angle_cutoff) safe_set('pi_cbr_distance', params.pi_cbr_distance_cutoff) safe_set('pi_cbr_angle', params.pi_cbr_angle_cutoff) safe_set('pi_ci_distance', params.pi_ci_distance_cutoff) safe_set('pi_ci_angle', params.pi_ci_angle_cutoff) safe_set('pi_ch_distance', params.pi_ch_distance_cutoff) safe_set('pi_ch_angle', params.pi_ch_angle_cutoff) safe_set('pi_nh_distance', params.pi_nh_distance_cutoff) safe_set('pi_nh_angle', params.pi_nh_angle_cutoff) safe_set('pi_oh_distance', params.pi_oh_distance_cutoff) safe_set('pi_oh_angle', params.pi_oh_angle_cutoff) safe_set('pi_sh_distance', params.pi_sh_distance_cutoff) safe_set('pi_sh_angle', params.pi_sh_angle_cutoff) safe_set('covalent_factor', params.covalent_cutoff_factor) safe_set('analysis_mode', params.analysis_mode)
def _set_defaults(self): """Reset all parameters to default values.""" default_params = AnalysisParameters() self.set_parameters(default_params)
[docs] def reset_to_defaults(self) -> None: """Public method to reset parameters to defaults.""" self._set_defaults()
def _open_preset_manager(self): """Open the preset manager dialog.""" from .preset_manager_dialog import PresetManagerDialog # Get current parameters current_params = self.get_parameters() # Open preset manager dialog = PresetManagerDialog(self.dialog, current_params) result = dialog.get_result() if result: # Apply the loaded preset self._apply_preset_data(result) messagebox.showinfo("Success", "Preset loaded successfully") def _apply_preset_data(self, data: Dict[str, Any]) -> None: """Apply preset data to parameters.""" if "parameters" not in data: raise ValueError("Invalid preset format: missing 'parameters' section") params = data["parameters"] # Apply hydrogen bond parameters if "hydrogen_bonds" in params: hb = params["hydrogen_bonds"] self.hb_distance.set(hb.get("h_a_distance_cutoff", ParametersDefault.HB_DISTANCE_CUTOFF)) self.hb_angle.set(hb.get("dha_angle_cutoff", ParametersDefault.HB_ANGLE_CUTOFF)) self.da_distance.set(hb.get("d_a_distance_cutoff", ParametersDefault.HB_DA_DISTANCE)) # Apply halogen bond parameters if "halogen_bonds" in params: xb = params["halogen_bonds"] self.xb_distance.set(xb.get("x_a_distance_cutoff", ParametersDefault.XB_DISTANCE_CUTOFF)) self.xb_angle.set(xb.get("dxa_angle_cutoff", ParametersDefault.XB_ANGLE_CUTOFF)) # Apply π interaction parameters if "pi_interactions" in params: pi = params["pi_interactions"] self.pi_distance.set(pi.get("h_pi_distance_cutoff", ParametersDefault.PI_DISTANCE_CUTOFF)) self.pi_angle.set(pi.get("dh_pi_angle_cutoff", ParametersDefault.PI_ANGLE_CUTOFF)) # Apply π interaction subtype parameters self.pi_ccl_distance.set(pi.get("ccl_pi_distance_cutoff", ParametersDefault.PI_CCL_DISTANCE_CUTOFF)) self.pi_ccl_angle.set(pi.get("ccl_pi_angle_cutoff", ParametersDefault.PI_CCL_ANGLE_CUTOFF)) self.pi_cbr_distance.set(pi.get("cbr_pi_distance_cutoff", ParametersDefault.PI_CBR_DISTANCE_CUTOFF)) self.pi_cbr_angle.set(pi.get("cbr_pi_angle_cutoff", ParametersDefault.PI_CBR_ANGLE_CUTOFF)) self.pi_ci_distance.set(pi.get("ci_pi_distance_cutoff", ParametersDefault.PI_CI_DISTANCE_CUTOFF)) self.pi_ci_angle.set(pi.get("ci_pi_angle_cutoff", ParametersDefault.PI_CI_ANGLE_CUTOFF)) self.pi_ch_distance.set(pi.get("ch_pi_distance_cutoff", ParametersDefault.PI_CH_DISTANCE_CUTOFF)) self.pi_ch_angle.set(pi.get("ch_pi_angle_cutoff", ParametersDefault.PI_CH_ANGLE_CUTOFF)) self.pi_nh_distance.set(pi.get("nh_pi_distance_cutoff", ParametersDefault.PI_NH_DISTANCE_CUTOFF)) self.pi_nh_angle.set(pi.get("nh_pi_angle_cutoff", ParametersDefault.PI_NH_ANGLE_CUTOFF)) self.pi_oh_distance.set(pi.get("oh_pi_distance_cutoff", ParametersDefault.PI_OH_DISTANCE_CUTOFF)) self.pi_oh_angle.set(pi.get("oh_pi_angle_cutoff", ParametersDefault.PI_OH_ANGLE_CUTOFF)) self.pi_sh_distance.set(pi.get("sh_pi_distance_cutoff", ParametersDefault.PI_SH_DISTANCE_CUTOFF)) self.pi_sh_angle.set(pi.get("sh_pi_angle_cutoff", ParametersDefault.PI_SH_ANGLE_CUTOFF)) # Apply general parameters if "general" in params: gen = params["general"] self.covalent_factor.set(gen.get("covalent_cutoff_factor", ParametersDefault.COVALENT_CUTOFF_FACTOR)) self.analysis_mode.set(gen.get("analysis_mode", ParametersDefault.ANALYSIS_MODE)) def _ok(self): """Handle OK button - save settings and close.""" self.result = self.get_parameters() self.dialog.destroy() def _cancel(self): """Handle Cancel button - close without saving.""" self.result = None self.dialog.destroy()
[docs] def get_result(self) -> Optional[AnalysisParameters]: """Get the configured parameters. :returns: Analysis parameters or None if cancelled :rtype: Optional[AnalysisParameters] """ self.dialog.wait_window() return self.result