class TestPattern(SheetPanel): sheet_type = GeneratorSheet dock = param.Boolean(False) edit_sheet = param.ObjectSelector(doc=""" Sheet for which to edit pattern properties.""") plastic = param.Boolean(default=False, doc=""" Whether to enable plasticity during presentation.""") duration = param.Number(default=1.0, softbounds=(0.0, 10.0), doc=""" How long to run the simulation for each presentation.""") Present = tk.Button(doc="""Present this pattern to the simulation.""") pattern_generator = param.ClassSelector(default=Constant(), class_=PatternGenerator, doc=""" Type of pattern to present. Each type has various parameters that can be changed.""" ) def __init__(self, master, plotgroup=None, **params): plotgroup = plotgroup or TestPatternPlotGroup() super(TestPattern, self).__init__(master, plotgroup, **params) self.auto_refresh = True self.plotcommand_frame.pack_forget() for name in ['pre_plot_hooks', 'plot_hooks', 'Fwd', 'Back']: self.hide_param(name) edit_sheet_param = self.get_parameter_object('edit_sheet') edit_sheet_param.objects = self.plotgroup.sheets() self.pg_control_pane = Frame(self) #,bd=1,relief="sunken") self.pg_control_pane.pack(side="top", expand='yes', fill='x') self.params_frame = tk.ParametersFrame( self.pg_control_pane, parameterized_object=self.pattern_generator, on_modify=self.conditional_refresh, msg_handler=master.status) self.params_frame.hide_param('Close') self.params_frame.hide_param('Refresh') # CEB: 'new_default=True' is temporary so that the current # behavior is the same as before; shoudl make None the # default and mean 'apply to all sheets'. self.pack_param('edit_sheet', parent=self.pg_control_pane, on_modify=self.switch_sheet, widget_options={ 'new_default': True, 'sort_fn_args': { 'cmp': lambda x, y: cmp(-x.precedence, -y.precedence) } }) self.pack_param('pattern_generator', parent=self.pg_control_pane, on_modify=self.change_pattern_generator, side="top") present_frame = Frame(self) present_frame.pack(side='bottom') self.pack_param('plastic', side='bottom', parent=present_frame) self.params_frame.pack(side='bottom', expand='yes', fill='x') self.pack_param('duration', parent=present_frame, side='left') self.pack_param('Present', parent=present_frame, on_set=self.present_pattern, side="right") def setup_plotgroup(self): super(TestPattern, self).setup_plotgroup() # CB: could copy the sheets instead (deleting connections etc) self.plotgroup._sheets = [ GeneratorSheet(name=gs.name, nominal_bounds=gs.nominal_bounds, nominal_density=gs.nominal_density) for gs in topo.sim.objects(GeneratorSheet).values() ] self.plotgroup._set_name("Test Pattern") def switch_sheet(self): if self.edit_sheet is not None: self.pattern_generator = self.edit_sheet.input_generator self.change_pattern_generator() def change_pattern_generator(self): """ Set the current PatternGenerator to the one selected and get the ParametersFrameWithApply to draw the relevant widgets """ # CEBALERT: if pattern generator is set to None, there will be # an error. Need to handle None in the appropriate place # (presumably tk.py). self.params_frame.set_PO(self.pattern_generator) for sheet in self.plotgroup.sheets(): if sheet == self.edit_sheet: sheet.set_input_generator(self.pattern_generator) self.conditional_refresh() def refresh(self, update=True): """ Simply update the plots: skip all handling of history. """ self.refresh_plots(update) def present_pattern(self): """ Move the user created patterns into the GeneratorSheets, run for the specified length of time, then restore the original patterns. """ input_dict = dict([(sheet.name,sheet.input_generator) \ for sheet in self.plotgroup.sheets()]) pattern_present(inputs=input_dict, durations=[self.duration], plastic=self.plastic, overwrite_previous=False, install_sheetview=True, restore_state=True) topo.guimain.auto_refresh(update=False)
class PlotGroupPanel(tk.TkParameterized, Frame): __abstract = True dock = param.Boolean(default=False, doc="on console or not") maximum_plot_history_length = param.Integer(default=20, bounds=(-1, None), doc=""" Value of maximum plots history length. Larger number means a longer history is kept and hence more memory is used. A length of -1 means keep all.""" ) # Default size for images used on buttons button_image_size = (20, 20) Refresh = tk.Button(image_path="tkgui/icons/redo-small.png", size=button_image_size, doc=""" Refresh the current plot (i.e. force the current plot to be regenerated by executing pre_plot_hooks and plot_hooks).""") Redraw = tk.Button( image_path="tkgui/icons/redo-small.png", size=button_image_size, doc= """Redraw the plot from existing data (i.e. execute plot_hooks only).""" ) Enlarge = tk.Button( image_path="tkgui/icons/viewmag+_2.2.png", size=button_image_size, doc="""Increase the displayed size of the current plots by about 20%.""" ) Reduce = tk.Button(image_path="tkgui/icons/viewmag-_2.1.png", size=button_image_size, doc=""" Reduce the displayed size of the current plots by about 20%. A minimum size that preserves at least one pixel per unit is enforced, to ensure that no data is lost when displaying.""") Fwd = tk.Button(image_path="tkgui/icons/forward-2.0.png", size=button_image_size, doc=""" Move forward through the history of all the plots shown in this window.""" ) Back = tk.Button(image_path="tkgui/icons/back-2.0.png", size=button_image_size, doc=""" Move backward through the history of all the plots shown in this window. When showing a historical plot, some functions will be disabled, because the original data is no longer available.""") gui_desired_maximum_plot_height = param.Integer(default=150, bounds=(0, None), doc=""" Value to provide for PlotGroup.desired_maximum_plot_height for PlotGroups opened by the GUI. Determines the initial, default scaling for the PlotGroup.""") # CB: is there a better way than using a property? def get_plotgroup(self): return self._extraPO def set_plotgroup(self, new_pg): self.change_PO(new_pg) plotgroup = property(get_plotgroup, set_plotgroup) def __init__(self, master, plotgroup, **params): """ If your parameter should be available in history, add its name to the params_in_history list, otherwise it will be disabled in historical views. """ tk.TkParameterized.__init__(self, master, extraPO=plotgroup, msg_handler=master.status, **params) Frame.__init__(self, master.content) self.parent = master #CEBALERT self.setup_plotgroup() self.canvases = [] self.plot_labels = [] ### JCALERT! Figure out why we need that! self._num_labels = 0 self.plotgroups_history = [] self.history_index = 0 self.params_in_history = [] # parameters valid to adjust in history # Factor for reducing or enlarging the Plots (where 1.2 = 20% change) self.zoom_factor = 1.2 # CEBALERT: rename these frames self.control_frame_1 = Frame(master.noscroll) self.control_frame_1.pack(side=TOP, expand=NO, fill=X) self.control_frame_2 = Frame(master.noscroll) self.control_frame_2.pack(side=TOP, expand=NO, fill=X) self.plot_frame = Tkinter.LabelFrame(self, text=self.plotgroup.name) self.plot_frame.pack(side=TOP, expand=YES, fill=BOTH) #,padx=5,pady=5) # CB: why did I need a new frame after switching to 8.5? # I've forgotten what i changed. self.plot_container = Tkinter.Frame(self.plot_frame) self.plot_container.pack(anchor="center") # Label does have a wraplength option...but it's in screen # units. Surely tk has a function to convert between # text and screen units? no_plot_note_text = """ Press Refresh on the pre-plot hooks to generate the plot, after modifying the hooks below if necessary. Note that Refreshing may take some time. Many hooks accept 'display=True' so that the progress can be viewed in an open Activity window, e.g. for debugging. """ self.no_plot_note = Label(self.plot_container, text=no_plot_note_text, justify="center", wraplength=350) self.no_plot_note_enabled = False self.control_frame_3 = Frame(master.noscroll_bottom) self.control_frame_3.pack(side=TOP, expand=NO, fill=X) self.control_frame_4 = Frame(self) self.control_frame_4.pack(side=TOP, expand=NO, fill=NONE) self.updatecommand_frame = Frame(self.control_frame_3) self.updatecommand_frame.pack(side=TOP, expand=YES, fill=X) self.plotcommand_frame = Frame(self.control_frame_3) self.plotcommand_frame.pack(side=TOP, expand=YES, fill=X) # CEBALERT: replace self.messageBar = self.parent.status self.pack_param('pre_plot_hooks', parent=self.updatecommand_frame, expand='yes', fill='x', side='left') self.pack_param('Refresh', parent=self.updatecommand_frame, on_set=self.refresh, side='right') self.params_in_history.append('Refresh') self.pack_param('plot_hooks', parent=self.plotcommand_frame, expand='yes', fill='x', side='left') # CEBALERT: should disable unless data exists. self.pack_param('Redraw', parent=self.plotcommand_frame, on_set=self.redraw_plots, side='right') self.pack_param('Enlarge', parent=self.control_frame_1, on_set=self.enlarge_plots, side=LEFT) self.params_in_history.append( 'Enlarge') # CEBNOTE: while it's a GUI op self.pack_param('Reduce', parent=self.control_frame_1, on_set=self.reduce_plots, side=LEFT) self.params_in_history.append('Reduce') if topo.tkgui.TK_SUPPORTS_DOCK: self.pack_param("dock", parent=self.control_frame_1, on_set=self.set_dock, side=LEFT) # Don't need to add these two to params_in_history because their # availability is controlled separately (determined by what's # in the history) self.pack_param('Back', parent=self.control_frame_2, on_set=lambda x=-1: self.navigate_pg_history(x), side=LEFT) self.pack_param('Fwd', parent=self.control_frame_2, on_set=lambda x=+1: self.navigate_pg_history(x), side=LEFT) #################### RIGHT-CLICK MENU STUFF #################### ### Right-click menu for canvases; subclasses can add cascades ### or insert commands on the existing cascades. self._canvas_menu = tk.Menu(self, tearoff=0) #self.context_menu self._unit_menu = tk.Menu(self._canvas_menu, tearoff=0) self._canvas_menu.add_cascade(menu=self._unit_menu, state=DISABLED, indexname='unit_menu') self._canvas_menu.add_separator() # CEBALERT: scheme for enabling/disabling menu items ('disable # items hack') needs to be generalized. What we have now is # just a mechanism to disable/enable cfs/rfs plots as # necessary. Hack includes the attribute below as well as # other items marked 'disable items hack'. # (Note that tk 8.5 has better handling of state switching # (using flags for each state, I think), so presumably this # can be cleaned up easily.) self._unit_menu_updaters = {} self._sheet_menu = tk.Menu(self._canvas_menu, tearoff=0) self._canvas_menu.add_cascade(menu=self._sheet_menu, state=DISABLED, indexname='sheet_menu') self._canvas_menu.add_separator() self.update_plot_frame(plots=False) ################################################################# # CB: don't forget to include ctrl-q # import __main__; __main__.__dict__['qqq']=self def set_dock(self): if self.dock: topo.guimain.some_area.consume(self.parent) self.refresh_title() else: topo.guimain.some_area.eject(self.parent) self.refresh_title() def setup_plotgroup(self): """ Perform any necessary initialization of the plotgroup. Subclasses can use this to set Parameters on their PlotGroups. """ self.plotgroup.desired_maximum_plot_height = self.gui_desired_maximum_plot_height def __process_canvas_event(self, event, func): """ Return a dictionary containing the event itself, and, if the event occurs on a plot of a sheet, store the plot and the coordinates ((r,c) and (x,y) for the cell center) on the sheet. Then, call func. """ # CB: I want this to be called for all the canvas events - see # ALERT by canvas button bindings. Surely can do better than # just passing func through. plot = event.widget.plot event_info = { 'event': event } # store event in case more info needed elsewhere # Later functions assume that if event_info does not contain # 'plot', then the event did not occur on a plot of a sheet. if plot.plot_src_name is not '': plot_width, plot_height = plot.bitmap.width(), plot.bitmap.height() if 0 <= event.x < plot_width and 0 <= event.y < plot_height: left, bottom, right, top = plot.plot_bounding_box.lbrt() # float() to avoid integer division x = (right - left) * float(event.x) / plot_width + left y = top - (top - bottom) * float(event.y) / plot_height r, c = topo.sim[plot.plot_src_name].sheet2matrixidx(x, y) event_info['plot'] = plot event_info['coords'] = [(r, c), (x, y)] func(event_info) def _canvas_right_click(self, event_info, show_menu=True): """ Update labels on right-click menu and popup the menu, plus store the event info for access by any menu commands that require it. If show_menu is False, popup menu is not displayed (in case subclasses wish to add extra menu items first). """ if 'plot' in event_info: plot = event_info['plot'] self._canvas_menu.entryconfig("sheet_menu", label="Combined plot: %s %s" % (plot.plot_src_name, plot.name), state=NORMAL) (r, c), (x, y) = event_info['coords'] self._canvas_menu.entryconfig( "unit_menu", label="Single unit:(% 3d,% 3d) Coord:(% 2.2f,% 2.2f)" % (r, c, x, y), state=NORMAL) self._right_click_info = event_info # CB: part of disable items hack for v in self._unit_menu_updaters.values(): v(plot) if show_menu: self._canvas_menu.tk_popup(event_info['event'].x_root, event_info['event'].y_root) def _update_dynamic_info(self, event_info): """ Update dynamic information. """ if 'plot' in event_info: plot = event_info['plot'] (r, c), (x, y) = event_info['coords'] location_string = "%s Unit:(% 3d,% 3d) Coord:(% 2.2f,% 2.2f)" % ( plot.plot_src_name, r, c, x, y) # CB: isn't there a nicer way to allow more info to be added? self.messageBar.dynamicinfo( self._dynamic_info_string(event_info, location_string)) else: self.messageBar.dynamicinfo('') def _dynamic_info_string(self, event_info, x): """ Subclasses can override to add extra relevant information. """ return x # rename (not specific to plot_frame) # document, and make display_* methods semi-private methods def update_plot_frame(self, plots=True, labels=True, geom=False): """ set geom True for any action that user would expect to lose his/her manual window size (e.g. pressing enlarge button) """ if plots: self.plotgroup.scale_images() self.display_plots() if labels: self.display_labels() self.refresh_title() if len(self.canvases) == 0: # CEB: check that pack's ok here self.no_plot_note.grid(row=1, column=0, sticky='nsew') self.no_plot_note_enabled = True self.representations['Enlarge']['widget']['state'] = DISABLED self.representations['Reduce']['widget']['state'] = DISABLED elif self.no_plot_note_enabled: self.no_plot_note.grid_forget() self.no_plot_note_enabled = False self.representations['Enlarge']['widget']['state'] = NORMAL self.representations['Reduce']['widget']['state'] = NORMAL self.__update_widgets_for_history() # have a general update_widgets method instead (that calls # update_widgets_for_history; can it also include # enlarge/reduce alterations?) # CBALERT: problem when docked: this event isn't being caught, # ie it doesn't end up going to the right place... (i.e. no # scrollbars when docked). #self.event_generate("<<SizeRight>>") self.parent.sizeright() if geom: try: self.parent.geometry('') except TclError: pass @with_busy_cursor def refresh_plots(self): """ Call plotgroup's make_plots with update=True (i.e. run pre_plot_hooks and plot_hooks), then display the result. """ self.plotgroup.make_plots(update=True) self.update_plot_frame() self.add_to_plotgroups_history() @with_busy_cursor def redraw_plots(self): """ Call plotgroup's make_plots with update=False (i.e. run only plot_hooks, not pre_plot_hooks), then display the result. """ self.plotgroup.make_plots(update=False) self.update_plot_frame(labels=False) def rescale_plots(self): """ Rescale the existing plots, without calling either the plot_hooks or the pre_plot_hooks, then display the result. """ self.plotgroup.scale_images() self.update_plot_frame(labels=False, geom=True) def refresh(self, update=True): """ Main steps for generating plots in the Frame. # if update is True, the SheetViews are re-generated """ # if we've been looking in the history, now need to return to the "current time" # plotgroup (but copy it: don't update the old one, which is a record of the previous state) if self.history_index != 0: self._switch_plotgroup(copy.copy(self.plotgroups_history[-1])) self.history_index = 0 if update: self.refresh_plots() else: self.redraw_plots() ### JABALERT: Can we make it simpler to make plots be put onto multiple lines? # (because this is just the smallest change I cpuld think of to # support row precedence, without altering the existing code!) def _determine_layout_of_plots(self, plots): """Calculate self._rows and self._cols, together giving the grid position of each plot.""" distinct_precedences = sorted(set([p.row_precedence for p in plots])) # 2*i because labels will occupy odd rows precedence2row = dict([(precedence, 2 * i) for precedence, i in zip( distinct_precedences, range(len(distinct_precedences)))]) # CB: a 2d array might have been clearer... self._rows = [precedence2row[p.row_precedence] for p in plots] self._cols = [] row_counts = dict([(row, 0) for row in self._rows]) for row in self._rows: self._cols.append(row_counts[row]) row_counts[row] += 1 # CEBALERT: this method needs cleaning, along with its versions in subclasses. def display_plots(self): """ This function should be redefined in subclasses for interesting things such as 2D grids. """ plots = self.plotgroup.plots self._determine_layout_of_plots(plots) self.zoomed_images = [ ImageTk.PhotoImage(p.bitmap.image) for p in plots ] new_sizes = [(str(zi.width()), str(zi.height())) for zi in self.zoomed_images] old_sizes = [(canvas['width'], canvas['height']) for canvas in self.canvases] # If the number of canvases or their sizes has changed, then # create a new set of canvases. If the new images will fit into the # old canvases, reuse them (prevents flicker) if len(self.zoomed_images) != len(self.canvases) or \ new_sizes != old_sizes: # Need new canvases... old_canvases = self.canvases self.canvases = [ Canvas(self.plot_container, width=image.width(), height=image.height(), borderwidth=1, highlightthickness=0, relief='groove') for image in self.zoomed_images ] for i, image, canvas in zip(range(len(self.zoomed_images)), self.zoomed_images, self.canvases): canvas.create_image(1, 1, anchor="nw", image=image) canvas.grid(row=self._rows[i], column=self._cols[i], padx=5) for c in old_canvases: c.grid_forget() else: # Don't need new canvases... for i, image, canvas in zip(range(len(self.zoomed_images)), self.zoomed_images, self.canvases): canvas.create_image(1, 1, anchor="nw", image=image) canvas.grid(row=self._rows[i], column=self._cols[i], padx=5) self._add_canvas_bindings() def _add_canvas_bindings(self): ### plotting over; bind events to each canvas for plot, canvas in zip(self.plotgroup.plots, self.canvases): # Store the corresponding plot with each canvas so that the # plot information (e.g. scale_factor) will be available # for the right_click menu. canvas.plot = plot # CEBALERT: I want process_canvas_event to be called for # all of these bindings, with an additional method also # called to do something specific to the action. I'm sure # python has something that lets this be done in a clearer # way. canvas.bind('<<right-click>>',lambda event: \ self.__process_canvas_event(event,self._canvas_right_click)) canvas.bind('<Motion>',lambda event: \ self.__process_canvas_event(event,self._update_dynamic_info)) canvas.bind('<Leave>',lambda event: \ self.__process_canvas_event(event,self._update_dynamic_info)) # When user has a menu up, it's often natural to click # elsewhere to make the menu disappear. Need to update the # dynamic information in that case. (Happens on OS X # anyway, but needed on Win and linux.) canvas.bind('<Button-1>',lambda event: \ self.__process_canvas_event(event,self._update_dynamic_info)) def display_labels(self): """ This function should be redefined by subclasses to match any changes made to display__plots(). Depending on the situation, it may be useful to make this function a stub, and display the labels at the same time the images are displayed. """ if len(self.canvases) == 0: pass elif self._num_labels != len(self.canvases): old_labels = self.plot_labels self.plot_labels = [ Label(self.plot_container, text=each) for each in self.plotgroup.labels ] for i in range(len(self.plot_labels)): self.plot_labels[i].grid(row=self._rows[i] + 1, column=self._cols[i], sticky=NSEW) for l in old_labels: l.grid_forget() self._num_labels = len(self.canvases) else: # Same number of labels; reuse to avoid flickering. for i in range(len(self.plot_labels)): self.plot_labels[i].configure(text=self.plotgroup.labels[i]) # CEBERRORALERT (minor): if no plot's displayed and I click # enlarge, then the enlarge button gets disabled. If I then press # refresh to get a plot, I can't enlarge it because the button's # disabled. Probably need to reset button status if the plots # change. def reduce_plots(self): """Function called by widget to reduce the plot size, when possible.""" if (not self.plotgroup.scale_images(1.0 / self.zoom_factor)): self.representations['Reduce']['widget']['state'] = DISABLED self.representations['Enlarge']['widget']['state'] = NORMAL self.update_plot_frame(labels=False, geom=True) def enlarge_plots(self): """Function called by widget to increase the plot size, when possible.""" if (not self.plotgroup.scale_images(self.zoom_factor)): self.representations['Enlarge']['widget']['state'] = DISABLED self.representations['Reduce']['widget']['state'] = NORMAL self.update_plot_frame(labels=False, geom=True) ###################################################################### ### HISTORY METHODS # CEBERRORALERT: history grows and grows! Consider what happens when # a window's open with auto-refresh and many plots are generated # (e.g. measure_rfs). And plotgroups might be much bigger than they # need to be. # CEBALERT: in a history research, a disabled widget does not display # up-to-date information (e.g. normalize checkbutton doesn't change). def add_to_plotgroups_history(self): """ If there are plots on display, and we're not doing a history research, the plotgroup is stored in the history depending on the parameter maximum_plot_history_length. If this parameter is -1, history is unlimited (except by your machine memory!!). If it is 0, then no history is saved. If it is N, then up to N plots are saved in history. """ if self.history_index == 0 and not len(self.canvases) == 0: # Only keep history length if len(self.plotgroups_history) > self.maximum_plot_history_length: try: del self.plotgroups_history[0] except IndexError: pass if self.maximum_plot_history_length > 0: self.plotgroups_history.append(copy.copy(self.plotgroup)) self.__update_widgets_for_history() def __set_widget_state(self, widget, state): # sets the widget's state to state, unless state=='normal' # and the widget's current state is 'readonly', in which # case readonly is preserved. # If a widget state was set to 'disabled' deliberately, this # will have the unwanted effect of enabling that widget. # Surely there's a better way than this! # (Probably the history stuff should store the old state # on the widget somewhere. That would also eliminate the # combobox-specific hack.) # CEBALERT: I guess some widgets don't have state? try: current_state = widget.configure('state')[ 3] # pyflakes:ignore (try/except test) except TclError: return ### hack to deal with combobox: see tkparameterizedobject's ### create_selector_widget(). if state == 'normal': if hasattr(widget, '_readonly_'): state = 'readonly' ########################################################### widget.configure(state=state) def __update_widgets_for_history(self): """ The plotgroup's non-history widgets are all irrelevant when the plotgroup's from history. """ if self.history_index != 0: state = 'disabled' else: state = 'normal' widgets_to_update = [ self.representations[p_name]['widget'] for p_name in self.representations if p_name not in self.params_in_history ] for widget in widgets_to_update: self.__set_widget_state(widget, state) self.__update_history_buttons() def __update_history_buttons(self): """ Enable/disable the back and forward buttons depending on where we are in a history research. """ space_back = len(self.plotgroups_history) + self.history_index - 1 space_fwd = -self.history_index back_button = self.representations['Back']['widget'] forward_button = self.representations['Fwd']['widget'] if space_back > 0: back_button['state'] = 'normal' else: back_button['state'] = 'disabled' if space_fwd > 0: forward_button['state'] = 'normal' else: forward_button['state'] = 'disabled' # JLENHANCEMENT: It would be nice to be able to scroll back through many # iterations. Could put in a box for entering either the iteration # number you want to view, or perhaps how many you want to jump... def navigate_pg_history(self, steps): self.history_index += steps self._switch_plotgroup( self.plotgroups_history[len(self.plotgroups_history) + self.history_index - 1]) self.update_plot_frame() ###################################################################### def _switch_plotgroup(self, newpg): """ Switch to a different plotgroup, e.g. one from the history buffer. Preserves some attributes from the current plotgroup that can apply across history, but leaves the others as-is. """ oldpg = self.plotgroup newpg.desired_maximum_plot_height = oldpg.desired_maximum_plot_height newpg.sheet_coords = oldpg.sheet_coords newpg.integer_scaling = oldpg.integer_scaling self.plotgroup = newpg ########################################################### def _plot_title(self): """ Provide a string describing the current set of plots. Override in subclasses to provide more information. """ return "%s at time %s" % (self.plotgroup.name, topo.sim.timestr(self.plotgroup.time)) # rename to refresh_titles def refresh_title(self): """ Set Window title and plot frame's title. """ title = self._plot_title() self.plot_frame.configure(text=title) self.parent.title(str(topo.sim.name) + ": " + title) # JABALERT: Used to say .replace(" at time ","/"); was there a reason? def destroy(self): """overrides toplevel destroy, adding removal from autorefresh panels""" if self in topo.guimain.auto_refresh_panels: topo.guimain.auto_refresh_panels.remove(self) Frame.destroy(self)