class CollapseFrame(Frame): def __init__(self, master, **cnf): super().__init__(master, **cnf) self.config(**self.style.dark) self._label_frame = Frame(self, **self.style.bright, height=20) self._label_frame.pack(side="top", fill="x", padx=2) self._label_frame.pack_propagate(0) self._label = Label(self._label_frame, **self.style.bright, **self.style.text_bright) self._label.pack(side="left") self._collapse_btn = Button(self._label_frame, width=20, **self.style.bright, **self.style.text_bright) self._collapse_btn.config(text=get_icon("triangle_up")) self._collapse_btn.pack(side="right", fill="y") self._collapse_btn.on_click(self.toggle) self.body = Frame(self, **self.style.dark) self.body.pack(side="top", fill="both", pady=2) self.__ref = Frame(self.body, height=0, width=0, **self.style.dark) self.__ref.pack(side="top") self._collapsed = False def update_state(self): self.__ref.pack(side="top") def collapse(self, *_): if not self._collapsed: self.body.pack_forget() self._collapse_btn.config(text=get_icon("triangle_down")) self.pack_propagate(0) self.config(height=20) self._collapsed = True def clear_children(self): self.body.clear_children() def expand(self, *_): if self._collapsed: self.body.pack(side="top", fill="both") self.pack_propagate(1) self._collapse_btn.config(text=get_icon("triangle_up")) self._collapsed = False def toggle(self, *_): if self._collapsed: self.expand() else: self.collapse() @property def label(self): return self._label["text"] @label.setter def label(self, value): self._label.config(text=value)
class CollapseFrame(Frame): __icons_loaded = False EXPAND = None COLLAPSE = None def __init__(self, master, **cnf): super().__init__(master, **cnf) self._load_icons() self.config(**self.style.surface) self._label_frame = Frame(self, **self.style.bright, height=20) self._label_frame.pack(side="top", fill="x", padx=2) self._label_frame.pack_propagate(0) self._label = Label(self._label_frame, **self.style.bright, **self.style.text_bright) self._label.pack(side="left") self._collapse_btn = Button(self._label_frame, width=20, **self.style.bright, **self.style.text_bright) self._collapse_btn.config(image=self.COLLAPSE) self._collapse_btn.pack(side="right", fill="y") self._collapse_btn.on_click(self.toggle) self.body = Frame(self, **self.style.surface) self.body.pack(side="top", fill="both", pady=2) self.__ref = Frame(self.body, height=0, width=0, **self.style.surface) self.__ref.pack(side="top") self._collapsed = False @classmethod def _load_icons(cls): if cls.__icons_loaded: return cls.EXPAND = get_icon_image("triangle_down", 14, 14) cls.COLLAPSE = get_icon_image("triangle_up", 14, 14) def update_state(self): self.__ref.pack(side="top") def collapse(self, *_): if not self._collapsed: self.body.pack_forget() self._collapse_btn.config(image=self.EXPAND) self.pack_propagate(0) self.config(height=20) self._collapsed = True def clear_children(self): self.body.clear_children() def expand(self, *_): if self._collapsed: self.body.pack(side="top", fill="both") self.pack_propagate(1) self._collapse_btn.config(image=self.COLLAPSE) self._collapsed = False def toggle(self, *_): if self._collapsed: self.expand() else: self.collapse() @property def label(self): return self._label["text"] @label.setter def label(self, value): self._label.config(text=value)
class StyleGroup(CollapseFrame): """ Main subdivision of the Style pane """ handles_layout = False self_positioned = False def __init__(self, master, pane, **cnf): super().__init__(master) self.style_pane = pane self.configure(**{**self.style.surface, **cnf}) self._empty_message = "Select an item to see styles" self._empty = Frame(self.body, **self.style.surface) self._empty_label = Label( self._empty, **self.style.text_passive, ) self._empty_label.pack(fill="both", expand=True, pady=15) self._widget = None self._prev_widget = None self._has_initialized = False # Flag to mark whether Style Items have been created self.items = {} @property def widget(self): return self._widget def can_optimize(self): return False def add(self, style_item): self.items[style_item.name] = style_item if self.style_pane._search_query is not None: if self._match_query(style_item.definition, self.style_pane._search_query): self._show(style_item) # make sure item is not available for reuse whether it # is displayed or not style_item._make_available(False) else: self._show(style_item) def remove(self, style_item): if style_item.name in self.items: self.items.pop(style_item.name) self._hide(style_item) def _show(self, item): item.pack(side="top", fill="x", pady=1) def _hide(self, item): item.pack_forget() def _get_prop(self, prop, widget): return widget.get_prop(prop) def _set_prop(self, prop, value, widget): widget.configure(**{prop: value}) def _hide_group(self): pass def _show_group(self): pass def _match_query(self, definition, query): return query in definition["name"] or query in definition[ "display_name"] def _show_empty(self, text=None): self._empty.pack(fill="both", expand=True) text = self._empty_message if text is None else text self._empty_label["text"] = text def _remove_empty(self): self._empty.pack_forget() def on_widget_change(self, widget): self._widget = widget if widget is None: self.collapse() return definitions = self.get_definition() if self.can_optimize(): for prop in definitions: self.items[prop]._re_purposed(definitions[prop]) else: self.style_pane.show_loading() # this unmaps all style items returning them to the pool for reuse self.clear_children() # make all items held by group available for reuse ReusableStyleItem.free_all(self.items.values()) self.items.clear() add = self.add list( map( lambda p: add( ReusableStyleItem.acquire(self, definitions[p], self. apply), ), definitions)) if not self.items: self._show_empty() else: self._remove_empty() # self.style_pane.body.scroll_to_start() self._has_initialized = True self._prev_widget = widget def _apply_action(self, prop, value, widget, data): self.apply(prop, value, widget, True) def _get_action_data(self, widget, prop): return {} def _get_key(self, widget, prop): return f"{widget}:{self.__class__.__name__}:{prop}" def apply(self, prop, value, widget=None, silent=False): is_external = widget is not None widget = self.widget if widget is None else widget if widget is None: return try: prev_val = self._get_prop(prop, widget) data = self._get_action_data(widget, prop) self._set_prop(prop, value, widget) new_data = self._get_action_data(widget, prop) self.style_pane.widget_modified(widget) if is_external: if widget == self.widget: self.items[prop].set_silently(value) if silent: return key = self._get_key(widget, prop) action = self.style_pane.last_action() if action is None or action.key != key: self.style_pane.new_action( Action( lambda _: self._apply_action(prop, prev_val, widget, data), lambda _: self._apply_action(prop, value, widget, new_data), key=key, )) else: action.update_redo(lambda _: self._apply_action( prop, value, widget, new_data)) except Exception as e: # Empty string values are too common to be useful in logger debug if value != '': logging.error(e) logging.error( f"Could not set {self.__class__.__name__} {prop} as {value}", ) def get_definition(self): return {} def supports_widget(self, widget): return True def on_search_query(self, query): item_found = False for item in self.items.values(): if self._match_query(item.definition, query): self._show(item) item_found = True else: self._hide(item) if not item_found: self._show_empty("No items match your search") else: self._remove_empty() def on_search_clear(self): # Calling search query with empty query ensures all items are displayed self.clear_children() self.on_search_query("")