예제 #1
0
 def __init__(self, master, style_def=None):
     super().__init__(master, style_def)
     self._spinner = Spinner(self, **self.style.dark_input)
     self._spinner.pack(fill="x")
     self._spinner.on_change(self.spinner_change)
     # initial set up is mandatory for all Choice subclasses
     self.set_up()
예제 #2
0
 def __init__(self, master, style_def=None):
     super().__init__(master, style_def)
     self.config(**self.style.dark_highlight_active)
     self._entry = SpinBox(self, from_=0, to=1e6, **self.style.spinbox)
     self._entry.config(**self.style.no_highlight)
     self._entry.set_validator(numeric_limit, 0, 1e6)
     self._entry.on_change(self._change)
     self._unit = Spinner(self, **self.style.dark_input)
     self._unit.config(**self.style.no_highlight, width=50)
     self._unit.set_item_class(Choice.ChoiceItem)
     self._unit.set_values(Duration.UNITS)
     self._unit.pack(side="right")
     self._unit.on_change(self._change)
     self._entry.pack(side='left', fill="x")
     self.set_def(style_def)
예제 #3
0
 def __init__(self, master, style_def=None):
     super().__init__(master, style_def)
     if style_def is None:
         style_def = {}
     self.style_def = style_def
     self._spinner = Spinner(self, **self.style.dark_input)
     self._spinner.pack(fill="x")
     self._spinner.on_change(self.spinner_change)
     self.set_up()
     values = style_def.get("options", ())
     if values:
         if not style_def.get('allow_empty', True):
             self._spinner.set_values(('', *values))
         else:
             self._spinner.set_values(values)
예제 #4
0
    def __init__(self, master, studio=None, **cnf):
        if not self._var_init:
            self._init_var(studio)
        super().__init__(master, studio, **cnf)

        f = Frame(self, **self.style.surface)
        f.pack(side="top", fill="both", expand=True, pady=4)
        f.pack_propagate(0)

        self._widget_set = Spinner(self._header, width=150)
        self._widget_set.config(**self.style.no_highlight)
        self._widget_set.set_values(list(self.CLASSES.keys()))
        self._widget_set.pack(side="left")
        self._widget_set.on_change(self.collect_groups)
        self._select_pane = ScrolledFrame(f, width=150)
        self._select_pane.place(x=0, y=0, relwidth=0.4, relheight=1)

        self._search_btn = Button(self._header, image=get_icon_image("search", 15, 15), width=25, height=25,
                                  **self.style.button)
        self._search_btn.pack(side="right")
        self._search_btn.on_click(self.start_search)
        self._search_selector = Label(self._select_pane.body, **self.style.text, text="search", anchor="w")
        self._search_selector.configure(**self.style.hover)

        self._widget_pane = ScrolledFrame(f, width=150)
        self._select_pane.body.config(**self.style.surface)
        self._widget_pane.place(relx=0.4, y=0, relwidth=0.6, relheight=1)

        self._pool = {}
        self._selectors = []
        self._selected = None
        self._component_cache = None
        self._extern_groups = []
        self._widget = None
        self.collect_groups(self.get_pref("widget_set"))
        # add custom widgets config to settings
        templates.update(_widget_pref_template)
        self._custom_group = None
        self._custom_widgets = []
        Preferences.acquire().add_listener(self._custom_pref_path, self._init_custom)
        self._reload_custom()
예제 #5
0
    def __init__(self, master, studio=None, **cnf):
        if not self._var_init:
            self._init_var(studio)
        super().__init__(master, studio, **cnf)

        f = Frame(self, **self.style.dark)
        f.pack(side="top", fill="both", expand=True, pady=4)
        f.pack_propagate(0)

        self._widget_set = Spinner(self._header, width=150)
        self._widget_set.config(**self.style.no_highlight)
        self._widget_set.set_values(list(self.CLASSES.keys()))
        self._widget_set.pack(side="left")
        self._widget_set.on_change(self.collect_groups)
        self._select_pane = ScrolledFrame(f, width=150)
        self._select_pane.place(x=0, y=0, relwidth=0.4, relheight=1)

        self._search_btn = Button(self._header,
                                  image=get_icon_image("search", 15, 15),
                                  width=25,
                                  height=25,
                                  **self.style.dark_button)
        self._search_btn.pack(side="right")
        self._search_btn.on_click(self.start_search)
        self._search_selector = Label(self._select_pane.body,
                                      **self.style.dark_text,
                                      text="search",
                                      anchor="w")
        self._search_selector.configure(**self.style.dark_on_hover)

        self._widget_pane = ScrolledFrame(f, width=150, bg="orange")
        self._select_pane.body.config(**self.style.dark)
        self._widget_pane.place(relx=0.4, y=0, relwidth=0.6, relheight=1)

        self._pool = {}
        self._selectors = []
        self._selected = None
        self._component_cache = None
        self.collect_groups(self.get_pref("widget_set"))
