class EnumFrame(data_frame.DataFrame): '''Used for enumerator nodes. When clicked, creates a dropdown box of all available enumerator options.''' option_cache = None sel_menu = None def __init__(self, *args, **kwargs): kwargs.update(relief='flat', bd=0, highlightthickness=0) data_frame.DataFrame.__init__(self, *args, **kwargs) try: sel_index = self.node.get_index() except Exception: sel_index = -1 # make the widgets self.content = tk.Frame(self, relief='flat', bd=0) self.display_comment() self.title_label = tk.Label( self.content, text=self.gui_name, justify='left', anchor='w', width=self.title_size, disabledforeground=self.text_disabled_color) self.sel_menu = ScrollMenu(self.content, f_widget_parent=self, menu_width=self.widget_width, sel_index=sel_index, max_index=self.desc.get('ENTRIES', 0) - 1, disabled=self.disabled, default_text="<INVALID>", option_getter=self.get_options, callback=self.select_option) if self.gui_name != '': self.title_label.pack(side="left", fill="x") self.content.pack(fill="x", expand=True) self.sel_menu.pack(side="left", fill="x") self.populate() self.pose_fields() self._initialized = True @property def widget_width(self): desc = self.desc width = desc.get('WIDGET_WIDTH', self.enum_menu_width) if width <= 0: for s in self.get_options().values(): width = max(width, len(s)) return width def apply_style(self, seen=None): self.sel_menu.menu_width = self.widget_width data_frame.DataFrame.apply_style(self, seen) def unload_node_data(self): field_widget.FieldWidget.unload_node_data(self) self.sel_menu.update_label(" ") def set_disabled(self, disable=True): disable = disable or not self.editable if self.node is None and not disable: return if bool(disable) != self.disabled and getattr(self, "sel_menu", None): self.sel_menu.set_disabled(disable) data_frame.DataFrame.set_disabled(self, disable) def flush(self): pass def edit_apply(self=None, *, edit_state, undo=True): state = edit_state w, node = field_widget.FieldWidget.get_widget_and_node( nodepath=state.nodepath, tag_window=state.tag_window) if undo: node.data = state.undo_node else: node.data = state.redo_node if w is not None: try: if w.desc is not state.desc: return try: w.sel_menu.sel_index = node.get_index() except Exception: # option doesnt exist, so make the displayed one blank w.sel_menu.sel_index = -1 w.needs_flushing = False w.sel_menu.update_label() w.set_edited() except Exception: print(format_exc()) def edit_create(self, **kwargs): # add own stuff kwargs.update(sel_index=self.sel_menu.sel_index, max_index=self.sel_menu.max_index) field_widget.FieldWidget.edit_create(self, **kwargs) def get_options(self, opt_index=None): ''' Returns a list of the option strings sorted by option index. ''' if self.option_cache is None or opt_index is not None: return self.generate_options(opt_index) if opt_index is None: return self.option_cache elif opt_index == e_c.ACTIVE_ENUM_NAME: opt_index = self.sel_menu.sel_index return self.option_cache.get(opt_index, None) def generate_options(self, opt_index=None): desc = self.desc options = {} option_count = desc.get('ENTRIES', 0) options_to_generate = range(option_count) if opt_index is not None: options_to_generate = ((opt_index, ) if opt_index in options_to_generate else ()) # sort the options by value(values are integers) use_gui_names = self.use_gui_names for i in options_to_generate: opt = desc[i] if use_gui_names and 'GUI_NAME' in opt: options[i] = opt['GUI_NAME'] else: options[i] = opt.get('NAME', '<UNNAMED %s>' % i)\ .replace('_', ' ') if opt_index is None: self.options_sane = True self.option_cache = options if self.sel_menu is not None: self.sel_menu.options_menu_sane = False self.sel_menu.max_index = option_count - 1 return options return options.get(opt_index, None) def reload(self): try: if self.disabled == self.sel_menu.disabled: pass elif self.disabled: self.sel_menu.disable() else: self.sel_menu.enable() for w in (self, self.content, self.sel_menu, self.title_label): w.tooltip_string = self.desc.get('TOOLTIP') options = self.get_options() option_count = len(options) if not option_count: self.sel_menu.sel_index = -1 self.sel_menu.max_index = -1 return try: curr_index = self.node.get_index() except Exception: curr_index = -1 self.sel_menu.sel_index = curr_index self.sel_menu.max_index = option_count - 1 self.sel_menu.update_label() except Exception: print(format_exc()) populate = reload def pose_fields(self): pass def select_option(self, opt_index=None): if None in (self.parent, self.node): return node = self.node curr_index = self.sel_menu.sel_index if (opt_index is None or opt_index < 0 or opt_index > self.sel_menu.max_index): print("Invalid option index '%s'" % opt_index) return self.sel_menu.sel_index = opt_index undo_node = self.node.data self.node.set_to(opt_index) # make an edit state if undo_node != self.node.data: self.set_edited() self.edit_create(undo_node=undo_node, redo_node=self.node.data) self.sel_menu.update_label()
class ArrayFrame(container_frame.ContainerFrame): '''Used for array nodes. Displays a single element in the ArrayBlock represented by it, and contains a combobox for selecting which array element is displayed.''' sel_index = -1 sel_menu = None populated = False option_cache = None options_sane = False def __init__(self, *args, **kwargs): kwargs.update(relief='flat', bd=0, highlightthickness=0, bg=self.default_bg_color) field_widget.FieldWidget.__init__(self, *args, **kwargs) tk.Frame.__init__(self, *args, **e_c.fix_kwargs(**kwargs)) show_frame = bool( kwargs.pop('show_frame', not self.blocks_start_hidden)) if self.is_empty and self.hide_if_blank: show_frame = False self.show = tk.BooleanVar() self.show.set(show_frame) self.options_sane = False node_len = 0 try: node_len = len(self.node) except Exception: pass self.sel_index = (node_len > 0) - 1 # make the title, element menu, and all the buttons self.controls = tk.Frame(self, relief='raised', bd=self.frame_depth) self.title = title = tk.Frame(self.controls, relief='flat', bd=0) self.buttons = buttons = tk.Frame(self.controls, relief='flat', bd=0) toggle_text = '-' if show_frame else '+' self.title_label = tk.Label( title, text=self.gui_name, justify='left', anchor='w', width=self.title_size, font=self.get_font("frame_title"), disabledforeground=self.text_disabled_color) self.title_label.font_type = "frame_title" self.show_btn = ttk.Checkbutton(title, width=3, text=toggle_text, command=self.toggle_visible, style='ShowButton.TButton') self.sel_menu = ScrollMenu(title, f_widget_parent=self, sel_index=self.sel_index, max_index=node_len - 1, option_getter=self.get_options, callback=self.select_option) self.shift_up_btn = ttk.Button(title, width=7, text='Shift ▲', command=self.shift_entry_up) self.shift_down_btn = ttk.Button(buttons, width=7, text='Shift ▼', command=self.shift_entry_down) self.add_btn = ttk.Button(buttons, width=4, text='Add', command=self.add_entry) self.insert_btn = ttk.Button(buttons, width=6, text='Insert', command=self.insert_entry) self.duplicate_btn = ttk.Button(buttons, width=9, text='Duplicate', command=self.duplicate_entry) self.delete_btn = ttk.Button(buttons, width=6, text='Delete', command=self.delete_entry) self.delete_all_btn = ttk.Button(buttons, width=10, text='Delete all', command=self.delete_all_entries) self.import_btn = ttk.Button(buttons, width=6, text='Import', command=self.import_node) self.export_btn = ttk.Button(buttons, width=6, text='Export', command=self.export_node) # pack the title, menu, and all the buttons for w in (self.shift_down_btn, self.export_btn, self.import_btn, self.delete_all_btn, self.delete_btn, self.duplicate_btn, self.insert_btn, self.add_btn): w.pack(side="right", padx=(0, 4), pady=(2, 2)) self.show_btn.pack(side="left") if self.gui_name != '': self.title_label.pack(side="left", fill="x", expand=True) self.sel_menu.pack(side="left", fill="x", expand=True, padx=(0, 4)) self.shift_up_btn.pack(side="right", padx=(0, 1), pady=(2, 2)) self.title.pack(fill="x", expand=True, padx=0) self.buttons.pack(fill="x", expand=True, padx=0) self.controls.pack(fill="x", expand=True, padx=0) self.populate() self._initialized = True @property def is_empty(self): if getattr(self, "node", None) is None: return True return len(self.node) == 0 def load_node_data(self, parent, node, attr_index, desc=None): field_widget.FieldWidget.load_node_data(self, parent, node, attr_index, desc) sub_node = attr_index = None if self.node: attr_index = self.sel_index if attr_index in range(len(self.node)): sub_node = self.node[attr_index] else: attr_index = len(self.node) - 1 if attr_index < 0: attr_index = None if self.sel_menu is not None: self.options_sane = self.sel_menu.options_menu_sane = False for wid in self.f_widgets: # there must be only one entry in self.f_widgets w = self.f_widgets[wid] if w.load_node_data(self.node, sub_node, attr_index): return True return False def unload_node_data(self): self.sel_menu.update_label(" ") container_frame.ContainerFrame.unload_node_data(self) def set_disabled(self, disable=True): disable = disable or not self.editable if self.node is None and not disable: return if getattr(self, "sel_menu", None): self.sel_menu.set_disabled(disable) if bool(disable) == self.disabled: pass elif not disable: self.set_all_buttons_disabled(False) self.disable_unusable_buttons() else: new_state = tk.DISABLED if disable else tk.NORMAL for w in (self.shift_up_btn, self.shift_down_btn, self.add_btn, self.insert_btn, self.duplicate_btn, self.delete_btn, self.delete_all_btn): if w: w.config(state=new_state) container_frame.ContainerFrame.set_disabled(self, disable) def apply_style(self, seen=None): container_frame.ContainerFrame.apply_style(self, seen) self.controls.config(bd=self.frame_depth, bg=self.frame_bg_color) self.title.config(bg=self.frame_bg_color) self.title_label.config(bg=self.frame_bg_color) self.buttons.config(bg=self.frame_bg_color) #if self.show.get(): # self.pose_fields() def destroy(self): # These will linger and take up RAM, even if the widget is destroyed. # Need to remove the references manually self.option_cache = None container_frame.ContainerFrame.destroy(self) def export_node(self): try: # pass call to the export_node method of the array entry's widget w = self.f_widgets[self.f_widget_ids[0]] except Exception: return w.export_node() def import_node(self): try: # pass call to the import_node method of the array entry's widget w = self.f_widgets[self.f_widget_ids[0]] except Exception: return w.import_node() def get_options(self, opt_index=None): ''' Returns a list of the option strings sorted by option index. ''' if (self.option_cache is None or not self.options_sane or opt_index is not None): result = self.generate_options(opt_index) if opt_index is not None: return result if opt_index is None: return self.option_cache elif opt_index == e_c.ACTIVE_ENUM_NAME: opt_index = self.sel_index if opt_index < 0: opt_index = -1 return self.option_cache.get(opt_index) def generate_options(self, opt_index=None): # sort the options by value(values are integers) options = {i: n for n, i in self.desc.get('NAME_MAP', {}).items()} if self.node: node, desc = self.node, self.desc sub_desc = desc['SUB_STRUCT'] def_struct_name = sub_desc['NAME'] if self.use_gui_names and 'GUI_NAME' in sub_desc: def_struct_name = sub_desc['GUI_NAME'] options_to_generate = range(len(node)) if opt_index is not None: options_to_generate = ((opt_index, ) if opt_index in options_to_generate else ()) for i in options_to_generate: if i in options: continue sub_node = node[i] if not hasattr(sub_node, 'desc'): continue sub_desc = sub_node.desc sub_struct_name = sub_desc.get('GUI_NAME', sub_desc['NAME']) if sub_struct_name == def_struct_name: continue options[i] = sub_struct_name if opt_index is None: self.options_sane = True self.option_cache = options if self.sel_menu is not None: self.sel_menu.options_menu_sane = False self.sel_menu.max_index = len(node) - 1 return options return options.get(opt_index, None) def set_shift_up_disabled(self, disable=True): ''' Disables the move up button if disable is True. Enables it if not. ''' if disable: self.shift_up_btn.config(state="disabled") else: self.shift_up_btn.config(state="normal") def set_shift_down_disabled(self, disable=True): ''' Disables the move down button if disable is True. Enables it if not. ''' if disable: self.shift_down_btn.config(state="disabled") else: self.shift_down_btn.config(state="normal") def set_add_disabled(self, disable=True): '''Disables the add button if disable is True. Enables it if not.''' if disable: self.add_btn.config(state="disabled") else: self.add_btn.config(state="normal") def set_insert_disabled(self, disable=True): '''Disables the insert button if disable is True. Enables it if not.''' if disable: self.insert_btn.config(state="disabled") else: self.insert_btn.config(state="normal") def set_duplicate_disabled(self, disable=True): ''' Disables the duplicate button if disable is True. Enables it if not. ''' if disable: self.duplicate_btn.config(state="disabled") else: self.duplicate_btn.config(state="normal") def set_delete_disabled(self, disable=True): '''Disables the delete button if disable is True. Enables it if not.''' if disable: self.delete_btn.config(state="disabled") else: self.delete_btn.config(state="normal") def set_delete_all_disabled(self, disable=True): ''' Disables the delete_all button if disable is True. Enables it if not. ''' if disable: self.delete_all_btn.config(state="disabled") else: self.delete_all_btn.config(state="normal") def edit_apply(self=None, *, edit_state, undo=True): state = edit_state edit_type = state.edit_type i = state.attr_index undo_node = state.undo_node redo_node = state.redo_node edit_info = state.edit_info sel_index = edit_info.get('sel_index', 0) w, node = field_widget.FieldWidget.get_widget_and_node( nodepath=state.nodepath, tag_window=state.tag_window) if edit_type == 'shift_up': node[i], node[i - 1] = node[i - 1], node[i] elif edit_type == 'shift_down': node[i], node[i + 1] = node[i + 1], node[i] elif edit_type in ('add', 'insert', 'duplicate'): if undo: sel_index = None node.pop(i) else: node.insert(i, redo_node) elif edit_type == 'delete': if undo: node.insert(i, undo_node) else: sel_index = None node.pop(i) elif edit_type == 'delete_all': if undo: node[:] = undo_node else: del node[:] sel_index = None else: raise TypeError('Unknown edit_state type') if w is not None: try: if w.desc is not state.desc: return if sel_index is None: pass elif edit_type in ('add', 'insert', 'duplicate', 'delete'): w.sel_index = sel_index elif edit_type in ('shift_up', 'shift_down'): w.sel_index = sel_index if undo: pass elif 'down' in edit_type: w.sel_index += 1 else: w.sel_index -= 1 max_index = len(node) - 1 w.sel_menu.max_index = max_index w.options_sane = w.sel_menu.options_menu_sane = False if w.sel_index < 0: w.select_option(0, force=True) elif w.sel_index > max_index: w.select_option(max_index, force=True) else: w.select_option(w.sel_index, force=True) w.needs_flushing = False w.set_edited() except Exception: print(format_exc()) def edit_create(self, **kwargs): # add own stuff kwargs.setdefault("sel_index", self.sel_index) field_widget.FieldWidget.edit_create(self, **kwargs) def shift_entry_up(self): if not hasattr(self.node, '__len__') or len(self.node) < 2: return node = self.node index = self.sel_index if index <= 0: return self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.edit_create(edit_type='shift_up', attr_index=index) node[index], node[index - 1] = node[index - 1], node[index] self.sel_index = self.sel_menu.sel_index = index - 1 self.options_sane = self.sel_menu.options_menu_sane = False self.sel_menu.update_label() def shift_entry_down(self): if not hasattr(self.node, '__len__') or len(self.node) < 2: return node = self.node index = self.sel_index if index >= len(node) - 1: return self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.edit_create(edit_type='shift_down', attr_index=index) node[index], node[index + 1] = node[index + 1], node[index] self.sel_index = self.sel_menu.sel_index = index + 1 self.options_sane = self.sel_menu.options_menu_sane = False self.sel_menu.update_label() def add_entry(self): if not hasattr(self.node, '__len__'): return field_max = self.field_max if field_max is not None and len(self.node) >= field_max: if self.enforce_max: return attr_index = len(self.node) self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.node.append() self.edit_create(edit_type='add', attr_index=attr_index, redo_node=self.node[attr_index], sel_index=attr_index) self.options_sane = self.sel_menu.options_menu_sane = False self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() self.select_option(len(self.node) - 1, True) def insert_entry(self): if not hasattr(self.node, '__len__'): return field_max = self.field_max if field_max is not None and len(self.node) >= field_max: if self.enforce_max: return attr_index = self.sel_index = max(self.sel_index, 0) self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.node.insert(attr_index) self.edit_create(edit_type='insert', attr_index=attr_index, redo_node=self.node[attr_index], sel_index=attr_index) self.options_sane = self.sel_menu.options_menu_sane = False self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() self.select_option(attr_index, True) # select the new entry def duplicate_entry(self): if not hasattr(self.node, '__len__') or len(self.node) < 1: return field_max = self.field_max if field_max is not None and len(self.node) >= field_max: if self.enforce_max: return self.sel_index = self.sel_menu.sel_index = max(self.sel_index, 0) self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk new_subnode = deepcopy(self.node[self.sel_index]) attr_index = len(self.node) self.edit_create(edit_type='duplicate', attr_index=attr_index, redo_node=new_subnode, sel_index=attr_index) self.node.append(new_subnode) self.options_sane = self.sel_menu.options_menu_sane = False self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() self.select_option(attr_index, True) def delete_entry(self): if not hasattr(self.node, '__len__') or len(self.node) == 0: return field_min = self.field_min if field_min is None: field_min = 0 if len(self.node) <= field_min: if self.enforce_min: return if not len(self.node): self.sel_menu.disable() return attr_index = max(self.sel_index, 0) self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.edit_create(edit_type='delete', undo_node=self.node[attr_index], attr_index=attr_index, sel_index=attr_index) del self.node[attr_index] attr_index = max(-1, min(len(self.node) - 1, attr_index)) self.options_sane = self.sel_menu.options_menu_sane = False self.select_option(attr_index, True) self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() def delete_all_entries(self): if not hasattr(self.node, '__len__') or len(self.node) == 0: return field_min = self.field_min if field_min is None: field_min = 0 if len(self.node) <= field_min: if self.enforce_min: return if not len(self.node): self.sel_menu.disable() return self.set_edited() # do this first so the TagWindow detects that # the title needs to be updated with an asterisk self.edit_create(edit_type='delete_all', undo_node=tuple(self.node[:])) del self.node[:] self.options_sane = self.sel_menu.options_menu_sane = False self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() self.select_option(self.sel_index, True) def set_all_buttons_disabled(self, disable=False): for btn in (self.add_btn, self.insert_btn, self.duplicate_btn, self.delete_btn, self.delete_all_btn, self.import_btn, self.export_btn, self.shift_up_btn, self.shift_down_btn): if disable: btn.config(state=tk.DISABLED) else: btn.config(state=tk.NORMAL) def disable_unusable_buttons(self): no_node = not hasattr(self.node, '__len__') if no_node or len(self.node) < 2: self.set_shift_up_disabled() self.set_shift_down_disabled() if no_node or (isinstance(self.desc.get('SIZE'), int) and self.enforce_min): self.set_add_disabled() self.set_insert_disabled() self.set_duplicate_disabled() self.set_delete_disabled() self.set_delete_all_disabled() return field_max = self.field_max field_min = self.field_min if field_min is None or field_min < 0: field_min = 0 enforce_min = self.enforce_min or field_min == 0 enforce_max = self.enforce_max if field_max is not None and len( self.node) >= field_max and enforce_max: self.set_add_disabled() self.set_insert_disabled() self.set_duplicate_disabled() if len(self.node) <= field_min and (enforce_min or not self.node): self.set_delete_disabled() self.set_delete_all_disabled() if not self.node: self.set_export_disabled() self.set_import_disabled() self.set_duplicate_disabled() def populate(self): node = self.node desc = self.desc sub_node = None sub_desc = desc['SUB_STRUCT'] if node and self.sel_index in range(len(node)): sub_node = node[self.sel_index] sub_desc = desc['SUB_STRUCT'] if hasattr(sub_node, 'desc'): sub_desc = sub_node.desc if self.content in (None, self): self.content = tk.Frame(self, relief="sunken", bd=self.frame_depth, bg=self.default_bg_color) self.sel_menu.default_text = sub_desc.get('GUI_NAME', sub_desc.get('NAME', "")) self.sel_menu.update_label() self.disable_unusable_buttons() rebuild = not bool(self.f_widgets) if hasattr(node, '__len__') and len(node) == 0: # disabling existing widgets self.sel_index = -1 self.sel_menu.max_index = -1 if self.f_widgets: self.unload_child_node_data() else: for w in self.f_widgets: if getattr(w, "desc", None) != sub_desc: rebuild = True break if rebuild: # destroy existing widgets and make new ones self.populated = False self.f_widget_ids = [] self.f_widget_ids_map = {} self.f_widget_ids_map_inv = {} # destroy any child widgets of the content for c in list(self.f_widgets.values()): c.destroy() for w in (self, self.content, self.title, self.title_label, self.controls, self.buttons): w.tooltip_string = self.desc.get('TOOLTIP') self.display_comment(self.content) widget_cls = self.widget_picker.get_widget(sub_desc) try: widget = widget_cls(self.content, node=sub_node, parent=node, show_title=False, dont_padx_fields=True, attr_index=self.sel_index, tag_window=self.tag_window, f_widget_parent=self, disabled=self.disabled) except Exception: print(format_exc()) widget = data_frame.NullFrame(self.content, node=sub_node, parent=node, show_title=False, dont_padx_fields=True, attr_index=self.sel_index, tag_window=self.tag_window, f_widget_parent=self, disabled=self.disabled) wid = id(widget) self.f_widget_ids.append(wid) self.f_widget_ids_map[self.sel_index] = wid self.f_widget_ids_map_inv[wid] = self.sel_index self.populated = True self.build_f_widget_cache() # now that the field widgets are created, position them if self.show.get(): self.pose_fields() if self.node is None: self.set_disabled(True) else: self.set_children_disabled(not self.node) def reload(self): '''Resupplies the nodes to the widgets which display them.''' try: node = self.node if self.node else () desc = self.desc is_empty = len(node) == 0 field_max = self.field_max field_min = self.field_min if field_min is None: field_min = 0 self.set_all_buttons_disabled(self.disabled) self.disable_unusable_buttons() if is_empty: self.sel_menu.sel_index = -1 self.sel_menu.max_index = -1 # if there is no index to select, destroy the content if self.sel_index != -1: self.sel_index = -1 self.unload_child_node_data() else: self.f_widget_ids_map = {} self.f_widget_ids_map_inv = {} self.sel_menu.sel_index = self.sel_index self.sel_menu.max_index = len(node) - 1 sub_node = None sub_desc = desc['SUB_STRUCT'] if node and self.sel_index in range(len(node)): sub_node = node[self.sel_index] sub_desc = desc['SUB_STRUCT'] if hasattr(sub_node, 'desc'): sub_desc = sub_node.desc for wid in self.f_widget_ids: w = self.f_widgets[wid] wid = id(w) if node and self.sel_index not in range(len(node)): # current selection index is invalid. call select_option # to reset it to some valid option. Don't reload though, # as we will be either reloading or repopulating below. self.select_option(force=True, reload=False) self.f_widget_ids_map[self.sel_index] = wid self.f_widget_ids_map_inv[wid] = self.sel_index # if the descriptors are different, gotta repopulate! if w.load_node_data(node, sub_node, self.sel_index, sub_desc): self.populate() self.apply_style() return w.reload() if w.desc.get("PORTABLE", True) and self.node: self.set_import_disabled(False) self.set_export_disabled(False) else: self.set_import_disabled() self.set_export_disabled() self.sel_menu.update_label() if self.node is None: self.set_disabled(True) else: self.set_children_disabled(not self.node) except Exception: print(format_exc()) def pose_fields(self): # there should only be one wid in here, but for # the sake of consistancy we'll loop over them. for wid in self.f_widget_ids: w = self.f_widgets[wid] # by adding a fixed amount of padding, we fix a problem # with difficult to predict padding based on nesting w.pack(fill='x', side='top', expand=True, padx=self.vertical_padx, pady=self.vertical_pady) # if there are no children in the content, we need to # pack in SOMETHING, update the idletasks, and then # destroy that something to resize the content frame if not self.f_widgets: f = tk.Frame(self.content, width=0, height=0, bd=0) f.pack() self.content.update_idletasks() f.destroy() self.content.pack(fill='both', side='top', anchor='nw', expand=True) def select_option(self, opt_index=None, force=False, reload=True): node = self.node if self.node else () curr_index = self.sel_index if opt_index is None: opt_index = curr_index if opt_index < 0: opt_index = 0 if opt_index == curr_index and not force: return elif not node: opt_index = -1 elif opt_index not in range(len(node)): opt_index = len(node) - 1 # flush any lingering changes self.flush() self.sel_index = opt_index self.sel_menu.sel_index = opt_index self.sel_menu.max_index = len(node) - 1 if reload: self.reload() self.sel_menu.update_label() @property def visible_field_count(self): # array frames only display one item at a time return 1
class DynamicEnumFrame(EnumFrame): options_sane = False def __init__(self, *args, **kwargs): kwargs.update(relief='flat', bd=0, highlightthickness=0) data_frame.DataFrame.__init__(self, *args, **kwargs) sel_index = -1 if self.node is None else self.node + 1 # make the widgets self.content = tk.Frame(self, relief='flat', bd=0) self.title_label = tk.Label( self.content, text=self.gui_name, justify='left', anchor='w', width=self.title_size, disabledforeground=self.text_disabled_color) self.sel_menu = ScrollMenu(self.content, f_widget_parent=self, menu_width=self.widget_width, sel_index=sel_index, max_index=0, disabled=self.disabled, default_text="<INVALID>", option_getter=self.get_options, callback=self.select_option) self.sel_menu.bind('<FocusIn>', self.flag_sanity_change) self.sel_menu.arrow_button.bind('<FocusIn>', self.flag_sanity_change) self.sel_menu.bind('<Enter>', self.flag_sanity_change) self.sel_menu.arrow_button.bind('<Enter>', self.flag_sanity_change) self.sel_menu.options_volatile = 'DYN_NAME_PATH' in self.desc if self.gui_name != '': self.title_label.pack(side="left", fill="x") self.content.pack(fill="x", expand=True) self.sel_menu.pack(side="left", fill="x") self.populate() self._initialized = True def edit_apply(self=None, *, edit_state, undo=True): state = edit_state attr_index = state.attr_index w_parent, parent = field_widget.FieldWidget.get_widget_and_node( nodepath=state.nodepath, tag_window=state.tag_window) if undo: parent[attr_index] = state.undo_node else: parent[attr_index] = state.redo_node if w_parent is not None: try: w = w_parent.f_widgets[w_parent.f_widget_ids_map[attr_index]] if w.desc is not state.desc: return w.sel_menu.sel_index = parent[attr_index] + 1 w.needs_flushing = False w.sel_menu.update_label() w.set_edited() except Exception: print(format_exc()) def generate_options(self, opt_index=None): desc = self.desc options = {0: "-1. NONE"} dyn_name_path = desc.get('DYN_NAME_PATH') if self.node is None: if opt_index is None: return options return None elif not dyn_name_path: print("Missing DYN_NAME_PATH path in dynamic enumerator.") print(self.parent.get_root().def_id, self.name) if opt_index is None: return options return None try: p_out, p_in = dyn_name_path.split('[DYN_I]') # We are ALWAYS going to go to the parent, so we need to slice if p_out.startswith('..'): p_out = p_out.split('.', 1)[-1] array = self.parent.get_neighbor(p_out) options_to_generate = range(len(array)) if opt_index is not None: options_to_generate = ((opt_index - 1, ) if opt_index - 1 in options_to_generate else ()) for i in options_to_generate: name = array[i].get_neighbor(p_in) if isinstance(name, list): name = repr(name).strip("[").strip("]") else: name = str(name) options[i + 1] = '%s. %s' % (i, name.split('\n')[0]) option_count = len(array) + 1 except Exception: print(format_exc()) option_count = 1 if opt_index is None: self.option_cache = options self.options_sane = True if self.sel_menu is not None: self.sel_menu.options_menu_sane = False self.sel_menu.max_index = option_count - 1 return options return options.get(opt_index, None) def reload(self): try: self.options_sane = False if self.disabled == self.sel_menu.disabled: pass elif self.disabled: self.sel_menu.disable() else: self.sel_menu.enable() self.generate_options() if self.node is not None: self.sel_menu.sel_index = self.node + 1 self.sel_menu.update_label() except Exception: print(format_exc()) populate = reload def select_option(self, opt_index=None): if None in (self.parent, self.node): return # make an edit state if self.node != opt_index - 1: self.set_edited() self.edit_create(undo_node=self.node, redo_node=opt_index - 1) self.sel_menu.sel_index = opt_index # since the node value is actually signed and can be -1, we'll # set entry 0 to be a node value of -1 and all other values # are one less than the entry index they are located in. self.node = self.parent[self.attr_index] = opt_index - 1 self.sel_menu.update_label() def flag_sanity_change(self, e=None): self.options_sane = self.sel_menu.options_menu_sane = ( not self.sel_menu.options_volatile) if not self.options_sane: self.option_cache = None