def _create_widgets(self) -> None: self.__ports_left_listbox = ScrollbarListbox( self, values=self.__ports_left, extract_id=lambda prt: prt.id_, extract_text=lambda prt: prt.name, on_select_callback=self.__on_select_listbox_left, columns=[Column('Port', main=True, stretch=tk.YES)]) self.__mid_frame = ttk.Frame(self) self.__remove_port_button = ttk.Button( self.__mid_frame, text='<<', command=self.__remove_from_selected) self.__add_port_button = ttk.Button(self.__mid_frame, text='>>', command=self.__add_to_selected) self.__right_frame = ttk.Frame(self) self.__ports_right_listbox = ScrollbarListbox( self.__right_frame, values=self.__ports_right, extract_id=lambda prt: prt.id_, extract_text=lambda prt: prt.name, on_select_callback=self.__on_select_listbox_right, columns=[Column('Selected ports', main=True, stretch=tk.YES)]) self.__buttons_frame = ttk.Frame(self.__right_frame) self.__ok_button = ttk.Button(self.__buttons_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__buttons_frame, text='Cancel', command=self.destroy)
def _create_widgets(self) -> None: self.__taxonomy_tree = ScrollbarListbox(self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Amount')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) # Ports combobox self.__port_combobox_var = tk.StringVar(value=SELECT_PORT) self.__port_combobox_var.trace('w', self.__on_combobox_changed) # state='readonly' means you cannot write freely in the combobox self.__port_combobox = ttk.Combobox(self.__left_frame, state='readonly', textvariable=self.__port_combobox_var, font=FONT) ports_names = self.__state.model.get_all_ports_names() self.__port_combobox['values'] = sorted(ports_names) # C(r)ud Buttons self.__add_port_button = ttk.Button(self.__left_frame, text='Add', state=tk.NORMAL, command=self.__on_add) self.__rename_port_button = ttk.Button(self.__left_frame, text='Rename', state=tk.DISABLED, command=self.__on_rename) self.__remove_port_button = ttk.Button(self.__left_frame, text='Remove', state=tk.DISABLED, command=self.__remove) # Force connection self.__force_connection_checkbox_var = tk.BooleanVar(value=False) self.__force_connection_checkbox_var.trace('w', self.__on_force_connection_toggled) self.__force_connection_checkbox_label = ttk.Label(self.__left_frame, text='Force connection:') self.__force_connection_checkbox = ttk.Checkbutton(self.__left_frame, state=tk.DISABLED, variable=self.__force_connection_checkbox_var) # Force connection checkbox self.__compatible_with_edit_button = ttk.Button(self.__left_frame, text='Edit compatibility', command=self.__on_edit_compatible_with, state=tk.DISABLED) self.__compatible_with_listbox = ScrollbarListbox(self.__left_frame, extract_text=lambda prt: prt.name, extract_id=lambda prt: prt.id_, columns=[Column('Compatible with', main=True, stretch=tk.YES)]) self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__amount_spinbox_label = ttk.Label(self.__left_frame, text='Has:') self.__amount_spinbox_var = tk.IntVar(value='') self.__amount_spinbox_var.trace('w', self.__on_amount_changed) self.__amount_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, textvariable=self.__amount_spinbox_var) self.__all_children_amount_spinbox_label = ttk.Label(self.__left_frame, text='Children have:') self.__all_children_amount_spinbox_var = tk.IntVar(value='') self.__all_children_amount_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, textvariable=self.__all_children_amount_spinbox_var) self.__apply_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to all children', command=self.__apply_to_all_children)
def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, values=self.__state.model.taxonomy, ) self.__right_frame = ttk.Frame(self) self.__cmp_name_var = tk.StringVar() self.__cmp_name_var.set('') self.__cmp_name_label = ttk.Label(self.__right_frame, anchor=tk.CENTER, textvariable=self.__cmp_name_var, style='Big.TLabel') self.__rename_button = ttk.Button(self.__right_frame, text='Rename', command=self.__on_rename, state=tk.DISABLED) self.__add_sibling_button = ttk.Button(self.__right_frame, text='Add sibling', command=self.__on_add_sibling) self.__add_child_button = ttk.Button(self.__right_frame, text='Add child', command=self.__on_add_child) self.__remove_button = ttk.Button(self.__right_frame, text='Remove', command=self.__remove, state=tk.DISABLED) self.__remove_recursively_button = ttk.Button( self.__right_frame, text='Remove recursively', state=tk.DISABLED, command=lambda: self.__remove(recursively=True)) self.__create_taxonomy_button = ttk.Button( self, text="Create taxonomy", command=self.__on_create_taxonomy, width=14)
def _create_widgets(self) -> None: self.__main_frame = ttk.Frame(self) self.__labels_frame = ttk.Frame(self.__main_frame) self.__main_label = ttk.Label(self.__labels_frame, text=PROJECT_NAME, anchor=tk.CENTER, style='Big.TLabel') self.__secondary_label = ttk.Label(self.__labels_frame, text=PROJECT_DESCRIPTION, anchor=tk.CENTER) self.__version_label = ttk.Label( self.__labels_frame, text=f'{VERSION_STRING} {PROJECT_VERSION}', style='Small.TLabel', anchor=tk.CENTER) self.__create_new_project_button = ttk.Button( self.__main_frame, text='Create new project', command=self.__on_create_new_project) self.__open_project_button = ttk.Button( self.__main_frame, text='Open project', command=lambda: open_project(callback=self.__proceed)) self.__solve_button = ttk.Button(self.__main_frame, text='Solve...', command=self.__on_solve) if self.__has_recent_projects: self.__recent_projects_listbox = ScrollbarListbox( self, on_select_callback=self.__on_select_recent_project, heading=TREEVIEW_HEADING, extract_id=lambda x: self.__settings.recently_opened_projects. index(x), extract_text=lambda x: f'{x.root_name} ' f'(~/{extract_file_name(x.path)})', values=self.__settings.recently_opened_projects, has_scrollbars=False)
def _create_widgets(self) -> None: self.__constraints_listbox = ScrollbarListbox( self, columns=[Column('Type')], heading='Constraint', extract_id=lambda crt: crt.id_, extract_text=lambda crt: crt.name, extract_values=lambda crt: 'Simple' if isinstance(crt, SimpleConstraint) else 'Complex', on_select_callback=self.__on_select_callback, values=self.__state.model.get_all_constraints()) self.__right_frame = ttk.Frame(self) # Ctr label self.__ctr_label_var = tk.StringVar(value='') self.__ctr_label = ttk.Label(self.__right_frame, textvariable=self.__ctr_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__add_simple_constraint_button = ttk.Button( self.__right_frame, text='Add simple constraint', command=self.__on_add) self.__add_complex_constraint_button = ttk.Button( self.__right_frame, text='Add complex constraint', command=lambda: self.__on_add(complex_=True)) self.__edit_constraint_button = ttk.Button(self.__right_frame, text='Edit', state=tk.DISABLED, command=self.__on_edit) self.__remove_constraint_button = ttk.Button( self.__right_frame, text='Remove', state=tk.DISABLED, command=self.__remove_constraint)
class InstancesTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to set the number of instances of components. Attributes: __selected_component: Currently selected component in the components taxonomy view. """ def __init__(self, parent_notebook: ttk.Notebook): self.__state = State() self.__selected_component: Optional[Component] = None Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox(self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Count'), Column('Min'), Column('Max'), Column('Symmetry breaking?')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) self.__global_symmetry_breaking_frame = ttk.Frame(self.__left_frame) self.__global_symmetry_breaking_checkbox_label = ttk.Label(self.__global_symmetry_breaking_frame, text='Symmetry breaking\n' 'for all components:') self.__global_symmetry_breaking_checkbox_var = tk.BooleanVar(value=False) self.__global_symmetry_breaking_checkbox = ttk.Checkbutton(self.__global_symmetry_breaking_frame, variable=self.__global_symmetry_breaking_checkbox_var) self.__apply_global_symmetry_breaking_button = ttk.Button(self.__global_symmetry_breaking_frame, text='Apply', command=self.__apply_symmetry_breaking_for_all_components) self.__component_separator = ttk.Separator(self.__left_frame, orient=tk.HORIZONTAL) self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__symm_breaking_checkbox_var = tk.BooleanVar(value=False) self.__symm_breaking_checkbox_var.trace('w', self.__on_symmetry_breaking_toggled) self.__symm_breaking_checkbox_label = ttk.Label(self.__left_frame, text='Symmetry\nbreaking:') self.__symm_breaking_checkbox = ttk.Checkbutton(self.__left_frame, variable=self.__symm_breaking_checkbox_var) self.__exact_value_radiobutton_var = tk.BooleanVar(value=True) self.__exact_value_radiobutton_var.trace('w', self.__on_exact_value_radiobutton_changed) self.__exact_value_radiobutton = ttk.Radiobutton(self.__left_frame, value=True, text='Exact', state=tk.DISABLED, variable=self.__exact_value_radiobutton_var) self.__range_radiobutton = ttk.Radiobutton(self.__left_frame, text='Range', value=False, state=tk.DISABLED, variable=self.__exact_value_radiobutton_var) self.__exact_minimum_spinbox_label_var = tk.StringVar(value=EXACT_COUNT_LABEL_TEXT) self.__exact_minimum_spinbox_label = ttk.Label(self.__left_frame, textvariable=self.__exact_minimum_spinbox_label_var) self.__exact_minimum_spinbox_var = tk.IntVar(value='') self.__exact_minimum_spinbox_var.trace('w', self.__on_count_changed) self.__exact_minimum_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__exact_minimum_spinbox_var) self.__max_spinbox_label = ttk.Label(self.__left_frame, text='Max:') self.__max_spinbox_var = tk.IntVar() self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox(self.__left_frame, from_=1, to=math.inf, textvariable=self.__max_spinbox_var) self.__apply_symmetry_breaking_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to children', command=self.__apply_symmetry_breaking_to_all_children, style='SmallFont.TButton') self.__apply_count_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to all children', command=self.__apply_count_to_all_children) def _setup_layout(self): self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__left_frame.grid(row=0, column=0, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__global_symmetry_breaking_frame.grid(row=0, column=0, columnspan=4, sticky=tk.NSEW) self.__global_symmetry_breaking_frame.columnconfigure(0, weight=1) self.__global_symmetry_breaking_frame.columnconfigure(1, weight=1) self.__global_symmetry_breaking_frame.columnconfigure(2, weight=1) self.__global_symmetry_breaking_checkbox_label.grid(row=0, column=0, sticky=tk.W, pady=CONTROL_PAD_Y, padx=CONTROL_PAD_X) self.__global_symmetry_breaking_checkbox.grid(row=0, column=1, sticky=tk.W, pady=CONTROL_PAD_Y, padx=CONTROL_PAD_X) self.__apply_global_symmetry_breaking_button.grid(row=0, column=2, sticky=tk.EW, pady=CONTROL_PAD_Y, padx=CONTROL_PAD_X) self.__component_separator.grid(row=1, column=0, columnspan=4, sticky=tk.EW, pady=CONTROL_PAD_Y, padx=CONTROL_PAD_X) self.__cmp_label.grid(row=2, column=0, columnspan=4, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__symm_breaking_checkbox_label.grid(row=3, column=0, sticky=tk.W, pady=CONTROL_PAD_Y, padx=CONTROL_PAD_X) self.__symm_breaking_checkbox.grid(row=3, column=1, sticky=tk.NW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__apply_symmetry_breaking_to_all_children_button.grid(row=3, column=2, columnspan=2, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__exact_value_radiobutton.grid(row=4, column=0, sticky=tk.W, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__range_radiobutton.grid(row=4, column=1, sticky=tk.W, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__exact_minimum_spinbox_label.grid(row=5, column=0, sticky=tk.W, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__exact_minimum_spinbox.grid(row=5, column=1, sticky=tk.EW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__max_spinbox_label.grid(row=5, column=2, sticky=tk.W, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__max_spinbox.grid(row=5, column=3, sticky=tk.EW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__apply_count_to_all_children_button.grid(row=6, column=0, columnspan=4, sticky=tk.NSEW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) # Hide 'for all children' widgets self.__apply_symmetry_breaking_to_all_children_button.grid_forget() self.__max_spinbox_label.grid_forget() self.__max_spinbox.grid_forget() self.__apply_count_to_all_children_button.grid_forget() self.__left_frame.columnconfigure(0, weight=1, uniform='fred') self.__left_frame.columnconfigure(1, weight=1, uniform='fred') self.__left_frame.columnconfigure(2, weight=1, uniform='fred') self.__left_frame.columnconfigure(3, weight=1, uniform='fred') self.columnconfigure(0, weight=1, uniform='fred') self.columnconfigure(1, weight=3, uniform='fred') self.rowconfigure(0, weight=1) # SubscribesToListeners def _subscribe_to_events(self): pub.subscribe(self.__on_model_loaded, actions.MODEL_LOADED) pub.subscribe(self.__build_tree, actions.TAXONOMY_EDITED) pub.subscribe(self._reset, actions.RESET) @staticmethod def __extract_values(cmp: Component) -> Tuple[Any, ...]: """Extracts the data of the component to show in the taxonomy view. :param cmp: Component from which to extract the data. :return: Tuple containing data about component (exact count, min_count, max_count, apply symmetry breaking?). """ return (cmp.count if cmp.count else '', cmp.min_count if cmp.min_count is not None else '', cmp.max_count if cmp.max_count is not None else '', BOOLEAN_TO_STRING_DICT[cmp.symmetry_breaking]) def __on_model_loaded(self) -> None: """Executed whenever a model is loaded from file.""" self.__build_tree() def __build_tree(self) -> None: """Fills the tree view with components from model.""" self.__taxonomy_tree.set_items(self.__state.model.taxonomy) # Taxonomy Treeview def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ selected_cmp: Component = self.__state.model.get_component(id_=cmp_id) self.__selected_component = selected_cmp self.__cmp_label_var.set(trim_string(selected_cmp.name, length=LABEL_LENGTH)) radiobutton_val = selected_cmp.exact if selected_cmp.exact is not None else True self.__exact_value_radiobutton_var.set(radiobutton_val) # Enable symmetry breaking checkbox & spinbox change_controls_state(tk.NORMAL, self.__exact_minimum_spinbox, self.__symm_breaking_checkbox, self.__range_radiobutton, self.__exact_value_radiobutton, self.__exact_minimum_spinbox) if selected_cmp.is_leaf: # Set the spinbox values if selected_cmp.exact: set_spinbox_var_value(self.__exact_minimum_spinbox_var, selected_cmp.count) else: set_spinbox_var_value(self.__exact_minimum_spinbox_var, selected_cmp.min_count) set_spinbox_var_value(self.__max_spinbox_var, selected_cmp.max_count) self.__symm_breaking_checkbox_var.set(selected_cmp.symmetry_breaking) self.__apply_symmetry_breaking_to_all_children_button.grid_forget() self.__apply_count_to_all_children_button.grid_forget() else: # Show apply to all children self.__apply_symmetry_breaking_to_all_children_button.grid(row=3, column=2, columnspan=2, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__apply_count_to_all_children_button.grid(row=6, column=0, columnspan=4, sticky=tk.NSEW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) # Reset symmetry breaking for all components self.__symm_breaking_checkbox_var.set(False) # Resetable def _reset(self) -> None: self.__selected_component = None # Disable widgets change_controls_state(tk.DISABLED, self.__exact_minimum_spinbox, self.__symm_breaking_checkbox, self.__range_radiobutton, self.__exact_value_radiobutton, self.__exact_minimum_spinbox) # Hide 'for all children' widgets self.__apply_symmetry_breaking_to_all_children_button.grid_forget() self.__max_spinbox_label.grid_forget() self.__max_spinbox.grid_forget() self.__apply_count_to_all_children_button.grid_forget() # Set entries to default self.__exact_minimum_spinbox_var.set('') self.__max_spinbox_var.set('') self.__cmp_label_var.set('') self.__symm_breaking_checkbox_var.set(False) self.__global_symmetry_breaking_checkbox_var.set(False) # Clear the tree self.__taxonomy_tree.set_items([]) def __on_symmetry_breaking_toggled(self, *_) -> None: """Executed whenever the __symmetry_breaking_checkbox is toggled""" if self.__selected_component and self.__selected_component.is_leaf: self.__selected_component.symmetry_breaking = self.__symm_breaking_checkbox_var.get() self.__taxonomy_tree.update_values(self.__selected_component) def __on_count_changed(self, *_) -> None: """Executed whenever the __exact_minimum_spinbox value changes.""" if self.__selected_component: exact = self.__exact_value_radiobutton_var.get() try: value = self.__exact_minimum_spinbox_var.get() if exact: if self.__selected_component.is_leaf: self.__selected_component.count = value # Reset min/max counter self.__selected_component.min_count = None self.__selected_component.max_count = None else: max_value = self.__max_spinbox_var.get() if self.__selected_component.is_leaf: self.__selected_component.min_count = value # Reset count property self.__selected_component.count = None if value >= max_value: new_max_value = value + 1 self.__max_spinbox_var.set(new_max_value) if self.__selected_component.is_leaf: self.__selected_component.max_count = new_max_value except tk.TclError: self.__selected_component.count = None # Reset both self.__selected_component.min_count = None finally: self.__taxonomy_tree.update_values(self.__selected_component) def __apply_count_to_all_children(self) -> None: """Executed whenever the __apply_count_to_all_children_button is pressed.""" if self.__selected_component: exact = self.__exact_value_radiobutton_var.get() if exact: count = None try: count = self.__exact_minimum_spinbox_var.get() except tk.TclError: pass finally: updated_cmps = self.__state.model.set_components_leaf_children_properties(self.__selected_component, exact=True, count=count, min_count=None, max_count=None) else: min_count = None max_count = None try: min_count = self.__exact_minimum_spinbox_var.get() max_count = self.__max_spinbox_var.get() except tk.TclError: pass finally: updated_cmps = self.__state.model.set_components_leaf_children_properties(self.__selected_component, exact=False, count=None, min_count=min_count, max_count=max_count) self.__taxonomy_tree.update_values(*updated_cmps) def __apply_symmetry_breaking_to_all_children(self): """Executed whenever the __apply_symmetry_breaking_to_all_children_button is pressed.""" if self.__selected_component: symm_breaking = True try: symm_breaking = self.__symm_breaking_checkbox_var.get() except tk.TclError: pass finally: updated_cmps = self.__state.model.set_components_leaf_children_properties(self.__selected_component, symmetry_breaking=symm_breaking) self.__taxonomy_tree.update_values(*updated_cmps) def __on_exact_value_radiobutton_changed(self, *_): """Executed whenever the __exact_value_radiobutton's value changes.""" if self.__selected_component: self.__exact_minimum_spinbox_var.set(0) self.__max_spinbox_var.set(0) self.__selected_component.count = None self.__selected_component.min_count = None self.__selected_component.max_count = None exact_value = self.__exact_value_radiobutton_var.get() if exact_value: self.__max_spinbox_label.grid_forget() # Hide 'Max' label and spinbox self.__max_spinbox.grid_forget() self.__exact_minimum_spinbox_label_var.set(EXACT_COUNT_LABEL_TEXT) else: # Restore max widgets self.__max_spinbox_label.grid(row=5, column=2, sticky=tk.W, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__max_spinbox.grid(row=5, column=3, sticky=tk.EW, padx=CONTROL_PAD_X, pady=CONTROL_PAD_Y) self.__exact_minimum_spinbox_label_var.set(MIN_COUNT_LABEL_TEXT) if self.__selected_component.is_leaf: self.__selected_component.exact = exact_value self.__taxonomy_tree.update_values(self.__selected_component) def __on_max_changed(self, *_): """Executed whenever the __max_spinbox value changes.""" if self.__selected_component: try: max_value = self.__max_spinbox_var.get() if self.__selected_component.is_leaf: self.__selected_component.max_count = max_value min_value = self.__exact_minimum_spinbox_var.get() if max_value <= min_value and max_value >= 1: new_min_value = max_value - 1 self.__exact_minimum_spinbox_var.set(new_min_value) if self.__selected_component.is_leaf: self.__selected_component.min_count = new_min_value except tk.TclError: pass finally: self.__taxonomy_tree.update_values(self.__selected_component) def __apply_symmetry_breaking_for_all_components(self): """Executed whenever the __apply_global_symmetry_breaking_button is pressed.""" if self.__taxonomy_tree: symm_breaking = self.__global_symmetry_breaking_checkbox_var.get() updated_cmps = self.__state.model.set_all_leaf_components_properties(symmetry_breaking=symm_breaking) self.__taxonomy_tree.update_values(*updated_cmps)
def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox(self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Count'), Column('Min'), Column('Max'), Column('Symmetry breaking?')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) self.__global_symmetry_breaking_frame = ttk.Frame(self.__left_frame) self.__global_symmetry_breaking_checkbox_label = ttk.Label(self.__global_symmetry_breaking_frame, text='Symmetry breaking\n' 'for all components:') self.__global_symmetry_breaking_checkbox_var = tk.BooleanVar(value=False) self.__global_symmetry_breaking_checkbox = ttk.Checkbutton(self.__global_symmetry_breaking_frame, variable=self.__global_symmetry_breaking_checkbox_var) self.__apply_global_symmetry_breaking_button = ttk.Button(self.__global_symmetry_breaking_frame, text='Apply', command=self.__apply_symmetry_breaking_for_all_components) self.__component_separator = ttk.Separator(self.__left_frame, orient=tk.HORIZONTAL) self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__symm_breaking_checkbox_var = tk.BooleanVar(value=False) self.__symm_breaking_checkbox_var.trace('w', self.__on_symmetry_breaking_toggled) self.__symm_breaking_checkbox_label = ttk.Label(self.__left_frame, text='Symmetry\nbreaking:') self.__symm_breaking_checkbox = ttk.Checkbutton(self.__left_frame, variable=self.__symm_breaking_checkbox_var) self.__exact_value_radiobutton_var = tk.BooleanVar(value=True) self.__exact_value_radiobutton_var.trace('w', self.__on_exact_value_radiobutton_changed) self.__exact_value_radiobutton = ttk.Radiobutton(self.__left_frame, value=True, text='Exact', state=tk.DISABLED, variable=self.__exact_value_radiobutton_var) self.__range_radiobutton = ttk.Radiobutton(self.__left_frame, text='Range', value=False, state=tk.DISABLED, variable=self.__exact_value_radiobutton_var) self.__exact_minimum_spinbox_label_var = tk.StringVar(value=EXACT_COUNT_LABEL_TEXT) self.__exact_minimum_spinbox_label = ttk.Label(self.__left_frame, textvariable=self.__exact_minimum_spinbox_label_var) self.__exact_minimum_spinbox_var = tk.IntVar(value='') self.__exact_minimum_spinbox_var.trace('w', self.__on_count_changed) self.__exact_minimum_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__exact_minimum_spinbox_var) self.__max_spinbox_label = ttk.Label(self.__left_frame, text='Max:') self.__max_spinbox_var = tk.IntVar() self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox(self.__left_frame, from_=1, to=math.inf, textvariable=self.__max_spinbox_var) self.__apply_symmetry_breaking_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to children', command=self.__apply_symmetry_breaking_to_all_children, style='SmallFont.TButton') self.__apply_count_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to all children', command=self.__apply_count_to_all_children)
class TaxonomyTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to create, edit and remove components. Attributes: __selected_component: Currently selected component in the components taxonomy view. """ def __init__(self, parent_notebook): self.__state: State = State() self.__selected_component: Optional[Component] = None Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, values=self.__state.model.taxonomy, ) self.__right_frame = ttk.Frame(self) self.__cmp_name_var = tk.StringVar() self.__cmp_name_var.set('') self.__cmp_name_label = ttk.Label(self.__right_frame, anchor=tk.CENTER, textvariable=self.__cmp_name_var, style='Big.TLabel') self.__rename_button = ttk.Button(self.__right_frame, text='Rename', command=self.__on_rename, state=tk.DISABLED) self.__add_sibling_button = ttk.Button(self.__right_frame, text='Add sibling', command=self.__on_add_sibling) self.__add_child_button = ttk.Button(self.__right_frame, text='Add child', command=self.__on_add_child) self.__remove_button = ttk.Button(self.__right_frame, text='Remove', command=self.__remove, state=tk.DISABLED) self.__remove_recursively_button = ttk.Button( self.__right_frame, text='Remove recursively', state=tk.DISABLED, command=lambda: self.__remove(recursively=True)) self.__create_taxonomy_button = ttk.Button( self, text="Create taxonomy", command=self.__on_create_taxonomy, width=14) def _setup_layout(self): self.__taxonomy_tree.grid(row=0, column=0, sticky=tk.NSEW) self.__right_frame.grid(row=0, column=1, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__cmp_name_label.grid(row=0, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__rename_button.grid(row=1, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_sibling_button.grid(row=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_child_button.grid(row=3, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__remove_button.grid(row=4, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__remove_recursively_button.grid(row=5, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__create_taxonomy_button.grid(row=1, column=0, padx=30, pady=5) # Hide widgets self.rowconfigure(0, weight=1) self.columnconfigure(1, weight=1, uniform='fred') self.columnconfigure(0, weight=3, uniform='fred') self.__right_frame.columnconfigure(0, weight=1) # SubscribesToListeners def _subscribe_to_events(self): pub.subscribe(self.__on_model_loaded, actions.MODEL_LOADED) pub.subscribe(self._reset, actions.RESET) # Taxonomy Treeview def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ selected_cmp: Component = self.__state.model.get_component(id_=cmp_id) self.__selected_component = selected_cmp self.__cmp_name_var.set(trim_string(selected_cmp.name, length=22)) change_controls_state(tk.NORMAL, self.__remove_button, self.__remove_recursively_button, self.__rename_button) def __on_model_loaded(self): """Executed whenever a model is loaded from file.""" self._reset() self.__build_tree() def __build_tree(self) -> None: """Fills the tree view with components from model.""" self.__taxonomy_tree.set_items(self.__state.model.taxonomy) # Resetable def _reset(self) -> None: self.__taxonomy_tree.set_items([]) self.__selected_component = None self.__cmp_name_var.set('') change_controls_state(tk.DISABLED, self.__remove_button, self.__remove_recursively_button, self.__rename_button) # Class-specific def __add(self, cmp_name: str, level: int, parent_id: Optional[int]) -> None: """Executed after creating of a new component. :param cmp_name: Name of a new component :param level: Level of the new component. :param parent_id: Id of the parent of the new component. """ cmp = Component(cmp_name, level, parent_id=parent_id, is_leaf=True, symmetry_breaking=True) self.__state.model.add_component(cmp) self.__taxonomy_tree.add_item(cmp) self.__selected_component = cmp self.__cmp_name_var.set(trim_string(cmp.name, length=22)) change_controls_state(tk.NORMAL, self.__remove_button, self.__remove_recursively_button, self.__rename_button) pub.sendMessage(actions.TAXONOMY_EDITED) def __on_add_sibling(self) -> None: """Executed whenever the __add_sibling_button is pressed.""" sibling_name = BASE_COMPONENT_NAME if self.__selected_component is None else self.__selected_component.name level = 0 if self.__selected_component is None else self.__selected_component.level parent_id = None if self.__selected_component is None else self.__selected_component.parent_id AskStringWindow( self, lambda cmp_name: self.__add(cmp_name, level, parent_id), 'Add sibling', f'Enter name of sibling of the {sibling_name} component.') def __on_add_child(self) -> None: """Executed whenever the __add_child_button is pressed.""" sibling_name = BASE_COMPONENT_NAME if self.__selected_component is None else self.__selected_component.name level = 0 if self.__selected_component is None else self.__selected_component.level + 1 parent_id = None if self.__selected_component is None else self.__selected_component.id_ AskStringWindow( self, lambda cmp_name: self.__add(cmp_name, level, parent_id), 'Add sibling', f'Enter name of child of the {sibling_name} component.') def __rename(self, new_name: str) -> None: """Executed after renaming __selected_component. :param new_name: New name of the component. """ cmp = self.__state.model.rename_component(self.__selected_component, new_name) self.__cmp_name_var.set(trim_string(cmp.name, length=22)) self.__taxonomy_tree.rename_item(self.__selected_component) pub.sendMessage(actions.TAXONOMY_EDITED) def __on_rename(self) -> None: """Executed whenever the __rename_button is pressed.""" if self.__selected_component: AskStringWindow( self, self.__rename, 'Rename component', f'Enter new name for "{self.__selected_component.name}" component.', string=self.__selected_component.name) def __remove(self, recursively=False) -> None: """Executed whenever the __rename_button or __remove_recursively_button is pressed. :param recursively: True if __remove_recursively_button is pressed. Removes the selected component and all of its children; Otherwise set to False and removes only the __selected_component, setting the parent_id of its children to __selected_component.parent_id. """ if self.__selected_component: if recursively: _, removed_ctrs = self.__state.model.remove_component_recursively( self.__selected_component) self.__taxonomy_tree.remove_item_recursively( self.__selected_component) else: _, removed_ctrs = self.__state.model.remove_component_preserve_children( self.__selected_component) self.__taxonomy_tree.remove_item_preserve_children( self.__selected_component) # Prompt about removing the constraints if removed_ctrs: removed_ctrs_names_list_string = f' {PUNCTUATOR_SYMBOL} ' removed_ctrs_names_list_string += f"\n {PUNCTUATOR_SYMBOL} ".join( [ctr.name for ctr in removed_ctrs]) messagebox.showwarning( message=f'Removed component was present in constraints.' f'\nThe following constraints were thus removed:' f'\n{removed_ctrs_names_list_string}', parent=self) # Disable constrols change_controls_state(tk.DISABLED, self.__remove_button, self.__remove_recursively_button, self.__rename_button) self.__selected_component = None self.__cmp_name_var.set('') pub.sendMessage(actions.TAXONOMY_EDITED) def __create_taxonomy(self): """Executes when the taxonomy is created (in the CreateTaxonomyWindow).""" self.__build_tree() pub.sendMessage(actions.TAXONOMY_EDITED) def __on_create_taxonomy(self) -> None: """Executed whenever the __create_taxonomy_button is pressed.""" if self.__state.model.taxonomy: answer = messagebox.askyesno( 'Create taxonomy', 'Warning: taxonomy has already been created.\n' 'If you use this option again, previous taxonomy will be ' "overwritten, and you will lose all constraints' data and " "possibly ports, resources, instances, associations" 'instances etc.\nIf you plan to make simple changes, use ' 'options such as "Create sibling", "Create child", etc. ' 'on the right tab.', icon=tk.messagebox.WARNING, parent=self) if not answer: return CreateTaxonomyWindow(self, self.__create_taxonomy)
class InitialWindow(HasCommonSetup, Window): """Presents the initial window right after opening the program. Attributes: __callback: Callback function executed when new project is created / project is loaded from file, before destroying this window. __has_recent_projects: Evaluates to True if __settings contain recently_opened_projects; otherwise evaluates to False. Indicates whether to show the "Recently opened projects" listbox. """ def __init__(self, parent_frame, callback: Callable): self.__state = State() self.__settings = Settings.get_settings() self.__callback = callback self.__has_recent_projects = len( self.__settings.recently_opened_projects) > 0 Window.__init__(self, parent_frame, WINDOW_TITLE) HasCommonSetup.__init__(self) def _create_widgets(self) -> None: self.__main_frame = ttk.Frame(self) self.__labels_frame = ttk.Frame(self.__main_frame) self.__main_label = ttk.Label(self.__labels_frame, text=PROJECT_NAME, anchor=tk.CENTER, style='Big.TLabel') self.__secondary_label = ttk.Label(self.__labels_frame, text=PROJECT_DESCRIPTION, anchor=tk.CENTER) self.__version_label = ttk.Label( self.__labels_frame, text=f'{VERSION_STRING} {PROJECT_VERSION}', style='Small.TLabel', anchor=tk.CENTER) self.__create_new_project_button = ttk.Button( self.__main_frame, text='Create new project', command=self.__on_create_new_project) self.__open_project_button = ttk.Button( self.__main_frame, text='Open project', command=lambda: open_project(callback=self.__proceed)) self.__solve_button = ttk.Button(self.__main_frame, text='Solve...', command=self.__on_solve) if self.__has_recent_projects: self.__recent_projects_listbox = ScrollbarListbox( self, on_select_callback=self.__on_select_recent_project, heading=TREEVIEW_HEADING, extract_id=lambda x: self.__settings.recently_opened_projects. index(x), extract_text=lambda x: f'{x.root_name} ' f'(~/{extract_file_name(x.path)})', values=self.__settings.recently_opened_projects, has_scrollbars=False) def _setup_layout(self) -> None: self.__labels_frame.grid(row=0, sticky=tk.NSEW) self.__main_label.grid(row=0, sticky=tk.EW + tk.S, pady=CONTROL_PAD_Y) self.__secondary_label.grid(row=1, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__version_label.grid(row=2, sticky=tk.EW + tk.N, pady=CONTROL_PAD_Y) self.__labels_frame.rowconfigure(0, weight=1) self.__labels_frame.rowconfigure(2, weight=1) self.__labels_frame.columnconfigure(0, weight=1) self.__create_new_project_button.grid(row=1, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__open_project_button.grid(row=2, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__solve_button.grid(row=3, sticky=tk.EW, pady=CONTROL_PAD_Y) if self.__has_recent_projects: self.__recent_projects_listbox.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__main_frame.grid(row=0, column=1, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) else: self.__main_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.columnconfigure(0, weight=1) self.__main_frame.rowconfigure(0, weight=1) self.__main_frame.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self._set_geometry(height=WINDOW_HEIGHT, width_ratio=WINDOW_WIDTH_RATIO) def __on_select_recent_project(self, project_index: int) -> None: """Executed whenever a project is selected from the list of recently opened projects. :param project_index: Index of the project in the list. """ project_info = self.__settings.recently_opened_projects[project_index] load_from_file(project_info.path, callback=self.__proceed) # self.__open_project(project_info.path) def __create_new_project(self, root_name: str) -> None: """Creates new project with given root component name.""" self.__state.model = Model() self.__state.model.set_root_name(root_name) self.__callback() self.destroy() def __on_create_new_project(self) -> None: """Executed whenever __create_new_project_button is pressed.""" AskStringWindow(self, self.__create_new_project, window_title='Set root name', prompt_text='Enter name of the root component') def __on_solve(self) -> None: """Executed whenever __create_new_project_button is pressed.""" SolveWindow(self.__main_frame) def __proceed(self) -> None: """Executes callback and destroys initial window.""" self.__callback() self.destroy()
class SimpleConstraintWindow(HasCommonSetup, Window): """Used create/edit SimpleConstraints. Attributes: __callback: Callback function to be executed after pressing the OK button on this Window. __constraint: SimpleConstraint to add / create. __components_ids: List of component ids concerned by this constraint. __selected_taxonomy_tree_item: Currently selected component in the components taxonomy view. __selected_listbox_item: Currently selected component in the selected components listview. """ def __init__(self, parent_frame, callback: Optional[Callable], constraint: Optional[SimpleConstraint] = None): self.__state = State() self.__callback = callback self.__constraint: SimpleConstraint = copy.deepcopy(constraint) if constraint is not None \ else SimpleConstraint() self.__components_ids = [] if constraint is None else [ *constraint.components_ids ] # Deep copy of component ids self.__selected_taxonomy_tree_item: Optional[Component] = None self.__selected_listbox_item: Optional[Component] = None Window.__init__(self, parent_frame, WINDOW_TITLE) HasCommonSetup.__init__(self) # HasCommonSetup def _create_widgets(self) -> None: self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, values=self.__state.model.taxonomy) self.__mid_frame = ttk.Frame(self) self.__add_component_button = ttk.Button( self.__mid_frame, text='>>', command=self.__add_to_selected) self.__add_components_recursively_button = ttk.Button( self.__mid_frame, text='>> (recursively)', command=self.__add_to_selected_recursively) self.__remove_component_button = ttk.Button( self.__mid_frame, text='<<', command=self.__remove_from_selected) self.__right_frame = ttk.Frame(self) self.__components_listbox = ScrollbarListbox( self.__right_frame, values=self.__state.model.get_components_by_ids( self.__constraint.components_ids), extract_id=lambda cmp: cmp.id_, extract_text=lambda cmp: cmp.name, on_select_callback=self.__on_select_listbox_component, columns=[Column('Selected components', main=True, stretch=tk.YES)]) # Name self.__name_entry_var = tk.StringVar(value=self.__constraint.name) self.__name_entry_label = ttk.Label(self.__right_frame, text='Name:') self.__name_entry = ttk.Entry(self.__right_frame, textvariable=self.__name_entry_var, font=FONT) # Description self.__description_text_label = ttk.Label(self.__right_frame, text='Description:') self.__description_text = tk.Text(self.__right_frame, height=2, font=FONT) if self.__constraint.description: self.__description_text.insert(tk.INSERT, self.__constraint.description) # Distinct checkbox self.__distinct_checkbox_var = tk.BooleanVar( value=self.__constraint.distinct) self.__distinct_checkbox_label = ttk.Label(self.__right_frame, text='Distinct?') self.__distinct_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__distinct_checkbox_var) # Has min checkbox self.__has_min_checkbox_var = tk.BooleanVar( value=self.__constraint.min_ is not None) self.__has_min_checkbox_var.trace('w', self.__on_has_min_changed) self.__has_min_checkbox_label = ttk.Label(self.__right_frame, text='Has min?') self.__has_min_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__has_min_checkbox_var) # Min spinbox min_var_value = self.__constraint.min_ if self.__constraint.min_ is not None else '' self.__min_spinbox_var = tk.IntVar(value=min_var_value) self.__min_spinbox_var.trace('w', self.__on_min_changed) self.__min_spinbox = ttk.Spinbox( self.__right_frame, from_=0, to=math.inf, textvariable=self.__min_spinbox_var, state=tk.NORMAL if self.__constraint.min_ is not None else tk.DISABLED) # Has max checkbox self.__has_max_checkbox_var = tk.BooleanVar( value=self.__constraint.max_ is not None) self.__has_max_checkbox_var.trace('w', self.__on_has_max_changed) self.__has_max_checkbox_label = ttk.Label(self.__right_frame, text='Has max?') self.__has_max_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__has_max_checkbox_var) # Max spinbox max_var_value = self.__constraint.max_ if self.__constraint.max_ is not None else '' self.__max_spinbox_var = tk.IntVar(value=max_var_value) self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox( self.__right_frame, from_=0, to=math.inf, textvariable=self.__max_spinbox_var, state=tk.NORMAL if self.__constraint.max_ is not None else tk.DISABLED) # Buttons frame self.__ok_button = ttk.Button(self.__right_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__right_frame, text='Cancel', command=self.destroy) def _setup_layout(self) -> None: self.__taxonomy_tree.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__mid_frame.grid(row=0, column=1) self.__remove_component_button.grid(row=1, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_component_button.grid(row=2, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_components_recursively_button.grid(row=3, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__right_frame.grid(row=0, column=2, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__name_entry_label.grid(row=0, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__name_entry.grid(row=0, column=1, columnspan=3, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__description_text_label.grid(row=1, column=0, sticky=tk.NW, pady=CONTROL_PAD_Y) self.__description_text.grid(row=1, column=1, columnspan=3, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__distinct_checkbox_label.grid(row=2, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__distinct_checkbox.grid(row=2, column=1, columnspan=3, sticky=tk.E, pady=CONTROL_PAD_Y) self.__has_min_checkbox_label.grid(row=3, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__has_min_checkbox.grid(row=3, column=1, sticky=tk.E, pady=CONTROL_PAD_Y) self.__has_max_checkbox_label.grid(row=3, column=2, sticky=tk.W, pady=CONTROL_PAD_Y) self.__has_max_checkbox.grid(row=3, column=3, sticky=tk.E, pady=CONTROL_PAD_Y) self.__min_spinbox.grid(row=4, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__max_spinbox.grid(row=4, column=2, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__components_listbox.grid(row=5, column=0, columnspan=4, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__ok_button.grid(row=6, column=0, columnspan=2, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__cancel_button.grid(row=6, column=2, columnspan=2, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__right_frame.rowconfigure(1, weight=1) self.__right_frame.rowconfigure(5, weight=2) self.__right_frame.columnconfigure(1, weight=1) self.__right_frame.columnconfigure(3, weight=1) self.columnconfigure(0, weight=1, uniform='fred') self.columnconfigure(2, weight=1, uniform='fred') self.rowconfigure(0, weight=1) self._set_geometry() # Taxonomy Treeview def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ self.__selected_taxonomy_tree_item = self.__state.model.get_component( id_=cmp_id) def __on_select_listbox_component(self, cmp_id: int) -> None: """Executed whenever a listview item is selected (by mouse click). :param cmp_id: Id of the selected component. """ self.__selected_listbox_item = self.__state.model.get_component( id_=cmp_id) def __add_to_selected(self): """Adds the __selected_taxonomy_tree_item to the list of selected component ids.""" if self.__selected_taxonomy_tree_item: if self.__selected_taxonomy_tree_item.id_ not in self.__components_ids: self.__components_ids.append( self.__selected_taxonomy_tree_item.id_) self.__components_listbox.add_item( self.__selected_taxonomy_tree_item) self.__selected_listbox_item = self.__selected_taxonomy_tree_item self.__selected_taxonomy_tree_item = None def __add_to_selected_recursively(self): """Adds the __selected_taxonomy_tree_item and all its children to the list of selected component ids.""" if self.__selected_taxonomy_tree_item: for c in self.__state.model.get_components_children( self.__selected_taxonomy_tree_item): if c.id_ not in self.__components_ids: self.__components_ids.append(c.id_) self.__components_listbox.add_item( c, select_item=False) # Append children self.__components_ids.append( self.__selected_taxonomy_tree_item.id_) self.__components_listbox.add_item( self.__selected_taxonomy_tree_item) self.__selected_listbox_item = self.__selected_taxonomy_tree_item self.__selected_taxonomy_tree_item = None def __remove_from_selected(self): """Adds the __selected_taxonomy_tree_item to the list of selected component ids.""" if self.__selected_listbox_item: self.__components_ids.remove(self.__selected_listbox_item.id_) self.__components_listbox.remove_item_recursively( self.__selected_listbox_item) self.__selected_taxonomy_tree_item = self.__selected_listbox_item self.__taxonomy_tree.select_item(self.__selected_listbox_item) self.__selected_listbox_item = None def __on_has_min_changed(self, *_): """Executed whenever the __has_min_checkbox is toggled""" has_min = self.__has_min_checkbox_var.get() if has_min: self.__min_spinbox.config(state=tk.ACTIVE) self.__min_spinbox_var.set(0) else: self.__min_spinbox_var.set('') self.__min_spinbox.config(state=tk.DISABLED) def __on_has_max_changed(self, *_): """Executed whenever the __has_max_checkbox is toggled""" has_max = self.__has_max_checkbox_var.get() if has_max: self.__max_spinbox.config(state=tk.ACTIVE) has_min = self.__has_min_checkbox_var.get() if has_min: min_ = self.__min_spinbox_var.get() self.__max_spinbox_var.set(min_) else: self.__max_spinbox_var.set(0) self.__constraint.max_ = 0 else: self.__max_spinbox_var.set('') self.__max_spinbox.config(state=tk.DISABLED) def __on_min_changed(self, *_): """Executed whenever the __min_spinbox value changes.""" try: min_ = self.__min_spinbox_var.get() has_max = self.__has_max_checkbox_var.get() if has_max: max_ = self.__max_spinbox_var.get() if min_ > max_: self.__max_spinbox_var.set(min_) except tk.TclError: pass def __on_max_changed(self, *_): """Executed whenever the __max_spinbox value changes.""" try: max_ = self.__max_spinbox_var.get() has_min = self.__has_min_checkbox_var.get() if has_min: min_ = self.__min_spinbox_var.get() if min_ > max_: self.__min_spinbox_var.set(max_) except tk.TclError: pass def __ok(self): """Executed whenever the __ok_button is pressed.""" min_ = None max_ = None if self.__has_min_checkbox_var.get(): # If component has min try: min_ = self.__min_spinbox_var.get() except tk.TclError: min_ = None if self.__has_max_checkbox_var.get(): # If component has max try: max_ = self.__max_spinbox_var.get() except tk.TclError: max_ = None try: name = self.__name_entry_var.get() # Rewrite values if they are correct self.__constraint.name = name self.__constraint.description = self.__description_text.get( 1.0, tk.END) self.__constraint.components_ids = self.__components_ids self.__constraint.distinct = self.__distinct_checkbox_var.get() self.__constraint.min_ = min_ self.__constraint.max_ = max_ # If this SimpleConstraint is not an independent constraint, but a part of a ComplexConstraint, # name is checked against the names of other SimpleConstraints in the antecedent/consequent # of the ComplexConstraint, # Otherwise, it is checked against all the SimpleConstraint names in model. self.__callback(self.__constraint) self.grab_release() self.destroy() except BGError as e: messagebox.showerror('Error', e.message, parent=self)
def _create_widgets(self) -> None: self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, values=self.__state.model.taxonomy) self.__mid_frame = ttk.Frame(self) self.__add_component_button = ttk.Button( self.__mid_frame, text='>>', command=self.__add_to_selected) self.__add_components_recursively_button = ttk.Button( self.__mid_frame, text='>> (recursively)', command=self.__add_to_selected_recursively) self.__remove_component_button = ttk.Button( self.__mid_frame, text='<<', command=self.__remove_from_selected) self.__right_frame = ttk.Frame(self) self.__components_listbox = ScrollbarListbox( self.__right_frame, values=self.__state.model.get_components_by_ids( self.__constraint.components_ids), extract_id=lambda cmp: cmp.id_, extract_text=lambda cmp: cmp.name, on_select_callback=self.__on_select_listbox_component, columns=[Column('Selected components', main=True, stretch=tk.YES)]) # Name self.__name_entry_var = tk.StringVar(value=self.__constraint.name) self.__name_entry_label = ttk.Label(self.__right_frame, text='Name:') self.__name_entry = ttk.Entry(self.__right_frame, textvariable=self.__name_entry_var, font=FONT) # Description self.__description_text_label = ttk.Label(self.__right_frame, text='Description:') self.__description_text = tk.Text(self.__right_frame, height=2, font=FONT) if self.__constraint.description: self.__description_text.insert(tk.INSERT, self.__constraint.description) # Distinct checkbox self.__distinct_checkbox_var = tk.BooleanVar( value=self.__constraint.distinct) self.__distinct_checkbox_label = ttk.Label(self.__right_frame, text='Distinct?') self.__distinct_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__distinct_checkbox_var) # Has min checkbox self.__has_min_checkbox_var = tk.BooleanVar( value=self.__constraint.min_ is not None) self.__has_min_checkbox_var.trace('w', self.__on_has_min_changed) self.__has_min_checkbox_label = ttk.Label(self.__right_frame, text='Has min?') self.__has_min_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__has_min_checkbox_var) # Min spinbox min_var_value = self.__constraint.min_ if self.__constraint.min_ is not None else '' self.__min_spinbox_var = tk.IntVar(value=min_var_value) self.__min_spinbox_var.trace('w', self.__on_min_changed) self.__min_spinbox = ttk.Spinbox( self.__right_frame, from_=0, to=math.inf, textvariable=self.__min_spinbox_var, state=tk.NORMAL if self.__constraint.min_ is not None else tk.DISABLED) # Has max checkbox self.__has_max_checkbox_var = tk.BooleanVar( value=self.__constraint.max_ is not None) self.__has_max_checkbox_var.trace('w', self.__on_has_max_changed) self.__has_max_checkbox_label = ttk.Label(self.__right_frame, text='Has max?') self.__has_max_checkbox = ttk.Checkbutton( self.__right_frame, variable=self.__has_max_checkbox_var) # Max spinbox max_var_value = self.__constraint.max_ if self.__constraint.max_ is not None else '' self.__max_spinbox_var = tk.IntVar(value=max_var_value) self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox( self.__right_frame, from_=0, to=math.inf, textvariable=self.__max_spinbox_var, state=tk.NORMAL if self.__constraint.max_ is not None else tk.DISABLED) # Buttons frame self.__ok_button = ttk.Button(self.__right_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__right_frame, text='Cancel', command=self.destroy)
def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, values=self.__state.model.taxonomy, columns=[Column('Has association?'), Column('Min'), Column('Max')]) self.__left_frame = ttk.Frame(self) # Cmp label self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, anchor=tk.CENTER, style='Big.TLabel') # Has association checkbox self.__has_association_checkbox_var = tk.BooleanVar(value=False) self.__has_association_checkbox_var.trace( 'w', self.__on_has_association_changed) self.__has_association_checkbox_label = ttk.Label( self.__left_frame, text='Has association?') self.__has_association_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_association_checkbox_var) # Has min checkbox self.__has_min_checkbox_var = tk.BooleanVar(value=False) self.__has_min_checkbox_var.trace('w', self.__on_has_min_changed) self.__has_min_checkbox_label = ttk.Label(self.__left_frame, text='Has min?') self.__has_min_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_min_checkbox_var) # Min spinbox self.__min_spinbox_var = tk.IntVar(value='') self.__min_spinbox_var.trace('w', self.__on_min_changed) self.__min_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__min_spinbox_var, font=FONT) # Has max checkbox self.__has_max_checkbox_var = tk.BooleanVar(value=False) self.__has_max_checkbox_var.trace('w', self.__on_has_max_changed) self.__has_max_checkbox_label = ttk.Label(self.__left_frame, text='Has max?') self.__has_max_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_max_checkbox_var) # Max spinbox self.__max_spinbox_var = tk.IntVar(value='') self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__max_spinbox_var, font=FONT)
class ResourcesTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to create, edit and remove components. Attributes: __selected_resource: Currently selected resource in the resources listview. __selected_component: Currently selected component in the components taxonomy view. """ def __init__(self, parent_notebook): self.__state = State() self.__selected_resource: Optional[Resource] = None self.__selected_component: Optional[Component] = None Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Produces')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) # Resources combobox self.__resource_combobox_var = tk.StringVar(value=SELECT_RESOURCE) self.__resource_combobox_var.trace('w', self.__on_combobox_changed) self.__resource_combobox = ttk.Combobox( self.__left_frame, state='readonly', font=FONT, textvariable=self.__resource_combobox_var) # Fill the Combobox resources_names = self.__state.model.get_all_resources_names() self.__resource_combobox['values'] = sorted(resources_names) self.__resource_combobox_var.set(SELECT_RESOURCE) # C(r)ud Buttons self.__add_resource_button = ttk.Button(self.__left_frame, text='Add', state=tk.NORMAL, command=self.__on_add) self.__rename_resource_button = ttk.Button(self.__left_frame, text='Rename', state=tk.DISABLED, command=self.__on_rename) self.__remove_resource_button = ttk.Button(self.__left_frame, text='Remove', state=tk.DISABLED, command=self.__remove) # Cmp label self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__produces_spinbox_label = ttk.Label(self.__left_frame, text='Produces:') self.__produces_spinbox_var = tk.IntVar(value='') self.__produces_spinbox_var.trace('w', self.__on_produced_changed) self.__produces_spinbox = ttk.Spinbox( self.__left_frame, from_=-math.inf, to=math.inf, textvariable=self.__produces_spinbox_var) self.__all_children_produce_spinbox_label = ttk.Label( self.__left_frame, text='Produces:') self.__all_children_produce_spinbox_var = tk.IntVar(value=0) self.__all_children_produce_spinbox = ttk.Spinbox( self.__left_frame, from_=-math.inf, to=math.inf, textvariable=self.__all_children_produce_spinbox_var) self.__apply_to_all_children_button = ttk.Button( self.__left_frame, text='Apply to all children', command=self.__apply_to_all_children) def _setup_layout(self): self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__left_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__resource_combobox.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_resource_button.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__rename_resource_button.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__remove_resource_button.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__cmp_label.grid(row=4, column=0, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.EW) self.__produces_spinbox_label.grid(row=5, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__produces_spinbox.grid(row=5, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__all_children_produce_spinbox_label.grid(row=5, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__all_children_produce_spinbox.grid(row=5, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__apply_to_all_children_button.grid(row=6, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.columnconfigure(0, weight=1, uniform='fred') self.columnconfigure(1, weight=3, uniform='fred') self.rowconfigure(0, weight=1) self.__left_frame.columnconfigure(1, weight=1) # Hide widgets self.__taxonomy_tree.grid_forget() self.__cmp_label.grid_forget() self.__produces_spinbox_label.grid_forget() self.__produces_spinbox.grid_forget() self.__all_children_produce_spinbox_label.grid_forget() self.__all_children_produce_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() # SubscribesToListeners def _subscribe_to_events(self): pub.subscribe(self.__on_model_loaded, actions.MODEL_LOADED) pub.subscribe(self.__on_taxonomy_edited, actions.TAXONOMY_EDITED) pub.subscribe(self._reset, actions.RESET) # HasTaxonomyTree def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ if self.__selected_resource: selected_cmp: Component = self.__state.model.get_component( id_=cmp_id) self.__selected_component = selected_cmp self.__cmp_label.grid(row=4, column=0, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.EW) self.__cmp_label_var.set(trim_string(selected_cmp.name, length=23)) if selected_cmp.is_leaf: self.__all_children_produce_spinbox_label.grid_forget( ) # Hide widgets (changing all children) self.__all_children_produce_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() produced = 0 if self.__selected_resource.id_ in selected_cmp.produces: produced = selected_cmp.produces[ self.__selected_resource.id_] self.__produces_spinbox_var.set(produced) self.__produces_spinbox_label.grid(row=5, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__produces_spinbox.grid(row=5, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) else: self.__produces_spinbox_label.grid_forget( ) # Hide widgets for leaves self.__produces_spinbox.grid_forget() # Show widgets (changing all children) self.__all_children_produce_spinbox_var.set(0) self.__all_children_produce_spinbox_label.grid( row=5, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__all_children_produce_spinbox.grid(row=5, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__apply_to_all_children_button.grid(row=6, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) def __extract_values(self, cmp: Component) -> Tuple[Any, ...]: """Extracts the data of the component to show in the taxonomy view. :param cmp: Component from which to extract the data. :return: Tuple containing data about component (amount of produced resource of type __selected_resource,). """ produces = '' if self.__selected_resource: if self.__selected_resource.id_ in cmp.produces: produces = cmp.produces[self.__selected_resource.id_] return produces, # Coma means 1-element tuple def __build_tree(self) -> None: """Fills the tree view with components from model.""" self.__taxonomy_tree.set_items(self.__state.model.taxonomy) def __on_model_loaded(self): """Executed whenever a model is loaded from file.""" self._reset() self.__build_tree() resources_names = self.__state.model.get_all_resources_names() self.__resource_combobox['values'] = sorted(resources_names) self.__resource_combobox_var.set(SELECT_RESOURCE) def __on_taxonomy_edited(self) -> None: """Executed whenever the structure of the taxonomy changes.""" self._reset() self.__build_tree() # Resetable def _reset(self) -> None: self.__selected_component = None self.__selected_resource = None # Hide widgets self.__produces_spinbox_label.grid_forget() self.__produces_spinbox.grid_forget() self.__all_children_produce_spinbox_label.grid_forget() self.__all_children_produce_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() # Set entries to default self.__resource_combobox['values'] = () self.__resource_combobox_var.set(SELECT_RESOURCE) self.__produces_spinbox_var.set('') self.__all_children_produce_spinbox_var.set('') self.__cmp_label_var.set('') # Disable buttons change_controls_state(tk.DISABLED, self.__rename_resource_button, self.__remove_resource_button) self.__taxonomy_tree.set_items([]) self.__taxonomy_tree.grid_forget() # Class-specific def __on_combobox_changed(self, *_): """Executed whenever the __resource_combobox value changes.""" res_name = self.__resource_combobox_var.get() resource = self.__state.model.get_resource(name=res_name) if resource: # Enable buttons change_controls_state(tk.NORMAL, self.__rename_resource_button, self.__remove_resource_button) self.__selected_resource = resource if self.__selected_component: if self.__selected_component.is_leaf: produced = 0 if resource.id_ in self.__selected_component.produces: produced = self.__selected_component.produces[ self.__selected_resource.id_] self.__produces_spinbox_var.set(produced) else: self.__all_children_produce_spinbox_var.set('') # Show the taxonomy tree self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__update_tree() def __update_tree(self): """Updates every leaf component in the taxonomy treeview.""" leaf_cmps = self.__state.model.get_components(is_leaf=True) self.__taxonomy_tree.update_values(*leaf_cmps) def __on_add(self): """Executed whenever the __add_resource_button is pressed.""" AskStringWindow(self, self.__add, 'Add resource', 'Enter name of a new resource.') def __add(self, name: str): """Executed after creating of a new resource. :param name: Name of a new resource. """ res = Resource(name) self.__state.model.add_resource(res) self.__selected_resource = res self.__resource_combobox['values'] = sorted( self.__state.model.get_all_resources_names()) self.__resource_combobox_var.set(res.name) # Enable buttons change_controls_state(tk.NORMAL, self.__rename_resource_button, self.__remove_resource_button) # Show the taxonomy tree self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__update_tree() def __on_rename(self) -> None: """Executed whenever the __rename_resource_button is pressed.""" if self.__selected_resource: AskStringWindow( self, self.__rename, 'Rename resource', f'Enter new name for "{self.__selected_resource.name}" resource.', string=self.__selected_resource.name) def __rename(self, new_name: str) -> None: """Executed after renaming of a resource. :param new_name: New name for the __selected_resource. """ res = self.__state.model.rename_resource(self.__selected_resource, new_name) self.__resource_combobox['values'] = sorted( self.__state.model.get_all_resources_names()) self.__resource_combobox_var.set(res.name) def __remove(self): """Executed whenever the __remove_resource_button is pressed. Removes __selected_resource from model. """ if self.__selected_resource: self.__state.model.remove_resource(self.__selected_resource) self.__resource_combobox['values'] = sorted( self.__state.model.get_all_resources_names()) self.__selected_resource = None self.__resource_combobox_var.set(SELECT_RESOURCE) # Disable buttons change_controls_state(tk.DISABLED, self.__rename_resource_button, self.__remove_resource_button) # Hide widgets self.__cmp_label.grid_forget() self.__produces_spinbox_label.grid_forget() self.__produces_spinbox.grid_forget() self.__all_children_produce_spinbox_label.grid_forget() self.__all_children_produce_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() # Hide the taxonomy tree self.__taxonomy_tree.grid_forget() def __on_produced_changed(self, *_): """Executed whenever the __produces_spinbox value changes.""" if self.__selected_component and self.__selected_resource: value = None try: value = self.__produces_spinbox_var.get() except tk.TclError: pass finally: if value: self.__selected_component.produces[ self.__selected_resource.id_] = value elif self.__selected_resource.id_ in self.__selected_component.produces: del self.__selected_component.produces[ self.__selected_resource.id_] self.__taxonomy_tree.update_values(self.__selected_component) def __apply_to_all_children(self): """Executed whenever the __apply_to_all_children_button is pressed.""" if self.__selected_component and self.__selected_resource: value = None try: value = self.__all_children_produce_spinbox_var.get() except tk.TclError: pass finally: updated_components = self.__state.model.set_resource_production_to_all_components_children( self.__selected_component, self.__selected_resource, value) self.__taxonomy_tree.update_values(*updated_components)
class PortsTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to create, edit and remove components. Attributes: __selected_port: Currently selected port in the ports listview. __selected_component: Currently selected component in the components taxonomy view. """ def __init__(self, parent_notebook): self.__state = State() self.__selected_port: Optional[Port] = None self.__selected_component: Optional[Component] = None Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self) -> None: self.__taxonomy_tree = ScrollbarListbox(self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Amount')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) # Ports combobox self.__port_combobox_var = tk.StringVar(value=SELECT_PORT) self.__port_combobox_var.trace('w', self.__on_combobox_changed) # state='readonly' means you cannot write freely in the combobox self.__port_combobox = ttk.Combobox(self.__left_frame, state='readonly', textvariable=self.__port_combobox_var, font=FONT) ports_names = self.__state.model.get_all_ports_names() self.__port_combobox['values'] = sorted(ports_names) # C(r)ud Buttons self.__add_port_button = ttk.Button(self.__left_frame, text='Add', state=tk.NORMAL, command=self.__on_add) self.__rename_port_button = ttk.Button(self.__left_frame, text='Rename', state=tk.DISABLED, command=self.__on_rename) self.__remove_port_button = ttk.Button(self.__left_frame, text='Remove', state=tk.DISABLED, command=self.__remove) # Force connection self.__force_connection_checkbox_var = tk.BooleanVar(value=False) self.__force_connection_checkbox_var.trace('w', self.__on_force_connection_toggled) self.__force_connection_checkbox_label = ttk.Label(self.__left_frame, text='Force connection:') self.__force_connection_checkbox = ttk.Checkbutton(self.__left_frame, state=tk.DISABLED, variable=self.__force_connection_checkbox_var) # Force connection checkbox self.__compatible_with_edit_button = ttk.Button(self.__left_frame, text='Edit compatibility', command=self.__on_edit_compatible_with, state=tk.DISABLED) self.__compatible_with_listbox = ScrollbarListbox(self.__left_frame, extract_text=lambda prt: prt.name, extract_id=lambda prt: prt.id_, columns=[Column('Compatible with', main=True, stretch=tk.YES)]) self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__amount_spinbox_label = ttk.Label(self.__left_frame, text='Has:') self.__amount_spinbox_var = tk.IntVar(value='') self.__amount_spinbox_var.trace('w', self.__on_amount_changed) self.__amount_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, textvariable=self.__amount_spinbox_var) self.__all_children_amount_spinbox_label = ttk.Label(self.__left_frame, text='Children have:') self.__all_children_amount_spinbox_var = tk.IntVar(value='') self.__all_children_amount_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, textvariable=self.__all_children_amount_spinbox_var) self.__apply_to_all_children_button = ttk.Button(self.__left_frame, text='Apply to all children', command=self.__apply_to_all_children) def _setup_layout(self) -> None: self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__left_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__port_combobox.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_port_button.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__rename_port_button.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__remove_port_button.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__force_connection_checkbox_label.grid(row=4, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__force_connection_checkbox.grid(row=4, column=1, sticky=tk.E, pady=CONTROL_PAD_Y) self.__compatible_with_edit_button.grid(row=5, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__compatible_with_listbox.grid(row=6, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__cmp_label.grid(row=7, column=0, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.EW) self.__amount_spinbox_label.grid(row=8, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__amount_spinbox.grid(row=8, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__all_children_amount_spinbox_label.grid(row=8, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) self.__all_children_amount_spinbox.grid(row=8, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__apply_to_all_children_button.grid(row=9, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.columnconfigure(1, weight=1) # Give all the remaining space to taxonomy tree self.rowconfigure(0, weight=1) self.__left_frame.columnconfigure(1, weight=1) # Hide widgets self.__taxonomy_tree.grid_forget() self.__cmp_label.grid_forget() self.__amount_spinbox_label.grid_forget() self.__amount_spinbox.grid_forget() self.__all_children_amount_spinbox_label.grid_forget() self.__all_children_amount_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() # Taxonomy Treeview def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ if self.__selected_port: selected_cmp = self.__state.model.get_component(id_=cmp_id) self.__selected_component = selected_cmp self.__cmp_label.grid(row=7, column=0, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.EW) # Show cmp label self.__cmp_label_var.set(trim_string(selected_cmp.name, length=30)) # Fill the label with component name if selected_cmp.is_leaf: self.__all_children_amount_spinbox_label.grid_forget() # Hide widgets (changing all children) self.__all_children_amount_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() self.__all_children_amount_spinbox_var.set(0) amount = 0 if self.__selected_port.id_ in selected_cmp.ports: amount = selected_cmp.ports[self.__selected_port.id_] self.__amount_spinbox_var.set(amount) self.__amount_spinbox_label.grid(row=8, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) # Show widgets for leaves self.__amount_spinbox.grid(row=8, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) else: self.__amount_spinbox_label.grid_forget() # Hide widgets for leaves self.__amount_spinbox.grid_forget() self.__all_children_amount_spinbox_label.grid(row=8, column=0, pady=CONTROL_PAD_Y, sticky=tk.W) # Show widgets (changing all children) self.__all_children_amount_spinbox.grid(row=8, column=1, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__apply_to_all_children_button.grid(row=9, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) def __extract_values(self, cmp: Component) -> Tuple[Any, ...]: """Extracts the data of the component to show in the taxonomy view. :param cmp: Component from which to extract the data. :return: Tuple containing data about component (number of ports the of type __selected_port that the component has,). """ amount = '' if self.__selected_port: if self.__selected_port.id_ in cmp.ports: amount = cmp.ports[self.__selected_port.id_] return amount, def __build_tree(self) -> None: """Fills the tree view with components from model.""" self.__taxonomy_tree.set_items(self.__state.model.taxonomy) def __on_combobox_changed(self, *_) -> None: """Executed whenever the __port_combobox value changes.""" # Hide component-specific widgets self.__cmp_label.grid_forget() self.__amount_spinbox_label.grid_forget() self.__amount_spinbox.grid_forget() self.__all_children_amount_spinbox_label.grid_forget() self.__all_children_amount_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() prt_name = self.__port_combobox_var.get() port = self.__state.model.get_port(name=prt_name) buttons_to_change_state_of = [ self.__rename_port_button, self.__remove_port_button, self.__force_connection_checkbox, self.__compatible_with_edit_button, ] if port: self.__selected_port = port change_controls_state(tk.NORMAL, *buttons_to_change_state_of) compatible_ports = self.__state.model.get_ports_by_ids(port.compatible_with) self.__compatible_with_listbox.set_items(compatible_ports) # Fill the 'compatible with' listbox self.__force_connection_checkbox_var.set(port.force_connection) self.__update_tree() # Update tree values self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) # Show tree else: self.__selected_port = None self.__force_connection_checkbox_var.set(False) self.__compatible_with_listbox.set_items([]) # Clear 'compatible with' listbox change_controls_state(tk.DISABLED, *buttons_to_change_state_of) self.__taxonomy_tree.grid_forget() # Hide tree def __update_tree(self) -> None: """Updates every leaf component in the taxonomy treeview.""" leaf_cmps = self.__state.model.get_components(is_leaf=True) self.__taxonomy_tree.update_values(*leaf_cmps) # SubscribesToListeners def _subscribe_to_events(self) -> None: pub.subscribe(self.__on_model_loaded, actions.MODEL_LOADED) pub.subscribe(self.__on_taxonomy_edited, actions.TAXONOMY_EDITED) pub.subscribe(self._reset, actions.RESET) def __on_taxonomy_edited(self): """Executed whenever the structure of the taxonomy changes.""" self.__build_tree() self.__selected_component = None self.__cmp_label_var.set('') self.__cmp_label.grid_forget() self.__amount_spinbox_label.grid_forget() self.__amount_spinbox.grid_forget() self.__all_children_amount_spinbox_label.grid_forget() self.__all_children_amount_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() def __on_model_loaded(self): """Executed whenever a model is loaded from file.""" self._reset() self.__build_tree() ports_names = self.__state.model.get_all_ports_names() self.__port_combobox['values'] = sorted(ports_names) # Resetable def _reset(self) -> None: self.__selected_port = None self.__selected_component = None self.__taxonomy_tree.set_items([]) self.__port_combobox_var.set(SELECT_PORT) self.__port_combobox['values'] = [] self.__taxonomy_tree.grid_forget() # Hide treeview # Class-specific # Ports def __on_add(self) -> None: """Executed whenever the __add_port_button is pressed.""" AskStringWindow(self, self.__add, 'Add port', 'Enter name of a new port.') def __add(self, name: str) -> None: """Executed after creating of a new port. :param name: Name of a new port. """ prt = Port(name) self.__state.model.add_port(prt) self.__selected_port = prt self.__port_combobox['values'] = sorted(self.__state.model.get_all_ports_names()) self.__port_combobox_var.set(prt.name) change_controls_state(tk.NORMAL, self.__rename_port_button, self.__remove_port_button, self.__force_connection_checkbox, self.__compatible_with_edit_button) def __on_rename(self) -> None: """Executed whenever the __rename_port_button is pressed.""" if self.__selected_port: AskStringWindow(self, self.__rename, 'Rename port', f'Enter new name for "{self.__selected_port.name}" port.', string=self.__selected_port.name) def __rename(self, new_name: str) -> None: """Executed after renaming of a port. :param new_name: New name for the __selected_port. """ prt = self.__state.model.rename_port(self.__selected_port, new_name) self.__port_combobox['values'] = sorted(self.__state.model.get_all_ports_names()) self.__port_combobox_var.set(prt.name) def __remove(self) -> None: """Executed whenever the __remove_port_button is pressed. Removes __selected_port from model. """ if self.__selected_port: removed_prt = self.__state.model.remove_port(self.__selected_port) updated_combobox_values = [val for val in [*self.__port_combobox['values']] if val != removed_prt.name] self.__port_combobox['values'] = updated_combobox_values self.__selected_port = None self.__port_combobox_var.set(SELECT_PORT) self.__compatible_with_listbox.set_items([]) # Clear the compatible with change_controls_state(tk.DISABLED, self.__rename_port_button, self.__remove_port_button, self.__force_connection_checkbox, self.__compatible_with_edit_button) # Hide component-specific widgets self.__cmp_label.grid_forget() self.__amount_spinbox_label.grid_forget() self.__amount_spinbox.grid_forget() self.__all_children_amount_spinbox_label.grid_forget() self.__all_children_amount_spinbox.grid_forget() self.__apply_to_all_children_button.grid_forget() self.__update_tree() def __on_edit_compatible_with(self): """Executed whenever the __compatible_with_edit_button is pressed.""" if self.__selected_port: all_ports = self.__state.model.ports compatible_ports = [p for p in all_ports if p.id_ in self.__selected_port.compatible_with] ports_rest = [p for p in all_ports if p not in compatible_ports and p.id_ != self.__selected_port.id_] SelectPortsWindow(self, self.__selected_port, compatible_ports, ports_rest, callback=self.__edit_compatible_with) def __edit_compatible_with(self, ports: List[Port]) -> None: """Executed after editing the list of ports compatible with __selected_port. :param ports: New list of ports compatible with __selected_port. """ if self.__selected_port: ports.sort(key=lambda x: x.name) self.__compatible_with_listbox.set_items(ports) self.__state.model.update_ports_compatibility(self.__selected_port, ports) def __on_force_connection_toggled(self, *_): """Executed whenever the __force_connection_checkbox is toggled.""" if self.__selected_port: value = self.__force_connection_checkbox_var.get() self.__selected_port.force_connection = value def __on_amount_changed(self, *_): """Executed whenever the __amount_spinbox value changes.""" if self.__selected_component and self.__selected_port: value = None try: value = self.__amount_spinbox_var.get() except tk.TclError: pass finally: if value: self.__selected_component.ports[self.__selected_port.id_] = value elif self.__selected_port.id_ in self.__selected_component.ports: del self.__selected_component.ports[self.__selected_port.id_] self.__taxonomy_tree.update_values(self.__selected_component) def __apply_to_all_children(self): """Executed whenever the __apply_to_all_children_button is pressed.""" if self.__selected_component and self.__selected_port: value = None try: value = self.__all_children_amount_spinbox_var.get() except tk.TclError: pass finally: updated_components = self.__state.model.set_ports_amount_to_all_components_children( self.__selected_component, self.__selected_port, value) self.__taxonomy_tree.update_values(*updated_components)
class ConstraintsTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to create, edit and remove constraints. Attributes: __selected_constraint: Currently selected constraint in the constraints list view. """ def __init__(self, parent_notebook): self.__state = State() self.__selected_constraint: Optional[ Any] = None # can be either simple or complex constraint Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self) -> None: self.__constraints_listbox = ScrollbarListbox( self, columns=[Column('Type')], heading='Constraint', extract_id=lambda crt: crt.id_, extract_text=lambda crt: crt.name, extract_values=lambda crt: 'Simple' if isinstance(crt, SimpleConstraint) else 'Complex', on_select_callback=self.__on_select_callback, values=self.__state.model.get_all_constraints()) self.__right_frame = ttk.Frame(self) # Ctr label self.__ctr_label_var = tk.StringVar(value='') self.__ctr_label = ttk.Label(self.__right_frame, textvariable=self.__ctr_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__add_simple_constraint_button = ttk.Button( self.__right_frame, text='Add simple constraint', command=self.__on_add) self.__add_complex_constraint_button = ttk.Button( self.__right_frame, text='Add complex constraint', command=lambda: self.__on_add(complex_=True)) self.__edit_constraint_button = ttk.Button(self.__right_frame, text='Edit', state=tk.DISABLED, command=self.__on_edit) self.__remove_constraint_button = ttk.Button( self.__right_frame, text='Remove', state=tk.DISABLED, command=self.__remove_constraint) def _setup_layout(self) -> None: self.__constraints_listbox.grid(row=0, column=0, sticky=tk.NSEW) self.__right_frame.grid(row=0, column=1, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__ctr_label.grid(row=0, column=0, pady=CONTROL_PAD_Y, sticky=tk.EW) self.__add_simple_constraint_button.grid(row=1, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__add_complex_constraint_button.grid(row=2, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__edit_constraint_button.grid(row=3, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__remove_constraint_button.grid(row=4, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) # SubscribesToEvents def _subscribe_to_events(self) -> None: pub.subscribe(self.__on_model_changed, actions.MODEL_LOADED) pub.subscribe(self.__on_model_changed, actions.TAXONOMY_EDITED) pub.subscribe(self._reset, actions.RESET) # Resetable def _reset(self) -> None: self.__constraints_listbox.set_items([]) self.__selected_constraint = None self.__ctr_label_var.set('') change_controls_state(tk.DISABLED, self.__edit_constraint_button, self.__remove_constraint_button) def __on_select_callback(self, ctr_id: int): """Executed whenever a constraints listview item is selected (by mouse click). :param ctr_id: Id of the selected constraint. """ selected_ctr = self.__state.model.get_constraint(id_=ctr_id) if selected_ctr: self.__selected_constraint = selected_ctr self.__ctr_label_var.set( trim_string(selected_ctr.name, length=LABEL_LENGTH)) # Enable buttons change_controls_state(tk.NORMAL, self.__edit_constraint_button, self.__remove_constraint_button) def __add(self, ctr: Any) -> None: """Executed after adding a new constraint in SimpleConstraintWindow or ComplexConstraintWindow. :param ctr: New SimpleConstraint or ComplexConstraint obtained from SimpleConstraintWindow or ComplexConstraintWindow. """ _, index = self.__state.model.add_constraint(ctr) self.__constraints_listbox.add_item(ctr, index=index) # Set selected constraint to the newly created constraint self.__selected_constraint = ctr self.__ctr_label_var.set(trim_string(ctr.name, length=LABEL_LENGTH)) # Enable buttons change_controls_state(tk.NORMAL, self.__edit_constraint_button, self.__remove_constraint_button) def __edit(self, ctr: Any): """Executed after editing the constraint in SimpleConstraintWindow or ComplexConstraintWindow. :param ctr: Edited SimpleConstraint or ComplexConstraint obtained from SimpleConstraintWindow or ComplexConstraintWindow. """ _, index = self.__state.model.edit_constraint(ctr) self.__selected_constraint = ctr # Just in case if the name has changed self.__constraints_listbox.rename_item( ctr, index=index) # Update constraint name in treeview self.__ctr_label_var.set(trim_string( ctr.name, length=LABEL_LENGTH)) # Update constraint label def __on_add(self, complex_=False): """Executed whenever the __add_simple_constraint_button or __add_complex_constraint_button are pressed. :param complex_: If __add_complex_constraint_button is pressed then set to True; False otherwise. """ if complex_: ComplexConstraintWindow(self, callback=self.__add) else: SimpleConstraintWindow(self, callback=self.__add) def __on_edit(self): """Executed whenever the __edit_constraint_button is pressed.""" if self.__selected_constraint: if isinstance(self.__selected_constraint, SimpleConstraint): SimpleConstraintWindow(self, constraint=self.__selected_constraint, callback=self.__edit) else: ComplexConstraintWindow(self, constraint=self.__selected_constraint, callback=self.__edit) def __remove_constraint(self): """Executed whenever the __remove_constraint_button is pressed. Removes selected constraint from model. """ if self.__selected_constraint: self.__state.model.remove_constraint(self.__selected_constraint) self.__constraints_listbox.remove_item_recursively( self.__selected_constraint) self.__selected_constraint = None # Hide widgets self.__ctr_label_var.set('') change_controls_state(tk.DISABLED, self.__edit_constraint_button, self.__remove_constraint_button) def __on_model_changed(self): """Executed whenever a model is loaded from file or taxonomy changes.""" ctrs = self.__state.model.get_all_constraints() self.__constraints_listbox.set_items(ctrs) self.__selected_constraint = None self.__ctr_label_var.set('') change_controls_state(tk.DISABLED, self.__edit_constraint_button, self.__remove_constraint_button)
def _create_widgets(self) -> None: # Name self.__data_frame = ttk.Frame(self) self.__name_entry_var = tk.StringVar(value=self.__constraint.name) self.__name_entry_label = ttk.Label(self.__data_frame, text='Name:') self.__name_entry = ttk.Entry(self.__data_frame, textvariable=self.__name_entry_var) # Description self.__description_text_label = ttk.Label(self.__data_frame, text='Description:') self.__description_text = tk.Text(self.__data_frame, height=8, width=40) if self.__constraint.description: self.__description_text.insert(tk.INSERT, self.__constraint.description) self.__implication_frame = ttk.Frame(self) self.__antecedent_frame = ttk.Frame(self.__implication_frame) self.__consequent_frame = ttk.Frame(self.__implication_frame) self.__antecedent_listbox = ScrollbarListbox( self.__antecedent_frame, values=self.__antecedent, heading='Condition', extract_id=lambda ctr: ctr.id_, extract_text=lambda ctr: ctr.name, on_select_callback=self.__on_select_antecedent) self.__consequent_listbox = ScrollbarListbox( self.__consequent_frame, values=self.__consequent, heading='Consequence', extract_id=lambda ctr: ctr.id_, extract_text=lambda ctr: ctr.name, on_select_callback=self.__on_select_consequent) # Antecedent all/any self.__antecedent_all_var = tk.BooleanVar( value=self.__constraint.antecedent_all) self.__antecedent_all_radiobutton = ttk.Radiobutton( self.__antecedent_frame, text='All', value=True, variable=self.__antecedent_all_var) self.__antecedent_any_radiobutton = ttk.Radiobutton( self.__antecedent_frame, text='Any', value=False, variable=self.__antecedent_all_var) # Consequent all/any self.__consequent_all_var = tk.BooleanVar( value=self.__constraint.consequent_all) self.__consequent_all_radiobutton = ttk.Radiobutton( self.__consequent_frame, text='All', value=True, variable=self.__consequent_all_var) self.__consequent_any_radiobutton = ttk.Radiobutton( self.__consequent_frame, text='Any', value=False, variable=self.__consequent_all_var) self.__add_antecedent_button = ttk.Button( self.__antecedent_frame, text='Add', command=self.__on_add_antecedent) self.__edit_antecedent_button = ttk.Button( self.__antecedent_frame, text='Edit', command=self.__on_edit_antecedent, state=tk.DISABLED) self.__remove_antecedent_button = ttk.Button( self.__antecedent_frame, text='Remove', state=tk.DISABLED, command=self.__remove_antecedent) self.__add_consequent_button = ttk.Button( self.__consequent_frame, text='Add', command=self.__on_add_consequent) self.__edit_consequent_button = ttk.Button( self.__consequent_frame, text='Edit', command=self.__on_edit_consequent, state=tk.DISABLED) self.__remove_consequent_button = ttk.Button( self.__consequent_frame, text='Remove', state=tk.DISABLED, command=self.__remove_consequent) # Buttons frame self.__buttons_frame = ttk.Frame(self) self.__ok_button = ttk.Button(self.__buttons_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__buttons_frame, text='Cancel', command=self.destroy)
class ComplexConstraintWindow(HasCommonSetup, Window): """Used to create/edit complex constraint. Attributes: __callback: Function to be executed when the 'OK' button is pressed, before destroying the window. __constraint: New constraint / deep copy of the edited constraint. __antecedent: List of SimpleConstraints forming an antecedent. __consequent: List of SimpleConstraints forming a consequent. __selected_antecedent: Currently selected simple constraint in the antecedent list view. __selected_consequent: Currently selected simple constraint in the consequent list view. """ def __init__(self, parent_frame, callback: Callable[[Any], Any], constraint: Optional[ComplexConstraint] = None): self.__callback = callback self.__constraint: ComplexConstraint = copy.deepcopy(constraint) if constraint is not None \ else ComplexConstraint() self.__antecedent: List[ SimpleConstraint] = [] if constraint is None else [ copy.deepcopy(sc) for sc in constraint.antecedent ] self.__consequent: List[ SimpleConstraint] = [] if constraint is None else [ copy.deepcopy(sc) for sc in constraint.consequent ] self.__selected_antecedent: Optional[SimpleConstraint] = None self.__selected_consequent: Optional[SimpleConstraint] = None Window.__init__(self, parent_frame, WINDOW_TITLE) HasCommonSetup.__init__(self) # HasCommonSetup def _create_widgets(self) -> None: # Name self.__data_frame = ttk.Frame(self) self.__name_entry_var = tk.StringVar(value=self.__constraint.name) self.__name_entry_label = ttk.Label(self.__data_frame, text='Name:') self.__name_entry = ttk.Entry(self.__data_frame, textvariable=self.__name_entry_var) # Description self.__description_text_label = ttk.Label(self.__data_frame, text='Description:') self.__description_text = tk.Text(self.__data_frame, height=8, width=40) if self.__constraint.description: self.__description_text.insert(tk.INSERT, self.__constraint.description) self.__implication_frame = ttk.Frame(self) self.__antecedent_frame = ttk.Frame(self.__implication_frame) self.__consequent_frame = ttk.Frame(self.__implication_frame) self.__antecedent_listbox = ScrollbarListbox( self.__antecedent_frame, values=self.__antecedent, heading='Condition', extract_id=lambda ctr: ctr.id_, extract_text=lambda ctr: ctr.name, on_select_callback=self.__on_select_antecedent) self.__consequent_listbox = ScrollbarListbox( self.__consequent_frame, values=self.__consequent, heading='Consequence', extract_id=lambda ctr: ctr.id_, extract_text=lambda ctr: ctr.name, on_select_callback=self.__on_select_consequent) # Antecedent all/any self.__antecedent_all_var = tk.BooleanVar( value=self.__constraint.antecedent_all) self.__antecedent_all_radiobutton = ttk.Radiobutton( self.__antecedent_frame, text='All', value=True, variable=self.__antecedent_all_var) self.__antecedent_any_radiobutton = ttk.Radiobutton( self.__antecedent_frame, text='Any', value=False, variable=self.__antecedent_all_var) # Consequent all/any self.__consequent_all_var = tk.BooleanVar( value=self.__constraint.consequent_all) self.__consequent_all_radiobutton = ttk.Radiobutton( self.__consequent_frame, text='All', value=True, variable=self.__consequent_all_var) self.__consequent_any_radiobutton = ttk.Radiobutton( self.__consequent_frame, text='Any', value=False, variable=self.__consequent_all_var) self.__add_antecedent_button = ttk.Button( self.__antecedent_frame, text='Add', command=self.__on_add_antecedent) self.__edit_antecedent_button = ttk.Button( self.__antecedent_frame, text='Edit', command=self.__on_edit_antecedent, state=tk.DISABLED) self.__remove_antecedent_button = ttk.Button( self.__antecedent_frame, text='Remove', state=tk.DISABLED, command=self.__remove_antecedent) self.__add_consequent_button = ttk.Button( self.__consequent_frame, text='Add', command=self.__on_add_consequent) self.__edit_consequent_button = ttk.Button( self.__consequent_frame, text='Edit', command=self.__on_edit_consequent, state=tk.DISABLED) self.__remove_consequent_button = ttk.Button( self.__consequent_frame, text='Remove', state=tk.DISABLED, command=self.__remove_consequent) # Buttons frame self.__buttons_frame = ttk.Frame(self) self.__ok_button = ttk.Button(self.__buttons_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__buttons_frame, text='Cancel', command=self.destroy) def _setup_layout(self) -> None: self.__data_frame.grid(row=0, column=0, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__name_entry_label.grid(row=0, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__name_entry.grid(row=0, column=1, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__description_text_label.grid(row=1, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__description_text.grid(row=1, column=1, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__implication_frame.grid(row=1, column=0, sticky=tk.NSEW) self.__antecedent_frame.grid(row=0, column=0, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__consequent_frame.grid(row=0, column=1, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__antecedent_listbox.grid(row=0, column=0, columnspan=4, sticky=tk.NSEW) self.__antecedent_all_radiobutton.grid(row=1, column=1, sticky=tk.W, pady=CONTROL_PAD_Y) self.__antecedent_any_radiobutton.grid(row=1, column=2, sticky=tk.W, pady=CONTROL_PAD_Y) self.__add_antecedent_button.grid(row=2, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__edit_antecedent_button.grid(row=3, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__remove_antecedent_button.grid(row=4, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__consequent_listbox.grid(row=0, column=0, columnspan=4, sticky=tk.NSEW) self.__consequent_all_radiobutton.grid(row=1, column=1, sticky=tk.W, pady=CONTROL_PAD_Y) self.__consequent_any_radiobutton.grid(row=1, column=2, sticky=tk.W, pady=CONTROL_PAD_Y) self.__add_consequent_button.grid(row=2, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__edit_consequent_button.grid(row=3, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__remove_consequent_button.grid(row=4, column=1, columnspan=2, pady=CONTROL_PAD_Y, sticky=tk.NSEW) self.__buttons_frame.grid(row=2, column=0, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__ok_button.grid(row=0, column=0, sticky=tk.E) self.__cancel_button.grid(row=0, column=1, sticky=tk.W) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.__implication_frame.columnconfigure(0, weight=1, uniform='fred') self.__implication_frame.columnconfigure(1, weight=1, uniform='fred') self.__implication_frame.rowconfigure(0, weight=1, uniform='fred') self.__antecedent_frame.rowconfigure(0, weight=1) self.__antecedent_frame.columnconfigure(0, weight=1) self.__antecedent_frame.columnconfigure(1, weight=1) self.__antecedent_frame.columnconfigure(2, weight=1) self.__antecedent_frame.columnconfigure(3, weight=1) self.__consequent_frame.rowconfigure(0, weight=1) self.__consequent_frame.columnconfigure(0, weight=1) self.__consequent_frame.columnconfigure(1, weight=1) self.__consequent_frame.columnconfigure(2, weight=1) self.__consequent_frame.columnconfigure(3, weight=1) self.__buttons_frame.columnconfigure(0, weight=1) self.__buttons_frame.columnconfigure(1, weight=1) self._set_geometry(width_ratio=WINDOW_WIDTH_RATIO, height_ratio=WINDOW_HEIGHT_RATIO) def __ok(self): """Executed whenever the __ok_button is pressed. Rewrites the information from window to the constraint object, calls the callback on it and destroys the window.""" try: self.__constraint.name = self.__name_entry_var.get() self.__constraint.description = self.__description_text.get( 1.0, tk.END) self.__constraint.antecedent = self.__antecedent self.__constraint.antecedent_all = self.__antecedent_all_var.get() self.__constraint.consequent = self.__consequent self.__constraint.consequent_all = self.__consequent_all_var.get() self.__callback(self.__constraint) self.grab_release() self.destroy() except BGError as e: messagebox.showerror('Error', e.message, parent=self) def __on_select_antecedent(self, id_: int) -> None: """Executed whenever a __antecedent_listbox item is selected (by mouse click). :param id_: Id of the selected antecedent item. """ self.__selected_antecedent = next( (a for a in self.__antecedent if a.id_ == id_), None) # Enable widgets change_controls_state(tk.NORMAL, self.__edit_antecedent_button, self.__remove_antecedent_button) def __on_select_consequent(self, id_: int) -> None: """Executed whenever a __consequent_listbox item is selected (by mouse click). :param id_: Id of the selected consequent item. """ self.__selected_consequent = next( (c for c in self.__consequent if c.id_ == id_), None) # Enable widgets change_controls_state(tk.NORMAL, self.__edit_consequent_button, self.__remove_consequent_button) def __on_add_antecedent(self) -> None: """Executed whenever the __add_antecedent_button is pressed.""" SimpleConstraintWindow(self, callback=self.__add_antecedent) def __add_antecedent(self, ant: SimpleConstraint) -> None: """Executed whenever an item is added to antecedent. :param ant: Item to add to antecedent. """ ant, index = self.__validate_constraint(ant, antecedent=True, added=True) self.__antecedent.append(ant) self.__antecedent_listbox.add_item(ant, index=index) self.__selected_antecedent = ant # Enable controls change_controls_state(tk.NORMAL, self.__edit_antecedent_button, self.__remove_antecedent_button) def __on_edit_antecedent(self) -> None: """Executed whenever the __edit_antecedent_button is pressed.""" if self.__selected_antecedent: SimpleConstraintWindow(self, constraint=self.__selected_antecedent, callback=self.__edit_antecedent) def __edit_antecedent(self, ant: SimpleConstraint) -> None: """Executed whenever an item from antecedent is edited. :param ant: Edited item from antecedent. """ ant, index = self.__validate_constraint(ant, antecedent=True, added=False) self.__antecedent_listbox.rename_item(ant, index=index) # Replace selected antecedent with edited self.__antecedent.remove(self.__selected_antecedent) self.__antecedent.append(ant) self.__antecedent_listbox.select_item(ant) self.__selected_antecedent = ant def __remove_antecedent(self) -> None: """Executes whenever __remove_antecedent_button is pressed. Removes item from antecedent.""" if self.__selected_antecedent: self.__antecedent.remove(self.__selected_antecedent) self.__antecedent_listbox.remove_item_recursively( self.__selected_antecedent) self.__selected_antecedent = None # Disable widgets change_controls_state(tk.DISABLED, self.__edit_antecedent_button, self.__remove_antecedent_button) def __on_add_consequent(self) -> None: """Executed whenever the __add_consequent_button is pressed.""" SimpleConstraintWindow(self, callback=self.__add_consequent) def __add_consequent(self, con: SimpleConstraint) -> None: """Executed whenever an item is added to consequent. :param con: Item to add to consequent. """ con, index = self.__validate_constraint(con, antecedent=False, added=True) self.__consequent.append(con) self.__consequent_listbox.add_item(con, index=index) self.__selected_consequent = con # Enable controls change_controls_state(tk.NORMAL, self.__edit_consequent_button, self.__remove_consequent_button) def __on_edit_consequent(self) -> None: """Executed whenever the __edit_consequent_button is pressed.""" if self.__selected_consequent: SimpleConstraintWindow(self, constraint=self.__selected_consequent, callback=self.__edit_consequent) def __edit_consequent(self, con: SimpleConstraint) -> None: """Executed whenever an item from consequent is edited. :param con: Edited item from consequent. """ con, index = self.__validate_constraint(con, antecedent=False, added=True) self.__consequent_listbox.rename_item(con, index=index) # Replace selected antecedent with edited self.__consequent.remove(self.__selected_consequent) self.__consequent.append(con) self.__consequent_listbox.select_item(con) self.__selected_consequent = con def __remove_consequent(self) -> None: if self.__selected_consequent: self.__consequent.remove(self.__selected_consequent) self.__consequent_listbox.remove_item_recursively( self.__selected_consequent) self.__selected_consequent = None change_controls_state(tk.DISABLED, self.__edit_consequent_button, self.__remove_consequent_button) @staticmethod def __get_element_index(str_list: List[str], item: str) -> int: """Returns an index of an name on a list. If element is not present on the list, it gets added to it. :param str_list: List of strings. :param item: Item. :return: Index of the item on the list. """ if item not in str_list: str_list.append(item) return sorted(str_list).index(item) def __validate_constraint(self, ctr: SimpleConstraint, antecedent: bool, added: bool) -> \ Tuple[SimpleConstraint, int]: """Validates an item of either antecedent or consequent and normalizes it's name. :param ctr: item. :param antecedent: If True then the item is a part of antecedent; Otherwise, a part of consequent. :param added: If True then item is added; Otherwise edited. :return: Tuple of the form (validate item, its index in the list of antecedents or consequents) """ ctr.name = normalize_name(ctr.name) selected_ctr = None if added else self.__selected_antecedent if antecedent else self.__selected_consequent names = [a.name for a in self.__antecedent ] if antecedent else [c.name for c in self.__consequent] if ctr.name in names and not (selected_ctr is not None and selected_ctr.id_ == ctr.id_): # If the name is contained in "names", and it's not because the constraint is edited ctr_type_string = 'Antecedent' if antecedent else 'Consequent' raise BGError( f'{ctr_type_string} already exists in complex constraint.') if not ctr.components_ids: raise BGError('Constraint must contain components.') elif ctr.max_ is None and ctr.min_ is None: raise BGError('Constraint has to have at least 1 bound.') return ctr, self.__get_element_index(names, ctr.name)
def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, columns=[Column('Produces')], values=self.__state.model.taxonomy) self.__left_frame = ttk.Frame(self) # Resources combobox self.__resource_combobox_var = tk.StringVar(value=SELECT_RESOURCE) self.__resource_combobox_var.trace('w', self.__on_combobox_changed) self.__resource_combobox = ttk.Combobox( self.__left_frame, state='readonly', font=FONT, textvariable=self.__resource_combobox_var) # Fill the Combobox resources_names = self.__state.model.get_all_resources_names() self.__resource_combobox['values'] = sorted(resources_names) self.__resource_combobox_var.set(SELECT_RESOURCE) # C(r)ud Buttons self.__add_resource_button = ttk.Button(self.__left_frame, text='Add', state=tk.NORMAL, command=self.__on_add) self.__rename_resource_button = ttk.Button(self.__left_frame, text='Rename', state=tk.DISABLED, command=self.__on_rename) self.__remove_resource_button = ttk.Button(self.__left_frame, text='Remove', state=tk.DISABLED, command=self.__remove) # Cmp label self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, style='Big.TLabel', anchor=tk.CENTER) self.__produces_spinbox_label = ttk.Label(self.__left_frame, text='Produces:') self.__produces_spinbox_var = tk.IntVar(value='') self.__produces_spinbox_var.trace('w', self.__on_produced_changed) self.__produces_spinbox = ttk.Spinbox( self.__left_frame, from_=-math.inf, to=math.inf, textvariable=self.__produces_spinbox_var) self.__all_children_produce_spinbox_label = ttk.Label( self.__left_frame, text='Produces:') self.__all_children_produce_spinbox_var = tk.IntVar(value=0) self.__all_children_produce_spinbox = ttk.Spinbox( self.__left_frame, from_=-math.inf, to=math.inf, textvariable=self.__all_children_produce_spinbox_var) self.__apply_to_all_children_button = ttk.Button( self.__left_frame, text='Apply to all children', command=self.__apply_to_all_children)
class SelectPortsWindow(HasCommonSetup, Window): """Windows used to create (or edit) a subset of a set of ports.. Attributes: __callback: Callback function to be executed after pressing the OK button on this Window. __ports_right: Set of selected ports. __ports_left: Set of not-selected ports. __selected_port_left: Currently selected port from __ports_right. __selected_port_right: Currently selected port from __ports_left. """ def __init__(self, parent_frame, selected_port: Port, ports_right: List[Port], ports_left: List[Port], callback: Callable[[List[Port]], Any]): self.__state: State = State() self.__callback: Callable[[List[Port]], Any] = callback self.__ports_right: List[Port] = ports_right self.__ports_left: List[Port] = ports_left self.__selected_port_left: Optional[Port] = None self.__selected_port_right: Optional[Port] = None Window.__init__(self, parent_frame, WINDOW_TITLE.format(selected_port.name)) HasCommonSetup.__init__(self) # HasCommonSetup def _create_widgets(self) -> None: self.__ports_left_listbox = ScrollbarListbox( self, values=self.__ports_left, extract_id=lambda prt: prt.id_, extract_text=lambda prt: prt.name, on_select_callback=self.__on_select_listbox_left, columns=[Column('Port', main=True, stretch=tk.YES)]) self.__mid_frame = ttk.Frame(self) self.__remove_port_button = ttk.Button( self.__mid_frame, text='<<', command=self.__remove_from_selected) self.__add_port_button = ttk.Button(self.__mid_frame, text='>>', command=self.__add_to_selected) self.__right_frame = ttk.Frame(self) self.__ports_right_listbox = ScrollbarListbox( self.__right_frame, values=self.__ports_right, extract_id=lambda prt: prt.id_, extract_text=lambda prt: prt.name, on_select_callback=self.__on_select_listbox_right, columns=[Column('Selected ports', main=True, stretch=tk.YES)]) self.__buttons_frame = ttk.Frame(self.__right_frame) self.__ok_button = ttk.Button(self.__buttons_frame, text='Ok', command=self.__ok) self.__cancel_button = ttk.Button(self.__buttons_frame, text='Cancel', command=self.destroy) def _setup_layout(self) -> None: self.__ports_left_listbox.grid(row=0, column=0, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__mid_frame.grid(row=0, column=1) self.__remove_port_button.grid(row=1, column=0, pady=CONTROL_PAD_Y) self.__add_port_button.grid(row=2, column=0, pady=CONTROL_PAD_Y) self.__right_frame.grid(row=0, column=2, sticky=tk.NSEW, padx=FRAME_PAD_X, pady=FRAME_PAD_Y) self.__ports_right_listbox.grid(row=0, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__buttons_frame.grid(row=2, column=0, sticky=tk.NSEW) self.__ok_button.grid(row=0, column=0, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__cancel_button.grid(row=0, column=1, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__right_frame.grid_columnconfigure(0, weight=1) self.__right_frame.grid_rowconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=2, uniform='fred') self.columnconfigure(1, weight=1, uniform='fred') self.columnconfigure(2, weight=2, uniform='fred') self.__buttons_frame.columnconfigure(0, weight=1) self.__buttons_frame.columnconfigure(1, weight=1) self._set_geometry() def __on_select_listbox_left(self, prt_id: int) -> None: """Executed whenever a __ports_left_listbox item is selected (by mouse click). :param prt_id: Id of the selected port. """ self.__selected_port_left = self.__state.model.get_port(id_=prt_id) def __on_select_listbox_right(self, prt_id: int) -> None: """Executed whenever a __ports_right_listbox item is selected (by mouse click). :param prt_id: Id of the selected port. """ self.__selected_port_right = self.__state.model.get_port(id_=prt_id) def __add_to_selected(self): """Removes the __selected_port_left from __ports_left and adds it to __ports_right.""" if self.__selected_port_left: prt = self.__selected_port_left self.__ports_left.remove(prt) self.__ports_left_listbox.remove_item_recursively(prt) self.__selected_port_left = None self.__ports_right.append(prt) self.__selected_port_right = prt right_port_names = sorted([p.name for p in self.__ports_right]) index = right_port_names.index(prt.name) self.__ports_right_listbox.add_item(prt, index=index) def __remove_from_selected(self): """Removes the __selected_port_right from __ports_right and adds it to __ports_left.""" if self.__selected_port_right: prt = self.__selected_port_right self.__ports_right.remove(prt) self.__ports_right_listbox.remove_item_recursively(prt) self.__selected_port_right = None self.__ports_left.append(prt) self.__selected_port_left = prt left_port_names = sorted([p.name for p in self.__ports_left]) index = left_port_names.index(prt.name) self.__ports_left_listbox.add_item(prt, index=index) def __ok(self): """Executed whenever the __ok_button is pressed.""" self.grab_release() self.__callback(self.__ports_right) self.destroy()
class AssociationsTab(Tab, HasCommonSetup, SubscribesToEvents, Resetable): """Used to set the number of information about the associations between the root component and other components. Attributes: __selected_component: Currently selected component in the components taxonomy view. """ def __init__(self, parent_notebook): self.__state = State() self.__selected_component: Optional[Component] = None Tab.__init__(self, parent_notebook, TAB_NAME) HasCommonSetup.__init__(self) SubscribesToEvents.__init__(self) # HasCommonSetup def _create_widgets(self): self.__taxonomy_tree = ScrollbarListbox( self, on_select_callback=self.__on_select_tree_item, heading=TREEVIEW_HEADING, extract_id=lambda x: x.id_, extract_text=lambda x: x.name, extract_ancestor=lambda x: '' if x.parent_id is None else x.parent_id, extract_values=self.__extract_values, values=self.__state.model.taxonomy, columns=[Column('Has association?'), Column('Min'), Column('Max')]) self.__left_frame = ttk.Frame(self) # Cmp label self.__cmp_label_var = tk.StringVar(value='') self.__cmp_label = ttk.Label(self.__left_frame, textvariable=self.__cmp_label_var, anchor=tk.CENTER, style='Big.TLabel') # Has association checkbox self.__has_association_checkbox_var = tk.BooleanVar(value=False) self.__has_association_checkbox_var.trace( 'w', self.__on_has_association_changed) self.__has_association_checkbox_label = ttk.Label( self.__left_frame, text='Has association?') self.__has_association_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_association_checkbox_var) # Has min checkbox self.__has_min_checkbox_var = tk.BooleanVar(value=False) self.__has_min_checkbox_var.trace('w', self.__on_has_min_changed) self.__has_min_checkbox_label = ttk.Label(self.__left_frame, text='Has min?') self.__has_min_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_min_checkbox_var) # Min spinbox self.__min_spinbox_var = tk.IntVar(value='') self.__min_spinbox_var.trace('w', self.__on_min_changed) self.__min_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__min_spinbox_var, font=FONT) # Has max checkbox self.__has_max_checkbox_var = tk.BooleanVar(value=False) self.__has_max_checkbox_var.trace('w', self.__on_has_max_changed) self.__has_max_checkbox_label = ttk.Label(self.__left_frame, text='Has max?') self.__has_max_checkbox = ttk.Checkbutton( self.__left_frame, state=tk.DISABLED, variable=self.__has_max_checkbox_var) # Max spinbox self.__max_spinbox_var = tk.IntVar(value='') self.__max_spinbox_var.trace('w', self.__on_max_changed) self.__max_spinbox = ttk.Spinbox(self.__left_frame, from_=0, to=math.inf, state=tk.DISABLED, textvariable=self.__max_spinbox_var, font=FONT) def _setup_layout(self): self.__taxonomy_tree.grid(row=0, column=1, sticky=tk.NSEW) self.__left_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=FRAME_PAD_Y, padx=FRAME_PAD_X) self.__cmp_label.grid(row=0, column=0, columnspan=4, sticky=tk.EW, pady=CONTROL_PAD_Y) self.__has_association_checkbox_label.grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=CONTROL_PAD_Y) self.__has_association_checkbox.grid(row=1, column=2, columnspan=2, sticky=tk.E, pady=CONTROL_PAD_Y) self.__has_min_checkbox_label.grid(row=2, column=0, sticky=tk.W, pady=CONTROL_PAD_Y) self.__has_min_checkbox.grid(row=2, column=1, sticky=tk.E, pady=CONTROL_PAD_Y) self.__has_max_checkbox_label.grid(row=2, column=2, sticky=tk.W, pady=CONTROL_PAD_Y) self.__has_max_checkbox.grid(row=2, column=3, sticky=tk.E, pady=CONTROL_PAD_Y) self.__min_spinbox.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__max_spinbox.grid(row=3, column=2, columnspan=2, sticky=tk.NSEW, pady=CONTROL_PAD_Y) self.__left_frame.columnconfigure(0, weight=1) self.__left_frame.columnconfigure(1, weight=1) self.__left_frame.columnconfigure(2, weight=1) self.__left_frame.columnconfigure(3, weight=1) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1, uniform='fred') self.columnconfigure(1, weight=3, uniform='fred') # SubscribesToListeners def _subscribe_to_events(self): pub.subscribe(self.__on_taxonomy_edited, actions.TAXONOMY_EDITED) pub.subscribe(self.__on_taxonomy_edited, actions.MODEL_LOADED) pub.subscribe(self._reset, actions.RESET) # Taxonomy Treeview def __on_select_tree_item(self, cmp_id: int) -> None: """Executed whenever a tree item is selected (by mouse click). :param cmp_id: Id of the selected component. """ selected_cmp = self.__state.model.get_component(id_=cmp_id) if selected_cmp: change_controls_state(tk.NORMAL, self.__has_association_checkbox) self.__selected_component = selected_cmp if selected_cmp.association: self.__has_association_checkbox_var.set(True) change_controls_state(tk.NORMAL, self.__has_min_checkbox, self.__has_max_checkbox) if selected_cmp.association.min_: self.__has_min_checkbox_var.set(True) self.__min_spinbox_var.set(selected_cmp.association.min_) change_controls_state(tk.NORMAL, self.__min_spinbox) else: self.__has_min_checkbox_var.set(False) change_controls_state(tk.DISABLED, self.__min_spinbox) self.__min_spinbox_var.set('') if selected_cmp.association.max_: self.__has_max_checkbox_var.set(True) self.__max_spinbox_var.set(selected_cmp.association.max_) change_controls_state(tk.NORMAL, self.__max_spinbox) else: self.__has_max_checkbox_var.set(False) change_controls_state(tk.DISABLED, self.__max_spinbox) self.__max_spinbox_var.set('') else: self.__has_association_checkbox_var.set(False) self.__has_min_checkbox_var.set( False) # Reset and block controls self.__has_max_checkbox_var.set(False) self.__min_spinbox_var.set('') self.__max_spinbox_var.set('') change_controls_state(tk.DISABLED, self.__has_max_checkbox, self.__has_min_checkbox, self.__max_spinbox, self.__min_spinbox) self.__cmp_label_var.set(trim_string(selected_cmp.name, length=20)) @staticmethod def __extract_values(cmp: Component) -> Tuple[Any, ...]: """Extracts the data of the component to show in the taxonomy view. :param cmp: Component from which to extract the data. :return: Tuple containing data about component (has association?, min_, max_?). """ has_association = '' min_ = '' max_ = '' if cmp.association: has_association = 'Yes' min_ = cmp.association.min_ if cmp.association.min_ is not None else '' max_ = cmp.association.max_ if cmp.association.max_ is not None else '' return has_association, min_, max_ def __build_tree(self) -> None: """Fills the tree view with components from model.""" self.__taxonomy_tree.set_items(self.__state.model.taxonomy) # Resetable def _reset(self) -> None: self.__taxonomy_tree.set_items([]) self.__selected_component = None # Set entries to default self.__has_association_checkbox_var.set(False) self.__has_min_checkbox_var.set(False) self.__has_max_checkbox_var.set(False) self.__min_spinbox_var.set('') self.__max_spinbox_var.set('') self.__cmp_label_var.set('') # Disable items change_controls_state(tk.DISABLED, self.__has_association_checkbox, self.__has_min_checkbox, self.__has_max_checkbox, self.__min_spinbox, self.__max_spinbox) def __on_taxonomy_edited(self) -> None: """Executed whenever the structure of the taxonomy changes.""" self._reset() self.__build_tree() def __on_has_association_changed(self, *_): """Executed whenever the __has_association_checkbox is toggled""" if self.__selected_component: has_association = self.__has_association_checkbox_var.get() if has_association: if not self.__selected_component.association: self.__selected_component.association = Association() change_controls_state(tk.NORMAL, self.__has_min_checkbox, self.__has_max_checkbox) else: self.__has_min_checkbox_var.set( False) # Reset and block controls self.__has_max_checkbox_var.set(False) self.__min_spinbox_var.set('') self.__max_spinbox_var.set('') change_controls_state(tk.DISABLED, self.__has_max_checkbox, self.__has_min_checkbox, self.__max_spinbox, self.__min_spinbox) self.__selected_component.association = None self.__taxonomy_tree.update_values(self.__selected_component) def __on_has_min_changed(self, *_): """Executed whenever the __has_min_checkbox is toggled""" if self.__selected_component: has_min = self.__has_min_checkbox_var.get() if has_min: change_controls_state(tk.NORMAL, self.__min_spinbox) if self.__selected_component.association and self.__selected_component.association.min_ is not None: self.__min_spinbox_var.set( self.__selected_component.association.min_) else: self.__min_spinbox_var.set(0) self.__selected_component.association.min_ = 0 else: if self.__selected_component.association: self.__selected_component.association.min_ = None change_controls_state(tk.DISABLED, self.__min_spinbox) self.__min_spinbox_var.set('') def __on_has_max_changed(self, *_): """Executed whenever the __has_max_checkbox_var is toggled""" if self.__selected_component: has_max = self.__has_max_checkbox_var.get() if has_max: change_controls_state(tk.NORMAL, self.__max_spinbox) if self.__selected_component.association: if self.__selected_component.association.max_ is not None: self.__max_spinbox_var.set( self.__selected_component.association.max_) else: if self.__selected_component.association.min_ is not None: # If component has a minimum association, set the value of Spinbox to it self.__max_spinbox_var.set( self.__selected_component.association.min_) else: # Otherwise set it to 0 self.__max_spinbox_var.set(0) self.__selected_component.association.max_ = 0 else: if self.__selected_component.association: self.__selected_component.association.max_ = None change_controls_state(tk.DISABLED, self.__max_spinbox) self.__max_spinbox_var.set('') def __on_min_changed(self, *_): """Executed whenever the __min_spinbox_var value changes.""" if self.__selected_component and self.__selected_component.association: # This gets triggered at unpredicted moments (e.g. enabling and disabling widgets # so it's necessary to check this condition. try: min_ = self.__min_spinbox_var.get() self.__selected_component.association.min_ = min_ if self.__selected_component.association.max_ is not None \ and self.__selected_component.association.max_ < min_: # If max < min; set max to min self.__selected_component.association.max_ = min_ self.__max_spinbox_var.set(min_) except tk.TclError: self.__selected_component.association.min_ = None finally: self.__taxonomy_tree.update_values(self.__selected_component) def __on_max_changed(self, *_): """Executed whenever the __max_spinbox_var value changes.""" if self.__selected_component and self.__selected_component.association: try: max_ = self.__max_spinbox_var.get() self.__selected_component.association.max_ = max_ if self.__selected_component.association.min_ is not None \ and self.__selected_component.association.min_ > max_: # If min > max; set min to max self.__selected_component.association.min_ = max_ self.__min_spinbox_var.set(max_) except tk.TclError: self.__selected_component.association.max_ = None finally: self.__taxonomy_tree.update_values(self.__selected_component)