예제 #6
0
class Choice(Editor):
    # Some subclasses may not need to repopulate when definition changes
    # When extending this class take note of this for the sake of performance
    _setup_once = False

    class ChoiceItem(CompoundList.BaseItem):
        def render(self):
            if not self.value:
                Label(self, **self.style.dark_text, text="select",
                      anchor="w").pack(fill="both")
                return
            Label(self, **self.style.dark_text, text=self._value,
                  anchor="w").pack(fill="both")

    def __init__(self, master, style_def=None):
        super().__init__(master, style_def)
        self._spinner = Spinner(self, **self.style.dark_input)
        self._spinner.pack(fill="x")
        self._spinner.on_change(self.spinner_change)
        # initial set up is mandatory for all Choice subclasses
        self.set_up()

    def set_up(self):
        self._spinner.set_item_class(Choice.ChoiceItem)
        # update the values from definition provided
        values = self.style_def.get("options", ())
        if values:
            if not self.style_def.get('allow_empty', True):
                self._spinner.set_values(('', *values))
            else:
                self._spinner.set_values(values)

    def spinner_change(self, value):
        if self._on_change is not None:
            self._on_change(value)

    def set(self, value):
        # Convert to string as values of type _tkinter.Tcl_Obj are common in ttk and may cause unpredictable behaviour
        self._spinner.set(str(value))

    def get(self):
        return self._spinner.get()

    def set_def(self, definition):
        super().set_def(definition)
        if not self._setup_once:
            # repopulate only when needed for the sake of efficiency
            self.set_up()
예제 #7
0
class Duration(TextMixin, Editor):
    UNITS = ('ns', 'ms', 'sec', 'min', 'hrs')
    MULTIPLIER = {'ns': 1e-6, 'ms': 1, 'sec': 1e3, 'min': 6e4, 'hrs': 3.6e5}

    def __init__(self, master, style_def=None):
        super().__init__(master, style_def)
        self.config(**self.style.dark_highlight_active)
        self._entry = SpinBox(self, from_=0, to=1e6, **self.style.spinbox)
        self._entry.config(**self.style.no_highlight)
        self._entry.set_validator(numeric_limit, 0, 1e6)
        self._entry.on_change(self._change)
        self._unit = Spinner(self, **self.style.dark_input)
        self._unit.config(**self.style.no_highlight, width=50)
        self._unit.set_item_class(Choice.ChoiceItem)
        self._unit.set_values(Duration.UNITS)
        self._unit.pack(side="right")
        self._unit.on_change(self._change)
        self._entry.pack(side='left', fill="x")
        self.set_def(style_def)

    def set_def(self, definition):
        super().set_def(definition)
        self._metric = definition.get("units", "ms")
        self._unit.set(self._metric)

    def get(self):
        if self._entry.get() == '':
            return ''
        else:
            m1 = self.MULTIPLIER.get(
                self._unit.get(),
                1)  # Multiplier 1 converts to milliseconds, default is ms
            m2 = self.MULTIPLIER.get(
                self._metric,
                1)  # Multiplier 2 converts to required units, default is ms
            return int((self._entry.get() * m1) / m2)
