import Tkinter as tk import tkFont as tkFont import ttk as ttk from event import EventHook from ..exceptions.handling import ExceptionHandler class GridBox(object): def __init__(self, master, headers, row, column): self.master = master self.headers = headers self.items_dict = {} self.tree = None self.container = ttk.Frame(self.master) self.container.grid(row=row, column=column, sticky=tk.W+tk.E+tk.N+tk.S) self._set_up_tree_widget() self._build_tree() # create a popup menu self.pop_menu = tk.Menu(self.tree, tearoff=0) self.pop_menu.add_command(label="New", command=self.new) self.pop_menu.add_command(label="Remove", command=self.remove) self.pop_menu.add_command(label="Remove All", command=self.remove_all) self.pop_menu.add_command(label="Edit", command=self.edit) self.pop_menu_add = tk.Menu(self.tree, tearoff=0) self.pop_menu_add.add_command(label="New", command=self.new) self.pop_menu_add.add_command(label="Remove All", command=self.remove_all) self.tree.bind("<Button-2>", self.pop_up) self.tree.bind("<Button-3>", self.pop_up) self.onChange = EventHook() self.tip = None def clearTip(self): self.setTip("") def setTipNotRequired(self): self.setTip("Not Required") def setTip(self, text): if self.tip != None: self.tip['text'] = text def item_count(self): return len(self.items_dict) def pop_up(self, event): item = self.tree.identify_row(event.y) if item: # mouse pointer over item self.tree.selection_set(item) self.tree.update() self.pop_menu.post(event.x_root, event.y_root) else: self.pop_menu_add.post(event.x_root, event.y_root) def get_selected_key(self): selection = self.tree.selection() if len(selection) > 0: return selection[0] else: return None def get_selected(self): key = self.get_selected_key() if key != None: return self.items_dict[key] else: return None def new(self): pass def get_item_values(self, item): return {} def edit_item(self, item): pass def remove_all(self): keys = self.items_dict.keys() for key in keys: self.remove_item(key) def remove_item(self, key): del self.items_dict[key] self.tree.delete(key) def remove(self): selection = self.get_selected_key() if selection != None: self.remove_item(selection) self.onChange.fire() def edit(self): try: item = self.get_selected() if item != None: self.edit_item(item) self.onChange.fire() print except ExceptionHandler.ExceptionType as e: ExceptionHandler.add(e, "Cannot edit item") def add_item(self, item): values = self.get_tree_values(item) key = self.tree.insert('', 'end', values = values) self.items_dict[key] = item self.adjust_width(values) self.default_sort() self.onChange.fire() def redraw_item(self, key): item = self.items_dict[key] values = self.get_tree_values(item) self.tree.item(key, text='', values=values) self.adjust_width(values) self.default_sort() def adjust_width(self, values): # adjust column's width if necessary to fit each value for ix, val in enumerate(values): if not val is None: try: col_w = tkFont.Font().measure(val) if self.tree.column(self.headers[ix],width=None)<col_w: self.tree.column(self.headers[ix], width=col_w) except ExceptionHandler.ExceptionType as e: ExceptionHandler.add("Cannot adjust column width {0}: {1}".format(val, e)) def get_tree_values(self, item): values = [] values_dict = self.get_item_values(item) for header in self.headers: values.append(values_dict[header]) return values def add_items(self, items): for item in items: self.add_item(item) self.default_sort() def get_items(self): return self.items_dict.values() def double_click(self, event): key = self.tree.identify('item', event.x, event.y) if key in self.items_dict: item = self.items_dict[key] self.edit_item(item) def _set_up_tree_widget(self): tree_container = ttk.Frame(self.container) tree_container.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S) #tree_container.pack(fill='both', expand=True) # create a treeview with dual scrollbars self.tree = ttk.Treeview(tree_container, columns=self.headers, show="headings") vsb = ttk.Scrollbar(tree_container, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_container, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(column=0, row=0, sticky='nsew') vsb.grid(column=1, row=0, sticky='ns') hsb.grid(column=0, row=1, sticky='ew') tree_container.grid_columnconfigure(0, weight=1) tree_container.grid_rowconfigure(0, weight=1) self.tree.bind("<Double-1>", self.double_click) def get_header_width(self, header): return tkFont.Font().measure(header.title()) * self.get_header_scale() def get_header_scale(self): return 1 def _build_tree(self): for col in self.headers: self.tree.heading(col, text=col.title(), command=lambda c=col: self.sortby(self.tree, c, 0)) # adjust the column's width to the header string self.tree.column(col, width=self.get_header_width(col)) def change_numeric(self, data): new_items = [] for item in data: new_item = (float(item[0]), item[1]) new_items.append(new_item) return new_items def preprocess_sort_values(self, data): return data def sortby(self, tree, col, descending): """sort tree contents when a column header is clicked on""" # grab values to sort data = [(tree.set(child, col), child) \ for child in tree.get_children('')] # if the data to be sorted is numeric change to float #data = change_numeric(data) # now sort the data in place data = self.preprocess_sort_values(data) data.sort(reverse=descending) for ix, item in enumerate(data): tree.move(item[1], '', ix) # switch the heading so it will sort in the opposite direction tree.heading(col, command=lambda col=col: self.sortby(tree, col, \ int(not descending))) def default_sort(self): self.sort_by_column_index(0) def sort_by_column_index(self, index): if len(self.headers) < 1: return first_column = self.headers[index] self.sortby(self.tree, first_column, False) class DialogGridBox(GridBox): def __init__(self, master, parent_dialog, row, column): self.parent_dialog = parent_dialog headers = self.get_headers() GridBox.__init__(self, master, headers, row, column) def get_headers(self): pass def get_item_values(self, item): pass def new_dialog(self, master, parent_dialog, item): pass def new(self): dialog = self.new_dialog(self.master, self.parent_dialog, None) if dialog.is_ok: self.add_item(dialog.item) self.onChange.fire() def edit_item(self, item): try: key = self.get_selected_key() item = self.items_dict[key] self.new_dialog(self.master, self.parent_dialog, item) self.redraw_item(key) self.onChange.fire() except ExceptionHandler.ExceptionType as e: ExceptionHandler.add(e, "ERROR editing item") def remove(self): GridBox.remove(self)