Source code for seamm.tk_node

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

"""The base class for Tk nodes (steps) in the GUI for flowcharts."""

import collections.abc
import copy
import logging
import json
import Pmw
import seamm
from seamm_util import default_units
import seamm_widgets as sw
import tkinter as tk
import tkinter.ttk as ttk

logger = logging.getLogger(__name__)


[docs] class TkNode(collections.abc.MutableMapping): """The base class for Tk nodes (steps) in the GUI for flowcharts. Parameters ---------- tk_flowchart : seamm.TkFlowchart The graphical flowchart this step is in. node : seamm.Node The non-graphical node this corresponds to. node_type : "simple", "loop" The type of node on the graph. "simple" has an in and out arrow. "loop" has three arrows. canvas: tkinter.Canvas The Canvas widget that this node is drawn on. x: int The x-coordinate the drawing for the node on the canvas. y: int The y-coordinate of the drawing for the node on the canvas. w: int The width of the drawing for the node on the canvas. h: int The height of the drawing for the node on the canvas. my_logger : logging.Logger, optional The logger to use. Defaults to the global one defined in the module. Fields ------ border canvas dialog : tkinter.Toplevel The dialog for editing the parameters. flowchart h logger : logging.Logger The logger for debug & warning output. node : seamm.Node The non-graphical node corresponding to this graphical one. node_type : enum("simple", "loop") The type of the node from the point of connectivity. popup_menu : tkinter.Menu The popup menu used for right-clicks. selected tag title title_label : tkinter.ttk.Label The label for the title of the step on the display. tk_flowchart : seamm.TkFlowchart The Tk Flowchart that contains this step. tk_subflowchart : seamm.TkFlowchart The sub flowchart is if this step contains one. w x y uuid Notes ----- The state is held in the corresponding non-graphical node, `self.node`. Many of the properties are thin-wrappers to the same property of the non-graphical node. Results are stored in the following columns of the results table:: 0 Result name 1 <separator> 2 Save in database 3 <separator> 4 Save as JSON 5 <separator> 6 checkbox 7 Save in variable named 8 <separator> 9 Save in table 10 as Column name 11 Units """ anchor_points = { "s": (+0.00, +0.50), "sse": (+0.25, +0.50), "se": (+0.50, +0.50), "ese": (+0.50, +0.25), "e": (+0.50, +0.00), "ene": (+0.50, -0.25), "ne": (+0.50, -0.50), "nne": (+0.25, -0.50), "n": (+0.00, -0.50), "nnw": (-0.25, -0.50), "nw": (-0.50, -0.50), "wnw": (-0.50, -0.25), "w": (-0.50, +0.00), "wsw": (-0.50, +0.25), "sw": (-0.50, +0.50), "ssw": (-0.25, +0.50), } def __init__( self, tk_flowchart=None, node=None, node_type="simple", canvas=None, x=None, y=None, w=None, h=None, my_logger=logger, ): """Initialize a node Keyword arguments: """ self._border = None self._selected = False self._tmp = None self._canvas = None self.canvas = canvas self.dialog = None self.logger = my_logger self.node = node self.node_type = node_type self.popup_menu = None self._tables = None # Temporary list of all tables up to an including this node self.title_label = None self.tk_flowchart = tk_flowchart self.tk_subflowchart = None if self.node is not None: if self.node.x is None: self.node.x = x if self.node.y is None: self.node.y = y if self.node.w is None: self.node.w = w if self.node.h is None: self.node.h = h # Widget information self._widget = {} self.tk_var = {} self.results_widgets = None # Because the default for saving properties in the database is True # we need to initialize the results to include them by default if self.node.parameters is not None and "results" in self.node.parameters: self.initialize_results() def __hash__(self): """Provide a unique key to make iterable.""" return self.node.uuid def __eq__(self, other): """Test for equality (identity) with another node.""" return self.__class__ == other.__class__ and self.__hash__() == other.__hash__() # Provide dict like access to the widgets to make # the code cleaner def __getitem__(self, key): """Allow [] access to the widgets.""" return self._widget[key] def __setitem__(self, key, value): """Allow [key] access to set a widget.""" self._widget[key] = value def __delitem__(self, key): """Allow deletion of widgets.""" if key in self._widget: self._widget[key].destroy() del self._widget[key] def __iter__(self): """Allow iteration over the widgets""" return iter(self._widget) def __len__(self): """Provide the nmber of widgets, for e.g. len() command.""" return len(self._widget) @property def border(self): """The border of the picture in the flowchart""" return self._border @border.setter def border(self, value): self._border = value @property def canvas(self): """The canvas for drawing the node""" return self._canvas @canvas.setter def canvas(self, value): self._canvas = value @property def flowchart(self): """The flowchart object""" return self.node.flowchart @flowchart.setter def flowchart(self, value): """The flowchart object""" self.node.flowchart = value @property def h(self): """The height of the graphical node""" return self.node.h @h.setter def h(self, value): self.node.h = value @property def selected(self): """Whether I am selected or not""" return self._selected @selected.setter def selected(self, value): self._selected = value if value: self.canvas.itemconfigure(self.border, outline="red") else: self.canvas.itemconfigure(self.border, outline="black") @property def tag(self): """The string representation of the uuid of the node""" return self.node.tag @property def title(self): """The title to display""" return self.node.title @title.setter def title(self, value): self.node.title = value if self.title_label is not None: self.canvas.itemconfigure(self.title_label, text=value) @property def w(self): """The width of the graphical node""" return self.node.w @w.setter def w(self, value): self.node.w = value @property def x(self): """The x-position of the center of the graphical node""" return self.node.x @x.setter def x(self, value): self.node.x = value @property def y(self): """The y-position of the center of the graphical node""" return self.node.y @y.setter def y(self, value): self.node.y = value @property def uuid(self): """The uuid of the node""" return self.node.uuid
[docs] def activate(self): """Add active handles at the anchor points and change the cursor.""" self.canvas.delete(self.tag + " && type=anchor") for pt, x, y in self.anchor_point("all"): x0 = x - 2 y0 = y - 2 x1 = x + 2 y1 = y + 2 self.canvas.create_oval( x0, y0, x1, y1, fill="red", outline="red", tags=[self.tag, "type=anchor", "anchor=" + pt], )
[docs] def activate_anchor_point(self, point, halo): """Put a marker on the anchor point to indicate it is under the cursor.""" x, y = self.anchor_point(point) self.canvas.create_oval( x - halo, y - halo, x + halo, y + halo, fill="red", outline="red", tags=[self.tag, "type=active_anchor", "anchor=" + point], )
[docs] def anchor_point(self, anchor="all"): """Where the anchor points are located. If "all" is given a dictionary of all points is returned""" if anchor == "all": result = [] for pt in type(self).anchor_points: a, b = type(self).anchor_points[pt] result.append((pt, int(self.x + a * self.w), int(self.y + b * self.h))) return result if anchor in type(self).anchor_points: a, b = type(self).anchor_points[anchor] return (int(self.x + a * self.w), int(self.y + b * self.h)) raise NotImplementedError("anchor position '{}' not implemented".format(anchor))
[docs] def check_anchor_points(self, x, y, halo): """If the position x, y is within halo or one of the anchor points activate the point and return the name of the anchor point """ points = [] for direction, edge in self.connections(): if direction == "out": points.append(edge.anchor1) else: points.append(edge.anchor2) for point, x0, y0 in self.anchor_point(): if x >= x0 - halo and x <= x0 + halo and y >= y0 - halo and y <= y0 + halo: if point in points: return None else: return point return None
[docs] def connections(self): """Return a list of all the incoming and outgoing edges for this node, giving the anchor points and other node """ return self.tk_flowchart.edges(self)
[docs] def create_dialog( self, title="Edit step", widget="frame", results_tab=False, ): """Create the base dialog for editing the parameters for a step. Parameters ---------- title : str The title of the dialog. widget : enum Whether to use a simple dialog ("frame") or use a notebook ("notebook"). results_tab : bool **OBSOLETE** Not longer used. """ toplevel = self.canvas.winfo_toplevel() self.logger.debug("Create dialog in tk_node base class") self.dialog = Pmw.Dialog( toplevel, buttons=("OK", "Cancel"), master=toplevel, title=title, command=self.handle_dialog, ) self.dialog.withdraw() results_tab = ( self.node.parameters is not None and "results" in self.node.parameters ) if widget == "notebook" or results_tab or "keywords" in self.node.metadata: # A tabbed notebook notebook = ttk.Notebook(self.dialog.interior()) notebook.pack(side="top", fill=tk.BOTH, expand=tk.YES) self["notebook"] = notebook # Main frame holding the widgets frame = ttk.Frame(notebook) self["frame"] = frame notebook.add(frame, text="Parameters", sticky=tk.NSEW) elif widget == "frame": # Create a frame to hold everything frame = ttk.Frame(self.dialog.interior()) frame.pack(expand=tk.YES, fill=tk.BOTH) self["frame"] = frame return frame if results_tab: # Second tab for results if requested rframe = self["results frame"] = ttk.Frame(notebook) notebook.add(rframe, text="Results", sticky=tk.NSEW) # Shortcut for parameters P = self.node.parameters if "create tables" in P: var = self.tk_var["create tables"] = tk.IntVar() if P["create tables"].value == "yes": var.set(1) else: var.set(0) self["create tables"] = ttk.Checkbutton( rframe, text="Create tables if needed", variable=var ) self["create tables"].grid(row=0, column=0, sticky=tk.W) self["results"] = sw.ScrolledColumns( rframe, columns=[ "Result", "|", "Save in database", "|", "Save as JSON", "|", "", "Save in variable named", "|", "Save in table", "as Column name", "Units", ], ) self["results"].grid(row=1, column=0, sticky=tk.NSEW) rframe.columnconfigure(0, weight=1) rframe.rowconfigure(1, weight=1) if "keywords" in self.node.metadata: # Next tab to handle adding keywords manually self.logger.debug("Adding the keyword tab") kframe = self["add_to_input"] = ttk.Frame(notebook) notebook.add(kframe, text="Add to input", sticky=tk.NSEW) self["keywords"] = sw.Keywords( kframe, metadata=self.node.metadata["keywords"], keywords=self.node.parameters["extra keywords"].value, ) self["keywords"].pack(expand="yes", fill="both") return frame
[docs] def deactivate(self): """Remove the decorations that indicate active anchor points""" self.canvas.delete(self.tag + " && type=anchor") self.canvas.delete(self.tag + " && type=active_anchor")
[docs] def default_edge_subtype(self): """Return the default subtype of the edge. Usually this is '' but for nodes with two or more edges leaving them, such as a loop, this method will return an appropriate default for the current edge. For example, by default the first edge emanating from a loop-node is the 'loop' edge; the second, the 'exit' edge. A return value of 'too many' indicates that the node exceeds the number of allowed exit edges. """ # how many outgoing edges are there? n_edges = len(self.tk_flowchart.edges(self, direction="out")) self.logger.debug("node.default_edge_label, n_edges = {}".format(n_edges)) if n_edges == 0: return "next" else: return "too many"
[docs] def double_click(self, event): """Handle a double-click on the node. This method raises the dialog to edit the parameters. Subclasses should override this as appropriate! """ self.edit()
[docs] def draw(self): """Draw the node on the given canvas, making it visible""" # Remove any graphics items self.undraw() # the outline x0 = self.x - self.w / 2 x1 = x0 + self.w y0 = self.y - self.h / 2 y1 = y0 + self.h self.border = self.canvas.create_rectangle( x0, y0, x1, y1, tags=[self.tag, "type=outline"], fill="white", ) # the label in the middle self.title_label = self.canvas.create_text( self.x, self.y, text=self.title, tags=[self.tag, "type=title"] ) for direction, edge in self.connections(): edge.move()
[docs] def edit(self): """Present a dialog for editing this step's parameters. Subclasses can override this. """ # Create the dialog if it doesn't exist if self.dialog is None: self.create_dialog() # After full creation, reset the dialog. This may do nothing, # or may layout the widgets, but can only be done after fully # creating the dialog. self.reset_dialog() # And resize the dialog to fit... self.fit_dialog() # And put it on-screen, the first time centered. If it contains # a subflowchart, save it so it can be restored on a 'Cancel' if self.tk_subflowchart is not None: self.tk_subflowchart.push() self.dialog.activate(geometry="centerscreenfirst")
[docs] def end_move(self, deltax, deltay): """End moving the node on the canvas. Parameters ---------- deltax : int The number of pixels to move in the x-direction. deltay : int The number of pixels to move in the y-direction. """ self.move(deltax, deltay) self._x0 = None self._y0 = None self._tmp = None
[docs] def fit_dialog(self): """Resize and fit the dialog to the current contents and the constraint of the window. """ self.logger.debug("Entering fit_dialog") frame = self["frame"] frame.update_idletasks() width = frame.winfo_width() height = frame.winfo_height() sw = frame.winfo_screenwidth() sh = frame.winfo_screenheight() self.logger.debug( " frame wxh = {} x {}, screen = {} x {}".format(width, height, sw, sh) ) mw = 0 mh = 0 if "notebook" in self: for tab in self["notebook"].tabs(): widget = frame.nametowidget(tab) widget.update_idletasks() self.logger.debug(" widget = {}".format(widget)) ww = widget.winfo_width() hh = widget.winfo_height() w = widget.winfo_reqwidth() h = widget.winfo_reqheight() self.logger.debug( " tab {} wxh = {} x {}, requested = {} x {}".format( tab, ww, hh, w, h ) ) if w > mw: mw = w if h > mh: mh = h if ww > width: width = ww if hh > height: height = hh # Need to do results again using the inside of the scrolled table.. if "results" in self: widget = self["results"].interior() self.logger.debug(" widget = {}".format(widget)) widget.update_idletasks() ww = widget.winfo_width() hh = widget.winfo_height() w = widget.winfo_reqwidth() h = widget.winfo_reqheight() self.logger.debug( " tab {} wxh = {} x {}, requested = {} x {}".format( tab, ww, hh, w, h ) ) if w > mw: mw = w if h > mh: mh = h if ww > width: width = ww if hh > height: height = hh else: mw = frame.winfo_reqwidth() mh = frame.winfo_reqheight() self.logger.debug(" frame requested = {} x {}".format(mw, mh)) if width < mw: width = mw width += 70 if width + 70 > 0.9 * sw: width = int(0.9 * sw) if height < mh: height = mh height += 70 if height > 0.9 * sh: height = int(0.9 * sh) self.dialog.geometry("{}x{}".format(width, height))
[docs] def from_flowchart(self, tk_flowchart=None, flowchart=None): """Recreate the graphics from the non-graphical flowchart. Only used in nodes that contain flowchart""" if self.tk_subflowchart is None or self.node.subflowchart is None: return self.tk_subflowchart.clear() # Add all the non-graphical nodes, making copies so that # when the flowchart is cleared our objects still exist translate = {} for node in self.node.subflowchart: extension = node.extension if extension is None: # Start node translate[node] = self.tk_subflowchart.get_node("1") else: new_node = copy.copy(node) self.logger.debug("creating {} node".format(extension)) plugin = self.tk_subflowchart.plugin_manager.get(extension) self.logger.debug(" plugin object: {}".format(plugin)) tk_node = plugin.create_tk_node( tk_flowchart=self.tk_subflowchart, canvas=self.tk_subflowchart.canvas, node=new_node, ) translate[node] = tk_node tk_node.from_flowchart() self.tk_subflowchart.graph.add_node(tk_node) tk_node.draw() # And the edges for edge in self.node.subflowchart.edges(): node1 = translate[edge.node1] node2 = translate[edge.node2] attr = {} for key in edge: if key not in ("node1", "node2"): attr[key] = edge[key] self.tk_subflowchart.add_edge(node1, node2, **attr)
[docs] def handle_dialog(self, result): """Do the right thing when the dialog is closed.""" if result is None or result == "Cancel": self.dialog.deactivate(result) # If there is a subflowchart, revert to the saved copy if self.tk_subflowchart is not None: self.tk_subflowchart.pop() # Reset the results widgets if they exist if self.results_widgets is not None: results = self.node.parameters["results"]["value"] self.logger.debug("Resetting results on Cancel") if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(" results dict\n---------") for key, item in results.items(): self.logger.debug(key) self.logger.debug( json.dumps(results[key], sort_keys=True, indent=3) ) for ( key, w_property, w_json, w_check, w_variable, w_table, w_column, w_units, ) in self.results_widgets: # noqa: E501 self.logger.debug(" key: {}".format(key)) w_variable.delete(0, tk.END) if w_table is not None: w_table.delete(0, tk.END) if w_column is not None: w_column.delete(0, tk.END) if key in results: tmp = results[key] self.logger.debug( " key dict\n---------\n" + json.dumps(tmp, sort_keys=True, indent=3) + "\n-----" ) if "variable" in tmp: self.tk_var[key].set(1) w_variable.insert(0, tmp["variable"]) else: self.tk_var[key].set(0) w_variable.insert(0, key.lower().replace(" ", "_")) if w_table is not None: if "table" in tmp: w_table.insert(0, tmp["table"]) w_column.insert(0, tmp["column"]) else: w_table.set("") w_column.insert(0, key.lower().replace("_", " ")) if w_property is not None: if "property" in tmp: self.tk_var["property " + key]["value"].set(1) else: self.tk_var["property " + key]["value"].set(0) if w_json is not None: if "json" in tmp: self.tk_var["json " + key]["value"].set(1) else: self.tk_var["json " + key]["value"].set(0) if w_units is not None: if "units" in tmp: w_units.set(tmp["units"]) else: self.logger.debug(" resetting widgets") self.tk_var[key].set(0) w_variable.insert(0, key.lower().replace(" ", "_")) if w_column is not None: w_column.insert(0, key.lower().replace("_", " ")) # Reset the parameters, if any if self.node.parameters is not None: self.node.parameters.reset_widgets() # Reset any keywords if "keywords" in self: self["keywords"].reset() # Reset the layout to make sure it is correct self.reset_dialog() elif result == "Help": self.help() elif result == "OK": self.dialog.deactivate(result) # Capture the parameters from the widgets if self.node.parameters is not None: self.node.parameters.set_from_widgets() # If there is a subflowchart, throw the saved copy away if self.tk_subflowchart is not None: self.tk_subflowchart.pop_and_discard() # Get what results to store, if the results tab exists if self.results_widgets is not None: # Shortcut for parameters P = self.node.parameters # and from the results tab... if "create tables" in P: if self.tk_var["create tables"].get(): P["create tables"].value = "yes" else: P["create tables"].value = "no" results = P["results"].value = {} for ( key, w_property, w_json, w_check, w_variable, w_table, w_column, w_units, ) in self.results_widgets: # noqa: E501 tmp = {} if self.tk_var[key].get(): tmp["variable"] = w_variable.get() if w_units is not None: tmp["units"] = w_units.get() if self.tk_var["json " + key].get(): tmp["json"] = True if w_table is not None: table = w_table.get() if table != "": tmp["table"] = table tmp["column"] = w_column.get() if w_units is not None: tmp["units"] = w_units.get() if w_property is not None: if self.tk_var["property " + key]["value"].get() == 1: tmp["property"] = self.tk_var["property " + key]["key"] elif "property" in tmp: del tmp["property"] if len(tmp) > 0: results[key] = tmp # And any keywords if "keywords" in self: P = self.node.parameters P["extra keywords"].value = self["keywords"].get_keywords() self["keywords"].keywords = P["extra keywords"].value else: self.dialog.deactivate(result) raise RuntimeError("Don't recognize dialog result '{}'".format(result))
[docs] def help(self): """Base class for presenting help, does nothing. Subclasses should override this. """ pass
[docs] def initialize_results(self): """Initialize the results if empty. When the GUI for the step is first created the `results` parameter is empty. However the default is to save properties to the database, so they need to be put into the `results` parameter. """ if self.node.parameters is None or "results" not in self.node.parameters: return results = self.node.parameters["results"].value if len(results) == 0: for key, entry in self.node.metadata["results"].items(): if "dimensionality" not in entry: continue if self.node.calculation is not None and "calculation" in entry: if self.node.calculation not in entry["calculation"]: continue if self.node.method is not None and "methods" in entry: if self.node.method not in entry["methods"]: continue if "property" in entry: results[key] = {"property": entry["property"]}
[docs] @staticmethod def is_expr(value): """Return whether the value is an expression or constant. Parameters ---------- value : str The value to test Returns ------- bool True for an expression, False otherwise. """ return len(value) > 0 and value[0] == "$"
[docs] def is_inside(self, x, y, halo=0): """Return a boolean indicating whether the point x, y is inside this node, using halo as a size around the point """ if x < self.x - self.w / 2 - halo: return False if x > self.x + self.w / 2 + halo: return False if y < self.y - self.h / 2 - halo: return False if y > self.y + self.h / 2 + halo: return False return True
[docs] def move(self, deltax, deltay): """Move the node on the canvas. Parameters ---------- deltax : int The number of pixels to move in the x-direction. deltay : int The number of pixels to move in the y-direction. """ if self._tmp is None: self._tmp = self.connections() self.x += deltax self.y += deltay self.canvas.move(self.tag, deltax, deltay) for connection in self._tmp: direction, edge = connection edge.move()
[docs] def next_anchor(self): """Return where the next node should be positioned. The default is <gap> below the 's' anchor point. """ return "s"
[docs] def remove_edge(self, edge): """Remove a given edge, or all edges if 'all' is given""" if isinstance(edge, str) and edge == "all": for direction, obj in self.connections(): self.remove_edge(obj) else: self.tk_flowchart.graph.remove_edge( edge.node1, edge.node2, edge.edge_type, edge.edge_subtype )
[docs] def reset_dialog(self, widget=None): """Reset the layout of the dialog as needed for the parameters. In this base class this does nothing. Override as needed in the subclasses derived from this class. """ pass
[docs] def right_click(self, event): """Respond to a right-click by posting the popup menu. This method provides a popup menu with a **delete** command. Subclasses should override or extend this as appropriate! The menu created in this base method is accessible in subclasses which should make it easy to override. """ if self.popup_menu is not None: self.popup_menu.destroy() self.popup_menu = tk.Menu(self.canvas, tearoff=0) self.popup_menu.add_command( label="Delete", command=lambda node=self: self.tk_flowchart.remove_node(node), ) if type(self) is seamm.tk_node.TkNode: self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
[docs] def setup_results(self): """Layout the results tab of the dialog""" if self.node.parameters is None or "results" not in self.node.parameters: return results = self.node.parameters["results"].value # Find what tables are in use. tables = set() for tmp in results.values(): if "table" in tmp: tables.add(tmp["table"]) self.node.tables = sorted(tables) tables.update(self.node.existing_tables()) self._tables = sorted(tables) del tables self.results_widgets = [] table = self["results"] table.clear() frame = table.interior() row = 0 for key, entry in self.node.metadata["results"].items(): if "dimensionality" not in entry: continue if self.node.calculation is not None and "calculation" in entry: if self.node.calculation not in entry["calculation"]: continue if self.node.method is not None and "methods" in entry: if self.node.method not in entry["methods"]: continue widgets = [] widgets.append(key) table.cell(row, 0, entry["description"]) # Property for DB. default is save if "property" in entry: var = self.tk_var["property " + key] = { "value": tk.IntVar(), "key": entry["property"], } if key in results and "property" in results[key]: var["value"].set(1) else: var["value"].set(0) w = ttk.Checkbutton(frame, variable=var["value"]) table.cell(row, 2, w) widgets.append(w) else: widgets.append(None) # JSON var = self.tk_var["json " + key] = tk.IntVar() if key in results and "json" in results[key]: var.set(1) else: var.set(0) w = ttk.Checkbutton(frame, variable=var) table.cell(row, 4, w) widgets.append(w) # variable var = self.tk_var[key] = tk.IntVar() var.set(0) w = ttk.Checkbutton(frame, variable=var) table.cell(row, 6, w) widgets.append(w) e = ttk.Entry(frame, width=15) e.insert(0, key.lower().replace(" ", "_")) table.cell(row, 7, e) widgets.append(e) if key in results: if "variable" in results[key]: var.set(1) e.delete(0, tk.END) e.insert(0, results[key]["variable"]) # table w = ttk.Combobox(frame, width=10, values=["", *self._tables]) table.cell(row, 9, w) widgets.append(w) w.bind("<<ComboboxSelected>>", self._table_cb) w.bind("<Return>", self._table_cb) w.bind("<FocusOut>", self._table_cb) e = ttk.Entry(frame, width=15) e.insert(0, key.lower().replace("_", " ")) table.cell(row, 10, e) widgets.append(e) if key in results: if "table" in results[key]: w.set(results[key]["table"]) e.delete(0, tk.END) e.insert(0, results[key]["column"]) # And units.... if "units" in entry and entry["units"] != "": units = entry["units"] w = ttk.Combobox(frame, width=10) widgets.append(w) table.cell(row, 11, w) w.config(values=[*default_units(units), ""]) w.set(units) if key in results and "units" in results[key]: w.set(results[key]["units"]) else: widgets.append(None) self.results_widgets.append(widgets) row += 1
[docs] def set_uuid(self): """Set the unique id of the node to a new uuid.""" self.node.set_uuid()
[docs] def to_dict(self): """Serialize to a dict""" data = { "x": self._x, "y": self._y, "w": self._w, "h": self._h, } return data
def _table_cb(self, event): "Update the list of tables as needed." table = event.widget.get() if table.strip() == "": return if table not in self._tables: self._tables.append(table) self._tables = sorted(self._tables) self.node.tables.append(table) self.node.tables.sort() for ( key, w_property, w_check, w_variable, w_table, w_column, w_units, ) in self.results_widgets: if w_table is not None: w_table.configure(values=["", *self._tables])
[docs] def update_flowchart(self, tk_flowchart=None, flowchart=None): """Update the nongraphical flowchart. Only used in nodes that contain flowcharts""" if self.tk_subflowchart is None or self.node.subflowchart is None: return # Make sure there is nothing in the flowchart self.node.subflowchart.clear(all=True) # Add all the non-graphical nodes, making copies so that # when the flowchart is cleared our objects still exist translate = {} for node in self.tk_subflowchart: translate[node] = self.node.subflowchart.add_node(copy.copy(node.node)) node.update_flowchart() # And the edges for edge in self.tk_subflowchart.edges(): attr = {} for key in edge: if key not in ("node1", "node2", "edge_type", "edge_subtype", "canvas"): attr[key] = edge[key] node1 = translate[edge.node1] node2 = translate[edge.node2] self.node.subflowchart.add_edge( node1, node2, edge.edge_type, edge.edge_subtype, **attr )
[docs] def undraw(self): """Remove all the visual components from the canvas.""" self.canvas.delete(self.tag)