예제 #8
0
class ComponentPane(BaseFeature):
    CLASSES = {
        "native": {
            "widgets": native.widgets
        },
        "legacy": {
            "widgets": legacy.widgets
        },
    }
    name = "Components"
    _var_init = False
    _defaults = {**BaseFeature._defaults, "widget_set": "native"}

    def __init__(self, master, studio=None, **cnf):
        if not self._var_init:
            self._init_var(studio)
        super().__init__(master, studio, **cnf)

        f = Frame(self, **self.style.dark)
        f.pack(side="top", fill="both", expand=True, pady=4)
        f.pack_propagate(0)

        self._widget_set = Spinner(self._header, width=150)
        self._widget_set.config(**self.style.no_highlight)
        self._widget_set.set_values(list(self.CLASSES.keys()))
        self._widget_set.pack(side="left")
        self._widget_set.on_change(self.collect_groups)
        self._select_pane = ScrolledFrame(f, width=150)
        self._select_pane.place(x=0, y=0, relwidth=0.4, relheight=1)

        self._search_btn = Button(self._header,
                                  image=get_icon_image("search", 15, 15),
                                  width=25,
                                  height=25,
                                  **self.style.dark_button)
        self._search_btn.pack(side="right")
        self._search_btn.on_click(self.start_search)
        self._search_selector = Label(self._select_pane.body,
                                      **self.style.dark_text,
                                      text="search",
                                      anchor="w")
        self._search_selector.configure(**self.style.dark_on_hover)

        self._widget_pane = ScrolledFrame(f, width=150, bg="orange")
        self._select_pane.body.config(**self.style.dark)
        self._widget_pane.place(relx=0.4, y=0, relwidth=0.6, relheight=1)

        self._pool = {}
        self._selectors = []
        self._selected = None
        self._component_cache = None
        self.collect_groups(self.get_pref("widget_set"))

    def _init_var(self, master=None):
        self._var_init = True
        for widget_set in self.CLASSES:
            self.CLASSES[widget_set]["var"] = BooleanVar(master, False)

    def _widget_sets_as_menu(self):
        return [
            (
                "checkbutton",  # Type checkbutton
                i.capitalize(),  # Label as title case
                None,  # Image
                partial(self.collect_groups, i),  # The callback
                {
                    "variable": self.CLASSES[i]["var"]
                }  # Additional config including the variable associated
            ) for i in self.CLASSES
        ]

    @property
    def selectors(self):
        return self._selectors

    def create_menu(self):
        return (
            ("command", "Search", get_icon_image("search", 14,
                                                 14), self.start_search, {}),
            ("cascade", "Widget set", None, None, {
                "menu": (*self._widget_sets_as_menu(), )
            }),
        )

    def collect_groups(self, widget_set):
        for other_set in [i for i in self.CLASSES if i != widget_set]:
            self.CLASSES[other_set]["var"].set(False)
        self.CLASSES[widget_set]["var"].set(True)
        self._widget_set.set(widget_set)
        self._select_pane.clear_children()
        self._pool = {}
        components = self.CLASSES.get(widget_set)["widgets"]
        for component in components:
            group = component.group.name
            if group in self._pool:
                self._pool[group].append(
                    Component(self._widget_pane.body, component))
            else:
                self._pool[group] = [
                    Component(self._widget_pane.body, component)
                ]
        self.render_groups()
        # component pool has changed so invalidate the cache
        self._component_cache = None
        self.set_pref("widget_set", widget_set)

    def get_components(self):
        if self._component_cache:
            return self._component_cache
        else:
            # flatten component pool and store to cache
            self._component_cache = [j for i in self._pool.values() for j in i]
            return self._component_cache

    def select(self, selector):
        if self._selected is not None:
            self._selected.deselect()
        selector.select()
        self._selected = selector
        self._widget_pane.clear_children()
        for component in self._pool[selector.name]:
            component.pack(side="top", pady=2, fill="x")

    def render_groups(self):
        self._selectors = []
        for group in self._pool:
            self.add_selector(Selector(self._select_pane.body, text=group))
        if len(self._selectors):
            self.select(self._selectors[0])

    def add_selector(self, selector):
        self._selectors.append(selector)
        selector.bind("<Button-1>", lambda *_: self.select(selector))
        selector.pack(side="top", pady=2, fill="x")

    def hide_selectors(self):
        for selector in self._selectors:
            selector.pack_forget()

    def show_selectors(self):
        for selector in self._selectors:
            selector.pack(side="top", pady=2, fill="x")

    def start_search(self, *_):
        super().start_search()
        self._widget_pane.scroll_to_start()
        if self._selected is not None:
            self._selected.deselect()
        self.hide_selectors()
        self._search_selector.pack(side="top", pady=2, fill="x")
        self._widget_pane.clear_children()
        # Display all components by running an empty query
        self.on_search_query("")

    def on_search_clear(self):
        super().on_search_clear()
        if len(self._selectors):
            self.select(self._selectors[0])
        self._search_selector.pack_forget()
        self.show_selectors()

    def on_search_query(self, query):
        for component in self.get_components():
            if query.lower() in component.component.display_name.lower():
                component.pack(side="top", pady=2, fill="x")
            else:
                component.pack_forget()
