Source code for xtb_step.tk_energy

# -*- coding: utf-8 -*-

"""The graphical part of an xTB Energy step.

This class is also the base class for ``TkOptimization`` and
``TkFrequencies``. It owns the "energy frame", which holds the
energy / Hamiltonian / solvation widgets common to all xTB substeps.
Subclasses inherit ``create_dialog``, ``reset_dialog``, and
``reset_energy_frame`` unchanged, and just add their own frames below
the energy frame -- exactly the MOPAC ``TkEnergy`` / ``TkOptimization``
pattern.

The dialog hides the ``solvent`` widget when ``solvation model`` is
``"none"`` and indents it under the solvation row when shown.
"""

import pprint  # noqa: F401
import tkinter as tk
import tkinter.ttk as ttk

import xtb_step  # noqa: F401, E999
import seamm
from seamm_util import ureg, Q_, units_class  # noqa: F401, E999
import seamm_widgets as sw


[docs] class TkEnergy(seamm.TkNode): """The graphical part of an Energy step in a flowchart. Subclasses (TkOptimization, TkFrequencies, ...) inherit the energy-frame layout and the conditional solvent display, and add their own frames in their own ``create_dialog`` / ``reset_dialog``. """ def __init__( self, tk_flowchart=None, node=None, canvas=None, x=None, y=None, w=200, h=50, ): """Initialize a graphical node.""" self.dialog = None super().__init__( tk_flowchart=tk_flowchart, node=node, canvas=canvas, x=x, y=y, w=w, h=h, )
[docs] def create_dialog(self, title="xTB Energy"): """Create the dialog and the energy frame. Subclasses should call ``super().create_dialog(title=...)`` first and then add their own frames into ``self["frame"]``. """ frame = super().create_dialog(title=title) # Size the dialog generously so the results tab fits without # heavy scrolling. Same pattern as TkxTB.create_dialog. screen_w = self.dialog.winfo_screenwidth() screen_h = self.dialog.winfo_screenheight() w = int(0.9 * screen_w) h = int(0.8 * screen_h) x = int(0.05 * screen_w / 2) y = int(0.1 * screen_h / 2) self.dialog.geometry(f"{w}x{h}+{x}+{y}") # Shortcut for parameters P = self.node.parameters # Energy frame -- holds method, accuracy, solvation, solvent e_frame = self["energy frame"] = ttk.LabelFrame( frame, borderwidth=4, relief="sunken", text="Hamiltonian Parameters", labelanchor="n", padding=10, ) # Create the value widgets (skip non-display ones) for key in xtb_step.EnergyParameters.parameters: if key in ("results", "extra keywords", "create tables"): continue self[key] = P[key].widget(e_frame) # Bind the solvation-model widget so changes redraw the energy # frame layout. smodel = self["solvation model"] smodel.combobox.bind("<<ComboboxSelected>>", self.reset_energy_frame) smodel.combobox.bind("<Return>", self.reset_energy_frame) smodel.combobox.bind("<FocusOut>", self.reset_energy_frame) # Note: do NOT call self.reset_dialog() here. The seamm framework # calls reset_dialog() itself after create_dialog() returns, by # which time any subclass create_dialog has already built its own # frames. Calling it here triggers MRO-dispatched subclass # reset_dialog() before subclass frames exist (KeyError). return frame
[docs] def reset_dialog(self, widget=None): """Top-level layout. Subclasses override and call ``super()``. Returns ------- int The next free row in ``self["frame"]``, so subclasses can grid their additional frames below the energy frame. """ # Remove any widgets previously packed in the top-level frame frame = self["frame"] for slave in frame.grid_slaves(): slave.grid_forget() # Place the energy frame at the top row = 0 self["energy frame"].grid(row=row, column=0, sticky=tk.EW) row += 1 # Lay out the energy widgets inside the energy frame, with the # conditional solvent visibility. self.reset_energy_frame() # Setup the results tab if there are any have_results = ( "results" in self.node.metadata and len(self.node.metadata["results"]) > 0 ) if have_results and "results" in self.node.parameters: self.setup_results() return row
[docs] def reset_energy_frame(self, widget=None): """Layout the widgets inside the energy frame. The ``solvent`` widget is shown only when ``solvation model`` is not ``"none"``. To get the indentation right despite the varying widths of the main-column labels, we use the Gaussian-step pattern: layout column 0 widgets and column 1 (indented) widgets separately, ``align_labels`` on each, and then size column 0 to ``w1 - w2 + 30`` so the column-1 label starts at roughly the position of the column-0 value. """ e_frame = self["energy frame"] for slave in e_frame.grid_slaves(): slave.grid_forget() row = 0 widgets = [] widgets2 = [] # Always-visible widgets in column 0 for key in ("method", "accuracy", "solvation model"): self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW) widgets.append(self[key]) row += 1 # Conditional: solvent in column 1 (indented) only if solvation != "none" smodel = self["solvation model"].get() if smodel != "none": self["solvent"].grid(row=row, column=1, sticky=tk.EW) widgets2.append(self["solvent"]) row += 1 w1 = sw.align_labels(widgets, sticky=tk.E) if widgets2: w2 = sw.align_labels(widgets2, sticky=tk.E) e_frame.columnconfigure(0, minsize=w1 - w2 + 30) return row
[docs] def right_click(self, event): """Right-click context menu.""" super().right_click(event) self.popup_menu.add_command(label="Edit..", command=self.edit) self.popup_menu.tk_popup(event.x_root, event.y_root, 0)