class Anchor(Editor): def __init__(self, master, style_def): super().__init__(master, style_def) self.set_def(style_def) self.config(width=150, height=110) self.n = ToggleButton(self, text="N", width=20, height=20) self.n.grid(row=0, column=0, columnspan=3, sticky='ns') self.w = ToggleButton(self, text='W', width=20, height=20) self.w.grid(row=1, column=0, sticky='ew') self.pad = Frame(self, width=60, height=60, **self.style.dark, **self.style.dark_highlight_active) self.pad.grid(row=1, column=1, padx=1, pady=1) self.pad.grid_propagate(0) self.pad.grid_columnconfigure(0, minsize=60) self.pad.grid_rowconfigure(0, minsize=60) self.floating = Frame(self.pad, **self.style.dark_on_hover, width=20, height=20) self.floating.grid(row=0, column=0, pady=1, padx=1) self.e = ToggleButton(self, text="E", width=20, height=20) self.e.grid(row=1, column=2, sticky='ew') self.s = ToggleButton(self, text='S', width=20, height=20) self.s.grid(row=2, column=0, columnspan=3, sticky='ns') self.anchors = {"n": self.n, "w": self.w, "e": self.e, "s": self.s} self._order = ("n", "s", "e", "w") self._selected = [] self._exclusive_pairs = ({"n", "s"}, {"e", "w"}) self._is_multiple = re.compile(r'(.*[ns].*[ns])|(.*[ew].*[ew])') for anchor in self.anchors: self.anchors[anchor].on_change(self._change, anchor) def _is_exclusive_of(self, anchor1, anchor2): return {anchor1, anchor2} in self._exclusive_pairs def _change(self, _, anchor): if not self.multiple: self._sanitize(anchor) self._adjust() if self._on_change: self._on_change(self.get()) def _sanitize(self, anchor): ex_anchor = [i for i in self.get() if self._is_exclusive_of(i, anchor)] if len(ex_anchor): self.anchors.get(ex_anchor[0]).set(False) def _adjust(self): sticky = '' if self.get() == 'center' else self.get() self.floating.grid(row=0, column=0, pady=1, padx=1, sticky=sticky) def get(self): anchor = ''.join([i for i in self._order if self.anchors[i].get()]) # No anchor means center but only when we are acting as an anchor editor # if self.multiple is True then we are a stickiness editor and an empty string will suffice if anchor == '': if not self.multiple: return 'center' return anchor def set(self, value): # bypass the special value 'center' before subjecting to validity check value = '' if value == 'center' else str(value) # Ignore invalid values if self._is_multiple.match(str(value)) and not self.multiple: return # Assume no anchor means center for anchor in self.anchors: self.anchors.get(anchor).set(anchor in value) self._adjust() def set_def(self, definition): # This flag determines whether multiple anchors are allowed at a time # set to True to obtain a sticky property editor self.multiple = definition.get("multiple", True) super().set_def(definition)
class EventPane(BaseFeature): name = "Event pane" icon = "blank" _defaults = { **BaseFeature._defaults, "side": "right", } NO_SELECTION_MSG = "You have not selected any widget selected" NO_EVENT_MSG = "You have not added any bindings" NO_MATCH_MSG = "No items match your search" def __init__(self, master, studio, **cnf): super().__init__(master, studio, **cnf) self.header = Frame(self, **self.style.surface) self.header.pack(side="top", fill="x") for i, title in enumerate(("Sequence", "Handler", "Add", " " * 3)): Label( self.header, **self.style.text_passive, text=title, anchor="w", ).grid(row=0, column=i, sticky='ew') # set the first two columns to expand evenly for column in range(2): self.header.grid_columnconfigure(column, weight=1, uniform=1) self.bindings = BindingsTable(self) self.bindings.on_value_change(self.modify_item) self.bindings.on_item_delete(self.delete_item) self.bindings.pack(fill="both", expand=True) self._add = Button(self._header, **self.style.button, width=25, height=25, image=get_icon_image("add", 15, 15)) self._add.pack(side="right") self._add.tooltip("Add event binding") self._add.on_click(self.add_new) self._search_btn = Button( self._header, **self.style.button, image=get_icon_image("search", 15, 15), width=25, height=25, ) self._search_btn.pack(side="right") self._search_btn.on_click(self.start_search) self._empty_frame = Label(self.bindings, **self.style.text_passive) self._show_empty(self.NO_SELECTION_MSG) def _show_empty(self, message): self._empty_frame.place(x=0, y=0, relwidth=1, relheight=1) self._empty_frame["text"] = message def _remove_empty(self): self._empty_frame.place_forget() def add_new(self, *_): if self.studio.selected is None: return self._remove_empty() new_binding = make_event("<>", "", False) widget = self.studio.selected if not hasattr(widget, "_event_map_"): setattr(widget, "_event_map_", {}) widget._event_map_[new_binding.id] = new_binding self.bindings.add(new_binding) def delete_item(self, item): widget = self.studio.selected if widget is None: return widget._event_map_.pop(item.id) self.bindings.remove(item.id) def modify_item(self, value: EventBinding): widget = self.studio.selected widget._event_map_[value.id] = value def on_select(self, widget): if widget is None: self._show_empty(self.NO_SELECTION_MSG) return self._remove_empty() bindings = getattr(widget, "_event_map_", {}) values = bindings.values() self.bindings.clear() self.bindings.add(*values) if not values: self._show_empty(self.NO_EVENT_MSG) def start_search(self, *_): if self.studio.selected: super().start_search() def on_search_query(self, query: str): showing = 0 self._remove_empty() self.bindings.hide_all() for item in self.bindings.items: if query in item.value.sequence or query in item.value.handler: item.show() showing += 1 if not showing: self._show_empty(self.NO_MATCH_MSG) def on_search_clear(self): self._remove_empty() self.bindings.hide_all() for item in self.bindings.items: item.show() super().on_search_clear()