예제 #9
0
class ComponentPane(BaseFeature):
    CLASSES = {
        "native": {"widgets": native.widgets},
        "legacy": {"widgets": legacy.widgets},
    }
    name = "Components"
    _var_init = False
    _defaults = {
        **BaseFeature._defaults,
        "widget_set": "native"
    }
    _custom_pref_path = "studio::custom_widget_paths"

    def __init__(self, master, studio=None, **cnf):
        if not self._var_init:
            self._init_var(studio)
        super().__init__(master, studio, **cnf)

        f = Frame(self, **self.style.surface)
        f.pack(side="top", fill="both", expand=True, pady=4)
        f.pack_propagate(0)

        self._widget_set = Spinner(self._header, width=150)
        self._widget_set.config(**self.style.no_highlight)
        self._widget_set.set_values(list(self.CLASSES.keys()))
        self._widget_set.pack(side="left")
        self._widget_set.on_change(self.collect_groups)
        self._select_pane = ScrolledFrame(f, width=150)
        self._select_pane.place(x=0, y=0, relwidth=0.4, relheight=1)

        self._search_btn = Button(self._header, image=get_icon_image("search", 15, 15), width=25, height=25,
                                  **self.style.button)
        self._search_btn.pack(side="right")
        self._search_btn.on_click(self.start_search)
        self._search_selector = Label(self._select_pane.body, **self.style.text, text="search", anchor="w")
        self._search_selector.configure(**self.style.hover)

        self._widget_pane = ScrolledFrame(f, width=150)
        self._select_pane.body.config(**self.style.surface)
        self._widget_pane.place(relx=0.4, y=0, relwidth=0.6, relheight=1)

        self._pool = {}
        self._selectors = []
        self._selected = None
        self._component_cache = None
        self._extern_groups = []
        self._widget = None
        self.collect_groups(self.get_pref("widget_set"))
        # add custom widgets config to settings
        templates.update(_widget_pref_template)
        self._custom_group = None
        self._custom_widgets = []
        Preferences.acquire().add_listener(self._custom_pref_path, self._init_custom)
        self._reload_custom()

    @property
    def custom_widgets(self):
        return self._custom_widgets

    def auto_find_load_custom(self, *modules):
        # locate and load all custom widgets in modules
        # module can be a module or a path to module file
        self._custom_widgets = []
        errors = {}
        for module in modules:
            if isinstance(module, str):
                try:
                    module = import_path(module)
                except Exception as e:
                    errors[module] = e
                    continue
            for attr in dir(module):
                if type(getattr(module, attr)) == WidgetMeta:
                    self._custom_widgets.append(getattr(module, attr))
        if errors:
            error_msg = "\n\n".join(
                [f"{path}\n{error}" for path, error in errors.items()]
            )
            MessageDialog.show_error(
                parent=self.window,
                message=f"Error loading widgets \n\n{error_msg}"
            )

        return self._custom_widgets

    def _init_custom(self, paths):
        # reload custom widget modules
        try:
            widgets = self.auto_find_load_custom(*paths)
        except Exception as e:

            return

        if not widgets:
            if self._custom_group is not None:
                self.unregister_group(self._custom_group)
                self._custom_group = None
            return

        if self._custom_group is None:
            self._custom_group = self.register_group(
                "Custom",
                widgets,
                ComponentGroup,
            )
        else:
            self._custom_group.update_components(widgets)
            # this will force group to be re-rendered
            self.select(self._custom_group.selector)

    def _reload_custom(self):
        self._init_custom(Preferences.acquire().get(self._custom_pref_path))

    def _init_var(self, master=None):
        self._var_init = True
        for widget_set in self.CLASSES:
            self.CLASSES[widget_set]["var"] = BooleanVar(master, False)

    def _widget_sets_as_menu(self):
        return [
            ("checkbutton",  # Type checkbutton
             i.capitalize(),  # Label as title case
             None,  # Image
             partial(self.collect_groups, i),  # The callback
             {"variable": self.CLASSES[i]["var"]}  # Additional config including the variable associated
             ) for i in self.CLASSES
        ]

    @property
    def selectors(self):
        return self._selectors

    def create_menu(self):
        return (
            (
                "command", "Reload custom widgets",
                get_icon_image("rotate_clockwise", 14, 14), self._reload_custom, {}
            ),
            (
                "command", "Search",
                get_icon_image("search", 14, 14), self.start_search, {}
            ),
            ("cascade", "Widget set", get_icon_image("blank", 14, 14), None, {"menu": (
                *self._widget_sets_as_menu(),
            )}),
        )

    def collect_groups(self, widget_set):
        for other_set in [i for i in self.CLASSES if i != widget_set]:
            self.CLASSES[other_set]["var"].set(False)
        self.CLASSES[widget_set]["var"].set(True)
        self._widget_set.set(widget_set)
        self._select_pane.clear_children()
        self._pool = {}
        components = self.CLASSES.get(widget_set)["widgets"]
        for component in components:
            group = component.group.name
            if group in self._pool:
                self._pool[group].append(Component(self._widget_pane.body, component))
            else:
                self._pool[group] = [Component(self._widget_pane.body, component)]
        self.render_groups()
        # component pool has changed so invalidate the cache
        self._component_cache = None
        self.set_pref("widget_set", widget_set)

    def get_components(self):
        if self._component_cache:
            # cache hit
            return self._component_cache
        # flatten component pool and store to cache
        self._component_cache = [j for i in self._pool.values() for j in i]
        self._component_cache.extend(
            [item for g in self._extern_groups for item in g.components]
        )
        return self._component_cache

    def select(self, selector):
        if self._selected is not None:
            self._selected.deselect()
        selector.select()
        self._selected = selector
        self._widget_pane.clear_children()

        if isinstance(selector.group, ComponentGroup):
            components = selector.group.components
        else:
            components = self._pool[selector.name]

        for component in components:
            component.pack(side="top", pady=2, fill="x")

    def _auto_select(self):
        # automatically pick a selector when no groups have
        # been explicitly selected and the pane is in limbo
        if self._selectors:
            self.select(self._selectors[0])
        else:
            self._widget_pane.clear_children()
            self._selected = None

    def render_groups(self):
        self._selectors = []
        for group in self._pool:
            self.add_selector(Selector(self._select_pane.body, text=group))
        self._auto_select()
        self.render_extern_groups()

    def render_extern_groups(self):
        for group in self._extern_groups:
            if group.supports(self._widget):
                self.add_selector(group.selector)
            else:
                self.remove_selector(group.selector)
                if self._selected == group.selector:
                    self._auto_select()

    def add_selector(self, selector):
        if selector in self._selectors:
            return
        self._selectors.append(selector)
        selector.bind("<Button-1>", lambda *_: self.select(selector))
        selector.pack(side="top", pady=2, fill="x")

    def remove_selector(self, selector):
        if selector in self._selectors:
            self._selectors.remove(selector)
        selector.pack_forget()

    def hide_selectors(self):
        for selector in self._selectors:
            selector.pack_forget()

    def show_selectors(self):
        for selector in self._selectors:
            selector.pack(side="top", pady=2, fill="x")

    def register_group(self, name, items, group_class, evaluator=None, component_class=None):
        group = group_class(self._widget_pane.body, name, items, evaluator, component_class)
        self._extern_groups.append(group)
        # link up selector and group
        group.selector = Selector(self._select_pane.body, text=group.name)
        group.selector.group = group
        self.render_extern_groups()
        return group

    def unregister_group(self, group):
        if group in self._extern_groups:
            self.remove_selector(group.selector)
            self._extern_groups.remove(group)
            self._auto_select()

    def on_select(self, widget):
        self._widget = widget
        self.render_extern_groups()

    def start_search(self, *_):
        super().start_search()
        self._widget_pane.scroll_to_start()
        if self._selected is not None:
            self._selected.deselect()
        self.hide_selectors()
        self._search_selector.pack(side="top", pady=2, fill="x")
        self._widget_pane.clear_children()
        # Display all components by running an empty query
        self.on_search_query("")

    def on_search_clear(self):
        super().on_search_clear()
        if self._selectors:
            self.select(self._selectors[0])
        self._search_selector.pack_forget()
        self.show_selectors()

    def on_search_query(self, query):
        for component in self.get_components():
            if query.lower() in component.component.display_name.lower():
                component.pack(side="top", pady=2, fill="x")
            else:
                component.pack_forget()
예제 #10
0
class Choice(Editor):
    class ChoiceItem(CompoundList.BaseItem):
        def render(self):
            if not self.value:
                Label(self, **self.style.dark_text, text="select",
                      anchor="w").pack(fill="both")
                return
            Label(self, **self.style.dark_text, text=self._value,
                  anchor="w").pack(fill="both")

    def __init__(self, master, style_def=None):
        super().__init__(master, style_def)
        if style_def is None:
            style_def = {}
        self.style_def = style_def
        self._spinner = Spinner(self, **self.style.dark_input)
        self._spinner.pack(fill="x")
        self._spinner.on_change(self.spinner_change)
        self.set_up()
        values = style_def.get("options", ())
        if values:
            if not style_def.get('allow_empty', True):
                self._spinner.set_values(('', *values))
            else:
                self._spinner.set_values(values)

    def set_up(self):
        self._spinner.set_item_class(Choice.ChoiceItem)

    def spinner_change(self, value):
        if self._on_change is not None:
            self._on_change(value)

    def set(self, value):
        # Convert to string as values of type _tkinter.Tcl_Obj are common in ttk and may cause unpredictable behaviour
        self._spinner.set(str(value))

    def get(self):
        return self._spinner.get()