class StructEditor(tk.Frame, Subscriber, Observable): """Displays and allow editing of the coordinates and points of one superstructure Args: parent (tk.Frame): widget that is the parent of the editor structure (model.structure.Structure): the ship superstructure that will be edited """ def __init__(self, parent, structure, command_stack): Subscriber.__init__(self, structure) Observable.__init__(self) tk.Frame.__init__(self, parent, borderwidth=4, relief="raised") self._structure = structure self._command_stack = command_stack self.bind("<Button-1>", self._on_click) self.bind("<FocusIn>", self._on_get_focus) self.bind("<FocusOut>", self._on_lost_focus) self._tree = Treeview(self, columns=["#", "X", "Y"], selectmode="browse") #kill the icon column self._tree.column("#0", minwidth=0, width=0) style = Style() style.configure("Treeview.Heading", font=(None, 16)) self._tree.column("#", minwidth=20, width=40, anchor=tk.CENTER) self._tree.column("X", minwidth=20, width=40, anchor=tk.CENTER) self._tree.column("Y", minwidth=20, width=40, anchor=tk.CENTER) self._tree.heading("#", text="#") self._tree.heading("X", text="\u21d5") self._tree.heading("Y", text="\u21d4") self._tree.grid(row=0, column=POINTS_TABLE_COL, sticky=tk.N + tk.S) self._tree.bind("<<TreeviewSelect>>", self._on_point_selected) self._tree.bind("<FocusIn>", self._on_get_focus) self._tree.bind("<FocusOut>", self._on_lost_focus) scroll = Scrollbar(self, command=self._tree.yview) scroll.grid(row=0, column=SCROLL_COL, sticky=tk.N + tk.S) scroll.bind("<FocusIn>", self._on_get_focus) self._tree.configure(yscrollcommand=scroll.set) self._index_of_sel_point = -1 self._fill_tree() self._edit_zone = EditZone(self, self._structure, command_stack, self._on_get_focus) self._edit_zone.grid(column=EDIT_ZONE_COL, row=0, sticky=tk.N) def _set_selection(self, new_sel_index): """Set the selected point to the new_sel_index Gives correct focus, update, etc to the editor's widgets if the index is outside of the self.points, does nothing """ if new_sel_index >= 0 and new_sel_index <= len(self.points) - 1: iid = self._tree.get_children()[new_sel_index] self._tree.selection_set(iid) def _on_click(self, *_args): self._tree.focus_set() def _on_get_focus(self, *_args): if self._index_of_sel_point == -1: self._set_selection(0) self.configure(relief="sunken") self._notify("focus", {}) def _on_lost_focus(self, event): if event.widget not in self.winfo_children(): self.configure(relief="raised") def _on_point_selected(self, _event): """called back when a point is selected in the table/treeview Updates the editable fields """ selected_iid = self._tree.selection() self._index_of_sel_point = self._tree.index(selected_iid) self._edit_zone.set_editable_point( self._tree.item(selected_iid)["values"][0]) self._notify("focus", {}) def _fill_tree(self): """fills the treeview with data from the structure """ self._tree.delete(*self._tree.get_children()) for point_index, point in enumerate(self._structure.points): self._tree.insert( '', 'end', values=[point_index, round(point[0]), round(point[1])]) if point_index == self._index_of_sel_point: self._set_selection(point_index) def _on_notification(self, observable, event_type, event_info): """Rebuild the treeview on structure update Depending on the structure state and the operation, change the selcted point """ if event_type == "add_point": self._index_of_sel_point = event_info["index"] self._fill_tree() else: if self._index_of_sel_point >= len(self._structure.points): self._index_of_sel_point = len(self._structure.points) self._edit_zone.unset_point() self._fill_tree() self._notify("focus", {}) def update_to_coord(self, point): """Move the selected point to the position of the given point Intended to be called from click on the top view Args: point (x, y): new position in funnel coordinates """ if self._index_of_sel_point != -1 and self._index_of_sel_point <= len( self.points) - 1: self._command_stack.do( model.structure.UpdatePoint(self._structure, self._index_of_sel_point, round(point[0]), round(point[1]))) elif self._index_of_sel_point == len(self.points) or not self.points: self._command_stack.do( model.structure.AddPoint(self._structure, self._index_of_sel_point + 1, round(point[0]), round(point[1]))) if self._index_of_sel_point + 1 >= len(self.points): self.winfo_toplevel().update() self._index_of_sel_point = len(self.points) else: self._set_selection(self._index_of_sel_point + 1) self.winfo_toplevel().update() @property def points(self): """Pipe throught the struct's properties""" return self._structure.points @property def fill(self): """Pipe throught the struct's properties""" return self._structure.fill @property def selected_index(self): """the index in the struct's point list of the currently selected point Should be -1 if none selected """ return self._index_of_sel_point
class MainWindow: """ Класс главного окна """ def __init__(self, top=None): """ Создание всех элементов окна """ top.geometry("1200x570+120+20") top.title("Football Analyser") top.configure(background=config.background_color) top.configure(highlightbackground=config.background_color) top.configure(highlightcolor="black") self.tree_view = Treeview(top, show="headings") self.first_name_label = Label(top) self.last_name_label = Label(top) self.country_label = Label(top) self.age_label = Label(top) self.plays_label = Label(top) self.goals_label = Label(top) self.first_name_entry = Entry(top) self.last_name_entry = Entry(top) self.country_entry = Entry(top) self.age_entry = Entry(top) self.plays_entry = Entry(top) self.goals_entry = Entry(top) self.entries_list = [ self.first_name_entry, self.last_name_entry, self.country_entry, self.age_entry, self.plays_entry, self.goals_entry ] self.add_button = Button(top) self.delete_button = Button(top) self.modify_button = Button(top) self.clear_fields_button = Button(top) self.analyze_button = Button(top) self.configure_tree_view()\ .configure_labels()\ .configure_entries()\ .configure_buttons()\ .fill_on_start() def configure_tree_view(self): """ Настройка treeview для отображения всех записей """ self.tree_view.place(relx=0.008, rely=0.018, relheight=0.837, relwidth=0.754) self.tree_view["columns"] = ("First name", "Last name", "Country", "Age", "Plays", "Goals") self.tree_view.column("First name", width=200) self.tree_view.column("Last name", width=200) self.tree_view.column("Country", width=100) self.tree_view.column("Age", width=100) self.tree_view.column("Plays", width=100) self.tree_view.column("Goals", width=100) self.tree_view.heading("First name", text="First name") self.tree_view.heading("Last name", text="Last name") self.tree_view.heading("Country", text="Country") self.tree_view.heading("Age", text="Age") self.tree_view.heading("Plays", text="Plays") self.tree_view.heading("Goals", text="Goals") self.tree_view.bind("<<TreeviewSelect>>", lambda event: self.on_select_item()) return self def configure_labels(self): """ Настройка текста над полями ввода """ self.first_name_label.place(relx=0.775, rely=0.07, height=26, width=74) self.first_name_label.configure(background=config.background_color) self.first_name_label.configure(text="First name") self.last_name_label.place(relx=0.775, rely=0.193, height=26, width=73) self.last_name_label.configure(background=config.background_color) self.last_name_label.configure(text="Last name") self.country_label.place(relx=0.775, rely=0.316, height=26, width=57) self.country_label.configure(background=config.background_color) self.country_label.configure(text="Country") self.age_label.place(relx=0.775, rely=0.439, height=26, width=33) self.age_label.configure(background=config.background_color) self.age_label.configure(text="Age") self.plays_label.place(relx=0.775, rely=0.561, height=26, width=39) self.plays_label.configure(background=config.background_color) self.plays_label.configure(text="Plays") self.goals_label.place(relx=0.775, rely=0.684, height=26, width=43) self.goals_label.configure(background=config.background_color) self.goals_label.configure(text="Goals") return self def configure_entries(self): """ Настройка полей ввода """ self.first_name_entry.place(relx=0.775, rely=0.123, height=24, relwidth=0.17) self.first_name_entry.configure(font=config.font) self.last_name_entry.place(relx=0.775, rely=0.246, height=24, relwidth=0.17) self.last_name_entry.configure(font=config.font) self.country_entry.place(relx=0.775, rely=0.368, height=24, relwidth=0.17) self.country_entry.configure(font=config.font) self.age_entry.place(relx=0.775, rely=0.491, height=24, relwidth=0.17) self.age_entry.configure(font=config.font) self.plays_entry.place(relx=0.775, rely=0.614, height=24, relwidth=0.17) self.plays_entry.configure(font=config.font) self.goals_entry.place(relx=0.775, rely=0.737, height=24, relwidth=0.17) self.goals_entry.configure(font=config.font) return self def configure_buttons(self): """ Настройка кнопок """ self.add_button.place(relx=0.792, rely=0.807, height=33, width=40) self.add_button.configure(background=config.background_color) self.add_button.configure(text="Add") self.add_button.configure(command=self.add_item) self.delete_button.place(relx=0.9, rely=0.807, height=33, width=56) self.delete_button.configure(background=config.background_color) self.delete_button.configure(text="Delete") self.delete_button.configure(command=self.delete_item) self.modify_button.place(relx=0.842, rely=0.807, height=33, width=59) self.modify_button.configure(background=config.background_color) self.modify_button.configure(text="Modify") self.modify_button.configure(command=self.modify_item) self.clear_fields_button.place(relx=0.8, rely=0.895, height=33, width=166) self.clear_fields_button.configure(background=config.background_color) self.clear_fields_button.configure(text="Clear fields") self.clear_fields_button.configure(command=self.clear_all_entries) self.analyze_button.place(relx=0.225, rely=0.877, height=53, width=336) self.analyze_button.configure(background=config.background_color) self.analyze_button.configure(text="Analyze") self.analyze_button.configure(font="-size 18") self.analyze_button.configure(command=self.analyze) return self def fill_on_start(self): """ Заполнение treeview записями из базы данных """ for row in db.get_records(): self.tree_view.insert("", tk.END, values=row) return self def on_select_item(self): """ Отображение выбранной записи в полях ввода для редактирования """ values = self.tree_view.item(self.tree_view.focus())["values"] for entry, val in zip(self.entries_list, values): entry.delete(0, tk.END) entry.insert(0, val) def clear_all_entries(self): """ Очистка всех полей ввода """ for entry in self.entries_list: entry.delete(0, tk.END) def delete_item(self): """ Удаление записи """ item = self.tree_view.focus() db.delete_record(self.tree_view.index(item)) self.tree_view.delete(item) self.clear_all_entries() def add_item(self): """ Добавление записи """ try: first_name = validator.validate_text(self.first_name_entry.get()) last_name = validator.validate_text(self.last_name_entry.get()) country = validator.validate_text(self.country_entry.get()) age = validator.validate_number(self.age_entry.get()) plays = validator.validate_number(self.plays_entry.get()) goals = validator.validate_number(self.goals_entry.get()) db.insert_record({ "first_name": first_name, "last_name": last_name, "country": country, "age": age, "plays": plays, "goals": goals }) self.tree_view.insert("", tk.END, values=(first_name, last_name, country, age, plays, goals)) except ValueError: messagebox.showerror("Invalid input", "Input are not valid string or number") self.on_select_item() def modify_item(self): """ Изменение записи """ try: item = self.tree_view.focus() index = self.tree_view.index(item) first_name = validator.validate_text(self.first_name_entry.get()) last_name = validator.validate_text(self.last_name_entry.get()) country = validator.validate_text(self.country_entry.get()) age = validator.validate_number(self.age_entry.get()) plays = validator.validate_number(self.plays_entry.get()) goals = validator.validate_number(self.goals_entry.get()) db.update_record( index, (first_name, last_name, country, age, plays, goals)) self.tree_view.item(item, values=(first_name, last_name, country, age, plays, goals)) except ValueError: messagebox.showerror("Invalid input", "Input are not valid string or number") self.on_select_item() def analyze(self): """ Вызов анализа """ analyse.full_analysis() messagebox.showinfo("Done", "Файлы отчета сохранены в output и graphics")
class StoreView(BaseView): ######## # Initializes and places all GUI elements. def _create_widgets(self): # left frame self._leftFrame = Frame(self) self._imgLabel = Label(self._leftFrame) self._locLabel = Label(self._leftFrame, text="Location:", font=BaseView.NORMAL_FONT) self._locPreview = SimpleLocationView(self._leftFrame) # right frame self._rightFrame = Frame(self) self._nameLabel = Label(self._rightFrame, compound=LEFT, text="Name", font=BaseView.LARGE_FONT) self._descLabel = Label(self._rightFrame, text="Description:", font=BaseView.NORMAL_FONT) self._descFrame = Frame(self._rightFrame) self._descText = Text(self._descFrame, width=50, height=4, state=DISABLED, wrap=WORD, font=BaseView.NORMAL_FONT) self._descScroll = Scrollbar(self._descFrame, command=self._descText.yview) self._descText.config(yscrollcommand=self._descScroll.set) self._notesLabel = Label(self._rightFrame, text="Notes:", font=BaseView.NORMAL_FONT) self._notesFrame = Frame(self._rightFrame) self._notesText = Text(self._notesFrame, width=50, height=4, state=DISABLED, wrap=WORD, font=BaseView.NORMAL_FONT) self._notesScroll = Scrollbar(self._notesFrame, command=self._notesText.yview) self._notesText.config(yscrollcommand=self._notesScroll.set) # bottom self._sep1 = Separator(self, orient="horizontal") self._invLabel = Label(self, text="Inventory:", font=BaseView.NORMAL_FONT) self._invFrame = Frame(self) self._inventory = Treeview( self._invFrame, columns=["type", "price", "qty", "stockDays"], selectmode="browse", height=15) self._inventory.heading("#0", text="Item", anchor=N + W) self._inventory.column("#0", width=300, anchor=N + W, stretch=True) self._inventory.heading("type", text="Type", anchor=N + W) self._inventory.column("type", width=100, anchor=N + W) self._inventory.heading("price", text="Price", anchor=N + W) self._inventory.column("price", width=60, anchor=N + W) self._inventory.heading("qty", text="Qty", anchor=N + W) self._inventory.column("qty", width=40, anchor=N + W) self._inventory.heading("stockDays", text="Stock Days", anchor=N + W) self._inventory.column("stockDays", width=200, anchor=N + W, stretch=True) self._invScroll = Scrollbar(self._invFrame, command=self._inventory.yview) self._inventory.config(yscrollcommand=self._invScroll.set) # placement: scrollbars self._descText.grid(row=0, column=0, sticky=N + W + E + S) self._descScroll.grid(row=0, column=1, sticky=N + S) self._notesText.grid(row=0, column=0, sticky=N + W + E + S) self._notesScroll.grid(row=0, column=1, sticky=N + S) self._inventory.grid(row=0, column=0, sticky=N + W + E + S) self._invScroll.grid(row=0, column=1, sticky=N + S) # placement: left frame self._imgLabel.grid(row=0, column=0, sticky=N + W + E + S) self._locLabel.grid(row=1, column=0, sticky=N + W) self._locPreview.grid(row=2, column=0, sticky=W + E) # placement: right frame self._nameLabel.grid(row=0, column=0, sticky=W) self._descLabel.grid(row=1, column=0, sticky=W) self._descFrame.grid(row=2, column=0, sticky=W) self._notesLabel.grid(row=3, column=0, sticky=W) self._notesFrame.grid(row=4, column=0, sticky=W) # bottom self._sep1.grid(row=1, column=0, columnspan=2, sticky=W + E) self._invLabel.grid(row=2, column=0, columnspan=2, sticky=N + W) self._invFrame.grid(row=3, column=0, columnspan=2, sticky=W + E) self._leftFrame.grid(row=0, column=0, sticky=N + W) self._rightFrame.grid(row=0, column=1, sticky=N + W) ######## # Add callbacks for all GUI element events and Tkinter variables. def _bind_widgets(self): self._locPreview.bind("<Double-Button-1>", self._open_loc) self._inventory.bind("<Double-Button-1>", self._inventory_on_double_click) ######## # Populates all GUI elements with new data. def populate(self, data): self._data = data if data == None: # null check self.set_defaults() return for k, v in data.items(): if k == "name": # non-null self._nameLabel.config(text=v) elif k == "img": if v == None: # null check v = BaseView.DEFAULT_IMG utility.update_img(self._imgLabel, v, maxSize=300) elif k == "location": # non-null self._locPreview.populate(v) elif k == "description": if v == None: # null check v = BaseView.EMPTY_STR utility.update_text(self._descText, v) elif k == "notes": if v == None: # null check v = BaseView.EMPTY_STR utility.update_text(self._notesText, v) elif k == "sells": self._update_inventory() ######## # Resets GUI elements to default values. def set_defaults(self): utility.update_img(self._imgLabel, BaseView.DEFAULT_IMG, maxSize=300) utility.update_text(self._descText, BaseView.EMPTY_STR) utility.update_text(self._notesText, BaseView.EMPTY_STR) ######## # Populates inventory with correct values def _update_inventory(self): allItems = self._inventory.get_children() for item in allItems: self._inventory.delete(item) self._imgs = [] if self._data == None: return for entry in self._data["sells"]: img = entry["item"]["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["item"]["name"] fields = [ BaseView.TYPE_MAP[entry["item"]["type"]], entry["price"], entry["qty"], entry["stockDays"] ] for i in range(len(fields)): if fields[i] == None: fields[i] = BaseView.EMPTY_STR self._imgs.append(img) self._inventory.insert("", END, image=img, text=name, values=fields) ######## # Opens location view through refBook. def _open_loc(self, *args, **kwargs): if self._refBook == None or self._data == None: return self._refBook.show_location(self._data["location"]["name"]) ######## # Callback for double clicking in inventory treeview. def _inventory_on_double_click(self, *args, **kwargs): if self._refBook == None or self._data == None or len( self._inventory.selection()) == 0: return self._refBook.show_item(self._data["sells"][self._inventory.index( self._inventory.selection())]["item"]["name"])
class App(Frame): """ Main App with treeview """ def __init__(self, parent=None, *args, **kwargs): Frame.__init__(self, parent) self.parent = parent # Create scrollbar scrollbar = Scrollbar(self) scrollbar.pack(side=RIGHT, fill=Y) # Create Treeview self.tree = Treeview(self, columns=('#0', '#1', '#2'), selectmode="browse", yscrollcommand=scrollbar.set) self.tree.pack(expand=TRUE, fill=X) scrollbar.config(command=self.tree) # Setup column heading self.tree.heading('#0', text='Thumbnail', anchor='center') self.tree.heading('#1', text='Name', anchor='center') self.tree.heading('#2', text='Origin Text', anchor='nw') # Setup column self.tree.column('#0', stretch=NO) self.tree.column('#1', stretch=NO) self.tree.column('#2', anchor='nw', minwidth=300) # Bind event self.tree.bind('<Double-1>', self.show_menu) # Variables init self.video_path_list = [] self.video_texts_list = [] self.video_name_list = [] self.video_images = [] self.load_video_info() def load_video_info(self): """ Load video text information """ self.video_path_list = get_video_list() self.video_texts_list = [] self.video_name_list = [] for path in self.video_path_list: self.video_texts_list.append( get_video_texts(get_video_info_filename(path))) self.video_name_list.append( get_video_names(get_video_meta_filename(path))) self.video_images = [] images = [ Image.open(get_cover_image_filename(path)) for path in self.video_path_list ] for img in images: img = img.resize((160, 90), Image.ANTIALIAS) self.video_images.append(ImageTk.PhotoImage(img)) for i in range(0, len(self.video_images)): txt = '\n'.join(self.video_texts_list[i][:5]) self.tree.insert('', 'end', image=self.video_images[i], value=(self.video_name_list[i], txt), tags='video') def refresh_treeview(self): """ Reload all treeview items after translate or replace image complete """ self.tree.delete(*self.tree.get_children()) self.update() self.load_video_info() self.update() def show_menu(self, event): """ Show menu when double click on treeview item :param event: Treeview double click event objject """ menu = tkinter.Menu(self, tearoff=0) menu.add_command(label='Translate to TW', command=self.do_translate) menu.add_command(label='Export SRT', command=self.export_srt) menu.add_command(label='Replace cover image', command=self.replace_cover_image) try: menu.tk_popup(event.x_root, event.y_root) finally: menu.grab_release() def do_translate(self): """ Translate selected video's texts from CN to TW """ index = self.tree.index(self.tree.focus()) texts = self.video_texts_list[index] tw_texts = [] progress_var = tkinter.DoubleVar() progress_step = float(100.0 / len(texts)) progress = 0 popup = tkinter.Toplevel(self.parent) popup.geometry('300x50') popup.transient(self.parent) popup.grab_set() popup.protocol('WM_DELETE_WINDOW', disable_popup_close) tkinter.Label(popup, text="Translating...").grid(row=0, column=0) progress_bar = ttk.Progressbar(popup, variable=progress_var, maximum=100, length=300) progress_bar.grid(row=1, column=0) popup.pack_slaves() try: for i in range(0, len(texts)): popup.update() tw_texts.append(do_single_translate(texts[i])) progress += progress_step progress_var.set(progress) except: messagebox.showerror(title='Error', message='Translate fail! Please try again') set_video_texts(tw_texts, get_video_info_filename(self.video_path_list[index])) popup.destroy() messagebox.showinfo(title='Success', message='Translate success!') self.refresh_treeview() def export_srt(self): """ Export selected video's subtitle to SRT file """ index = self.tree.index(self.tree.focus()) filename = simpledialog.askstring("File name", "What is SRT file name?", parent=self) f = open(get_video_info_filename(self.video_path_list[index]), encoding='utf-8') txt = f.read() f.close() subtitle_txt = analyseFile(txt) subtitle_srt = createSrt(subtitle_txt) f = open(get_export_srt_filename(filename), "w", encoding='utf-8') f.write(subtitle_srt) f.close() messagebox.showinfo( title='Export', message='Export srt file success!\nPlease check it on the Desktop.' ) def replace_cover_image(self): """ Replace selected video cover image """ index = self.tree.index(self.tree.focus()) img_file = filedialog.askopenfilename( parent=self, initialdir=os.getcwd(), title="Please select a JPEG file:", filetypes=[('JPEG', ".jpg")]) cover_image_filename = get_cover_image_filename( self.video_path_list[index]) shutil.copy(img_file, cover_image_filename) messagebox.showinfo(title='Replace', message='Replace cover image success!') self.refresh_treeview()
class RefBook(Frame): ######## # Opens database from provided arguments and creates and populates widgets. def __init__(self, master, *args, **kwargs): super().__init__(master) self._dbm = DatabaseManager(*args, **kwargs) self._openItems = {} self._openStores = {} self._openAttacks = {} self._openCreatures = {} self._openLocations = {} self._create_widgets() self._populate_items() self._populate_stores() self._populate_attacks() self._populate_creatures() self._populate_locations() ######## # Initializes and places all GUI elements. def _create_widgets(self): self._nb = Notebook(self) # items self._itemTab = Frame(self._nb) self._itemTree = Treeview(self._itemTab, columns=["type", "value"], selectmode="browse", height=25) self._itemTree.heading("#0", text="Item", anchor=N + W) self._itemTree.column("#0", width=300, anchor=N + W, stretch=True) self._itemTree.heading("type", text="Type", anchor=N + W) self._itemTree.column("type", width=100, anchor=N + W) self._itemTree.heading("value", text="Value", anchor=N + W) self._itemTree.column("value", width=60, anchor=N + W) self._itemScroll = Scrollbar(self._itemTab, command=self._itemTree.yview) self._itemTree.config(yscrollcommand=self._itemScroll.set) self._itemTree.grid(row=0, column=0, sticky=N + W + S + E) self._itemScroll.grid(row=0, column=1, sticky=N + S) self._nb.add(self._itemTab, text="Items") # stores self._storeTab = Frame(self._nb) self._storeTree = Treeview(self._storeTab, columns=["location"], selectmode="browse", height=25) self._storeTree.heading("#0", text="Store", anchor=N + W) self._storeTree.column("#0", width=330, anchor=N + W, stretch=True) self._storeTree.heading("location", text="Location", anchor=N + W) self._storeTree.column("location", width=130, anchor=N + W) self._storeScroll = Scrollbar(self._storeTab, command=self._storeTree.yview) self._storeTree.config(yscrollcommand=self._storeScroll.set) self._storeTree.grid(row=0, column=0, sticky=N + W + S + E) self._storeScroll.grid(row=0, column=1, sticky=N + S) self._nb.add(self._storeTab, text="Stores") # attacks self._attackTab = Frame(self._nb) self._attackTree = Treeview(self._attackTab, columns=["id", "spell"], selectmode="browse", height=25) self._attackTree.heading("#0", text="Attacks", anchor=N + W) self._attackTree.column("#0", width=370, anchor=N + W, stretch=True) self._attackTree.heading("id", text="ID", anchor=N + W) self._attackTree.column("id", width=40, anchor=N + E) self._attackTree.heading("spell", text="Spell", anchor=N + W) self._attackTree.column("spell", width=50, anchor=CENTER) self._attackScroll = Scrollbar(self._attackTab, command=self._attackTree.yview) self._attackTree.config(yscrollcommand=self._attackScroll.set) self._attackTree.grid(row=0, column=0, sticky=N + W + S + E) self._attackScroll.grid(row=0, column=1, sticky=N + S) self._nb.add(self._attackTab, text="Attacks") # creatures self._creatureTab = Frame(self._nb) self._creatureTree = Treeview(self._creatureTab, columns=["hd"], selectmode="browse", height=25) self._creatureTree.heading("#0", text="Creatures", anchor=N + W) self._creatureTree.column("#0", width=420, anchor=N + W, stretch=True) self._creatureTree.heading("hd", text="HD", anchor=N + W) self._creatureTree.column("hd", width=40, anchor=N + E) self._creatureScroll = Scrollbar(self._creatureTab, command=self._creatureTree.yview) self._creatureTree.config(yscrollcommand=self._creatureScroll.set) self._creatureTree.grid(row=0, column=0, sticky=N + W + S + E) self._creatureScroll.grid(row=0, column=1, sticky=N + S) self._nb.add(self._creatureTab, text="Creatures") # locations self._locationTab = Frame(self._nb) self._locationTree = Treeview(self._locationTab, selectmode="browse", height=25) self._locationTree.heading("#0", text="Locations", anchor=N + W) self._locationTree.column("#0", width=460, anchor=N + W, stretch=True) self._locationScroll = Scrollbar(self._locationTab, command=self._locationTree.yview) self._locationTree.config(yscrollcommand=self._locationScroll.set) self._locationTree.grid(row=0, column=0, sticky=N + W + S + E) self._locationScroll.grid(row=0, column=1, sticky=N + S) self._nb.add(self._locationTab, text="Locations") self._nb.grid(row=0, column=0, sticky=N + W + S + E) # bindings self._itemTree.bind("<Double-Button-1>", self._item_on_double_click) self._storeTree.bind("<Double-Button-1>", self._store_on_double_click) self._attackTree.bind("<Double-Button-1>", self._attack_on_double_click) self._creatureTree.bind("<Double-Button-1>", self._creature_on_double_click) self._locationTree.bind("<Double-Button-1>", self._location_on_double_click) ######## # Populates item list. def _populate_items(self): self._itemList = self._dbm.get_item_list() allItems = self._itemTree.get_children() for item in allItems: self._itemTree.delete(item) self._itemImgs = [] if self._dbm == None: return for entry in self._itemList: img = entry["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["name"] fields = [BaseView.TYPE_MAP[entry["type"]], entry["value"]] for i in range(len(fields)): if fields[i] == None: fields[i] = BaseView.EMPTY_STR self._itemImgs.append(img) self._itemTree.insert("", END, image=img, text=name, values=fields) ######## # Populates store list. def _populate_stores(self): self._storeList = self._dbm.get_store_list() allStores = self._storeTree.get_children() for store in allStores: self._storeTree.delete(store) self._storeImgs = [] if self._dbm == None: return for entry in self._storeList: img = entry["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["name"] fields = [entry["location"]] for i in range(len(fields)): if fields[i] == None: fields[i] = BaseView.EMPTY_STR self._storeImgs.append(img) self._storeTree.insert("", END, image=img, text=name, values=fields) ######## # Populates attack list. def _populate_attacks(self): self._attackList = self._dbm.get_attack_list() allAttacks = self._attackTree.get_children() for attack in allAttacks: self._attackTree.delete(attack) self._attackImgs = [] if self._dbm == None: return for entry in self._attackList: img = entry["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["name"] fields = [entry["id"], "✔" if entry["isSpell"] else ""] for i in range(len(fields)): if fields[i] == None: fields[i] = BaseView.EMPTY_STR self._attackImgs.append(img) self._attackTree.insert("", END, image=img, text=name, values=fields) ######## # Populates creature list. def _populate_creatures(self): self._creatureList = self._dbm.get_creature_list() allCreatures = self._creatureTree.get_children() for creature in allCreatures: self._creatureTree.delete(creature) self._creatureImgs = [] if self._dbm == None: return for entry in self._creatureList: img = entry["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["name"] fields = [str(entry["hd"])] for i in range(len(fields)): if fields[i] == None: fields[i] = BaseView.EMPTY_STR self._creatureImgs.append(img) self._creatureTree.insert("", END, image=img, text=name, values=fields) ######## # Populates location list. def _populate_locations(self): self._locationList = self._dbm.get_location_list() allLocations = self._locationTree.get_children() for location in allLocations: self._locationTree.delete(location) self._locationImgs = [] if self._dbm == None: return for entry in self._locationList: img = entry["img"] if img == None: img = BaseView.DEFAULT_IMG img = utility.get_img(img, maxSize=20) name = entry["name"] self._locationImgs.append(img) self._locationTree.insert("", END, image=img, text=name) ######## # Callback for double clicking in item treeview. def _item_on_double_click(self, *args, **kwargs): if len(self._itemTree.selection()) == 0: return self.show_item(self._itemList[self._itemTree.index( self._itemTree.selection())]["name"]) ######## # Callback for double clicking in store treeview. def _store_on_double_click(self, *args, **kwargs): if len(self._storeTree.selection()) == 0: return curr = self._storeList[self._storeTree.index( self._storeTree.selection())] self.show_store(curr["name"], curr["location"]) ######## # Callback for double clicking in attack treeview. def _attack_on_double_click(self, *args, **kwargs): if len(self._attackTree.selection()) == 0: return self.show_attack(self._attackList[self._attackTree.index( self._attackTree.selection())]["id"]) ######## # Callback for double clicking in creature treeview. def _creature_on_double_click(self, *args, **kwargs): if len(self._creatureTree.selection()) == 0: return self.show_creature(self._creatureList[self._creatureTree.index( self._creatureTree.selection())]["name"]) ######## # Callback for double clicking in location treeview. def _location_on_double_click(self, *args, **kwargs): if len(self._locationTree.selection()) == 0: return self.show_location(self._locationList[self._locationTree.index( self._locationTree.selection())]["name"]) ######## # Shows a full item in a new window. def show_item(self, name): if name in self._openItems.keys(): # TODO: make this cleaner try: self._openItems[name].lift() self._openItems[name].focus_set() return except TclError: pass # fallthrough to make new window newWindow = Toplevel(self) itemView = ItemView(newWindow, self._dbm.get_item(name), refBook=self) itemView.grid(row=0, column=0, sticky=N + W + E + S) newWindow.title(name) newWindow.lift() newWindow.focus_set() self._openItems[name] = newWindow ######## # Shows a full store in a new window. def show_store(self, name, location): key = (name, location) if key in self._openStores.keys(): # TODO: make this cleaner try: self._openStores[key].lift() self._openStores[key].focus_set() return except TclError: pass # fallthrough to make new window newWindow = Toplevel(self) storeView = StoreView(newWindow, self._dbm.get_store(name, location), refBook=self) storeView.grid(row=0, column=0, sticky=N + W + E + S) newWindow.title(name) newWindow.lift() newWindow.focus_set() self._openStores[key] = newWindow ######## # Shows a full attack in a new window. def show_attack(self, attackId): if attackId in self._openAttacks.keys(): # TODO: make this cleaner try: self._openAttacks[attackId].lift() self._openAttacks[attackId].focus_set() return except TclError: pass # fallthrough to make new window attack = self._dbm.get_attack(attackId) newWindow = Toplevel(self) attackView = AttackView(newWindow, self._dbm.get_attack(attackId), refBook=self) attackView.grid(row=0, column=0, sticky=N + W + E + S) newWindow.title("{} ({})".format(attack["name"], attack["id"])) newWindow.lift() newWindow.focus_set() self._openAttacks[attackId] = newWindow ######## # Shows a full creature in a new window. def show_creature(self, name): if name in self._openCreatures.keys(): # TODO: make this cleaner try: self._openCreatures[name].lift() self._openCreatures[name].focus_set() return except TclError: pass # fallthrough to make new window newWindow = Toplevel(self) creatureView = CreatureView(newWindow, self._dbm.get_creature(name), refBook=self) creatureView.grid(row=0, column=0, sticky=N + W + E + S) newWindow.title(name) newWindow.lift() newWindow.focus_set() self._openCreatures[name] = newWindow ######## # Shows a full location in a new window. def show_location(self, name): if name in self._openLocations.keys(): # TODO: make this cleaner try: self._openLocations[name].lift() self._openLocations[name].focus_set() return except TclError: pass # fallthrough to make new window newWindow = Toplevel(self) locationView = LocationView(newWindow, self._dbm.get_location(name), refBook=self) locationView.grid(row=0, column=0, sticky=N + W + E + S) newWindow.title(name) newWindow.lift() newWindow.focus_set() self._openLocations[name] = newWindow
class ElementListWidget(Frame): def __init__(self, parent, label, columns, showError): Frame.__init__(self, parent) self.showError = showError self.columnconfigure(0, weight = 1) self.rowconfigure(1, weight = 1) # Название таблицы self.titleLabel = Label(self, text = label) self.titleLabel.grid(column = 0, row = 0, sticky = W + E) # Таблица значений columns = ("Метка", "№") + columns self.tree = Treeview(self, columns = columns, displaycolumns = columns, selectmode = "browse") self.tree.grid(column = 0, row = 1, sticky = W + N + E + S) # Настраиваем внешний вид таблицы (первые колонки) self.tree.column("#0", width = 0, stretch = 0) # Прячем колонку с иконкой self.tree.column( columns[0], anchor = W, width = 150) self.tree.heading(columns[0], anchor = W, text = columns[0]) self.tree.column( columns[1], anchor = E, width = 80) self.tree.heading(columns[1], anchor = E, text = columns[1]) self.tree.bind("<<TreeviewSelect>>", self.onSelectionChanged) # Панель с кнопками self.buttonPanel = Frame(self) self.buttonPanel.grid(column = 0, row = 2, sticky = W + E) self.buttonPanel.columnconfigure(0, weight = 1) self.buttonPanel.columnconfigure(3, minsize = emptySpaceSize, weight = 0) self.buttonPanel.columnconfigure(6, minsize = emptySpaceSize, weight = 0) self.buttonPanel.columnconfigure(9, weight = 1) # Кнопки добавления/удаления элемента self.buttonAdd = Button(self.buttonPanel, text = "+", width = 3, command = self.onButtonAddClicked) self.buttonAdd.grid(column = 1, row = 0) self.buttonRemove = Button(self.buttonPanel, text = "-", width = 3, state = DISABLED, command = self.onButtonRemoveClicked) self.buttonRemove.grid(column = 2, row = 0) # Кнопки перемещения элемента self.buttonUp = Button(self.buttonPanel, text = "↑", width = 3, state = DISABLED, command = self.onButtonUpClicked) self.buttonUp.grid(column = 4, row = 0) self.buttonDown = Button(self.buttonPanel, text = "↓", width = 3, state = DISABLED, command = self.onButtonDownClicked) self.buttonDown.grid(column = 5, row = 0) # Кнопки применить/отменить (для выбранного элемента) self.buttonCancel = Button(self.buttonPanel, text = "✗", width = 3, command = self.updateSelectedFrame) self.buttonCancel.grid(column = 7, row = 0) self.buttonApply = Button(self.buttonPanel, text = "✓", width = 3, command = self.onButtonApplyClicked) self.buttonApply.grid(column = 8, row = 0) # Редактирование выделенного элемента self.i = StringVar() self.label = (StringVar(), StringVar()) self.selectedFrame = Frame(self) self.selectedFrame.grid(column = 0, row = 3, sticky = W + E) # Номер Label(self.selectedFrame, text = "№:") \ .grid(column = 0, row = 0) Label(self.selectedFrame, textvariable = self.i, width = 3, justify = RIGHT) \ .grid(column = 1, row = 0) # Пустое пространство self.selectedFrame.columnconfigure(2, minsize = emptySpaceSize, weight = 0) # Метка Entry(self.selectedFrame, textvariable = self.label[0]) \ .grid(column = 3, row = 0, sticky = W + E) Entry(self.selectedFrame, textvariable = self.label[1], bg = defaultValueBG) \ .grid(column = 4, row = 0, sticky = W + E) # Виджет для элементов классов-потомков self.detailFrame = Frame(self.selectedFrame) self.detailFrame.grid(column = 3, row = 1, columnspan = 2, sticky = W + N + E + S) self.selectedFrame.columnconfigure(3, weight = 1) self.selectedFrame.columnconfigure(4, weight = 1) self.selectedFrame.rowconfigure(1, weight = 1) def onButtonUpClicked(self): item = self.selectedItem() if item is None: return prev = self.tree.prev(item) if prev != "": parent, index = self.tree.parent(item), self.tree.index(item) self.tree.move(item, parent, index - 1) # Корректируем номера элементов self.tree.set(item, "№", index - 1) self.tree.set(prev, "№", index) self.updateSelectedFrame(item) def onButtonDownClicked(self): item = self.selectedItem() if item is None: return next = self.tree.next(item) if next != "": parent, index = self.tree.parent(item), self.tree.index(item) self.tree.move(item, parent, index + 1) # Корректируем номера элементов self.tree.set(item, "№", index + 1) self.tree.set(next, "№", index) self.updateSelectedFrame(item) def onButtonAddClicked(self): pass def onButtonRemoveClicked(self): item = self.selectedItem() if item is None: return next = self.tree.next(item) self.tree.delete(item) while next != "": i = int(self.tree.set(next, "№")) self.tree.set(next, "№", i - 1) next = self.tree.next(next) self.onSelectionChanged() def onButtonApplyClicked(self, item = None): if item is None: item = self.selectedItem() if item is None: return None label = self.label[0].get() self.tree.set(item, "Метка", label) return item def onSelectionChanged(self, event = None): item = self.selectedItem() # Обновляем состояние кнопок state = DISABLED if item is None else NORMAL for x in (self.buttonRemove, self.buttonUp, self.buttonDown): x["state"] = state self.updateSelectedFrame(item) def selectedItem(self): selection = self.tree.selection() return None if type(selection) == type("") else selection[0] def clear(self): for item in self.tree.get_children(): self.tree.delete(item) def updateSelectedFrame(self, item = None, values = None): if item is None: item = self.selectedItem() values = None if item is None: i = "" label = "" else: if values is None: values = self.tree.set(item) i = values["№"] label = values["Метка"] self.i.set(i) self.label[0].set(label) return (item, values) def addElement(self, values): self.tree.insert(parent = "", index = END, values = values) def setDefaultElement(self, label): self.label[1].set(label) def elementsCount(self): return len(self.tree.get_children()) def elements(self, transform): return [ transform(item) for item in self.tree.get_children() ]
class ShowListUserDialog(Toplevel): def __init__(self,parent,**kw): Toplevel.__init__(self,parent,**kw) self.parent = parent self.numberTempUserAdd = 0 self.data = Database() self.listUser = self.data.getUserList() self.menu = Menu(self) self.config(menu = self.menu) self.drawMenu(self.menu) self.drawDialog() for user in self.listUser: self.addUserIntoTree(user) self.linkAccelerator() def linkAccelerator(self): self.bind_all("<Control-N>",self.onAddUser) self.bind_all("<Delete>", self.onDeleteUser) self.treeUser.bind(sequence="<Double-Button-1>",func=self.onEditUser) def drawMenu(self, parent): # Type Menu typeMenu = Menu(parent) typeMenu.add_command(label = "Thêm User", command = self.onAddUser, accelerator = "Ctrl+Shift+N") typeMenu.add_command(label = "Xóa User", command = self.onDeleteUser, accelerator = "Delete") parent.add_cascade(label = "Chức năng", menu = typeMenu) def drawDialog(self): layoutTreeUser = Frame(self) listAttribute = ["Password", "Họ tên"] yScrollTree = Scrollbar(layoutTreeUser, orient = VERTICAL) xScrollTree = Scrollbar(layoutTreeUser, orient = HORIZONTAL) # Create Delete, Add and Edit Button # Create Tree View self.treeUser = Treeview(layoutTreeUser, column = listAttribute, yscrollcommand = yScrollTree.set, xscrollcommand = xScrollTree.set ) self.treeUser.heading(column = "#0", text = "Username") self.treeUser.column(column = "#0", width = 100, minwidth = 100) for nameAttr in listAttribute: self.treeUser.heading(column = nameAttr, text = nameAttr) self.treeUser.column(column = nameAttr, width = 100, minwidth = 100) #Create Scrollbar for tree view yScrollTree.pack(side = RIGHT, fill = Y) xScrollTree.pack(side = BOTTOM, fill = X) self.treeUser.pack(side = TOP, anchor = "w", fill = BOTH, expand = True) yScrollTree.config(command = self.treeUser.yview) xScrollTree.config(command = self.treeUser.xview) layoutTreeUser.pack(side = TOP, fill = BOTH, expand = True) def addUserIntoTree(self,userInput): temp = userInput # if not self.treeUser.exists(temp.idParent) : # self.treeUser.insert("","end", str(temp.idParent), text = temp.idParent) # self.treeUser.item(temp.idParent, open = True) self.treeUser.insert("","end", str(temp.username), text = temp.username) self.treeUser.set(temp.username, "Password", temp.passWord) self.treeUser.set(temp.username, "Họ tên", temp.name) def onAddUser(self, event = None): if self.numberTempUserAdd > 9: messagebox.showwarning("Warning","Bạn chỉ có thể thêm tạm 10 username \n Hãy Sửa ID để lưu lại những loại tạm trên", parent = self) return userTemp = User(name = "#",username=str(self.numberTempUserAdd),passWord="******") self.addUserIntoTree(userTemp) self.numberTempUserAdd += 1 def onEditUser(self, event = None): listTemp = ["0","1","2","3","4","5","6","7","8","9"] curUser = self.treeUser.item(self.treeUser.focus()) col = self.treeUser.identify_column(event.x) print(curUser) print(col) if curUser["text"] in listTemp and col != "#0": if col != "#0": messagebox.showinfo("Thêm User","Sửa username tạm này để lưu lại vào Database",parent = self) return cellValue = None if col == "#0": temp = simpledialog.askstring("Đổi Username", "Nhập mới", parent = self) if temp != None: if len(temp)>0: cellValue = curUser["text"] try: if cellValue in listTemp: newUser = User(curUser["values"][1],temp,curUser["values"][0]) self.data.insertUser(newUser) self.treeUser.update() print("Sau khi sua:",self.treeUser.item(self.treeUser.focus())) else: self.data.updateUsernameOfUser(cellValue,temp) except (sqlite3.IntegrityError, TclError): messagebox.showwarning("Opps !!!!",message="Username bạn nhập đã tồn tại !!!!", parent = self) return if cellValue in listTemp: self.numberTempUserAdd -= 1 else: messagebox.showwarning("Empty !!!", "Username không thể để trống",parent = self) self.treeUser.insert("", str(self.treeUser.index(self.treeUser.focus())), temp ,text = temp, values = curUser["values"]) self.treeUser.delete(self.treeUser.focus()) return if col == "#1": temp = simpledialog.askstring("Đổi Password", "Nhập Password mới: ", parent = self) if temp != None: if len(temp) > 0: cellValue = curUser["values"][0] self.data.updatePassOfUser(curUser["text"],temp) curUser["values"][0] = temp print(curUser["text"]) self.treeUser.item(curUser["text"], values = curUser["values"]) self.treeUser.update() else: messagebox.showwarning("Empty !!!", "Password không thể để trống",parent = self) return if col == "#2": temp = simpledialog.askstring("Đổi tên", "Nhập tên mới",parent = self) if temp != None: if len(temp) > 0: cellValue = curUser["values"][1] self.data.updateNameOfuser(curUser["text"],temp) curUser["values"][1] = temp print(curUser["text"]) self.treeUser.item(curUser["text"], values = curUser["values"]) self.treeUser.update() else: messagebox.showwarning("Empty !!!", "Họ tên không thể để trống",parent = self) return print("Cell Values = ", cellValue) def onDeleteUser(self, event = None): curItem = self.treeUser.selection() accept = messagebox.askokcancel("Xóa User này","Bạn thật sự muốn xóa!!! Bạn không thể hoàn tác hành động này", parent = self) if accept == True: if len(curItem) == 0: messagebox.showwarning(title = "Empty !!!", message = "Xin hãy chọn User bạn muốn xóa", parent = self) return for choose in curItem: treeItem = self.treeUser.item(choose) self.data.deleteUser(treeItem["text"]) self.treeUser.delete(treeItem["text"]) def onDestroy(self, event): # ask = """ # Bạn thực sự muốn đóng ứng dụng # """ # messagebox.askokcancel("Closing!!!",) self.parent.linkAccelerator() self.destroy() # if __name__ == "__main__": # root = Tk() # ShowListUserDialog(root) # root.mainloop()
class FormChildTemplate: def __init__(self, frm_parent, connection): self.connection = connection self.directive = Message() self.decide_template = True self.id_selected = 0 self.frm_child_list = LabelFrame(frm_parent) self.frm_child_crud = LabelFrame(frm_parent) self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) self.initialize_components() def initialize_components(self): """ Method that initialize the visual components for each form associated with the local administration """ # Resources for the Forms self.new_icon = PhotoImage(file=r"./Resources/create.png") self.modify_icon = PhotoImage(file=r"./Resources/modify.png") self.remove_icon = PhotoImage(file=r"./Resources/delete.png") self.save_icon = PhotoImage(file=r"./Resources/save.png") self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png") self.add_icon = PhotoImage(file=r"./Resources/right.png") self.delete_icon = PhotoImage(file=r"./Resources/left.png") self.up_arrow = PhotoImage(file=r"./Resources/up_arrow.png") self.down_arrow = PhotoImage(file=r"./Resources/down_arrow.png") self.star_icon = PhotoImage(file=r"./Resources/star.png") self.back_icon = PhotoImage(file=r"./Resources/back.png") self.view_icon = PhotoImage(file=r"./Resources/view.png") # Components for List FRM lbl_sep1 = Label(self.frm_child_list) lbl_sep1.grid(row=0, column=0, padx=10, pady=25) self.trv_available = Treeview(self.frm_child_list, height=20, columns=('N', 'Name')) self.trv_available.heading('#0', text='ID', anchor=CENTER) self.trv_available.heading('#1', text='N', anchor=CENTER) self.trv_available.heading('#2', text='Name', anchor=CENTER) self.trv_available.column('#0', width=0, minwidth=50, stretch=NO) self.trv_available.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available.column('#2', width=375, minwidth=375, stretch=NO) self.trv_available.bind("<ButtonRelease-1>", self.select_template_summary) self.trv_available.grid(row=0, column=1, sticky=W, pady=25) vsb_trv_av = Scrollbar(self.frm_child_list, orient="vertical", command=self.trv_available.yview) vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS) self.trv_available.configure(yscrollcommand=vsb_trv_av.set) frm_aux4 = Frame(self.frm_child_list) btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new) btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E) btn_new_ttp = CreateToolTip(btn_new, 'New template') btn_view = Button(frm_aux4, image=self.view_icon, command=self.click_view) btn_view.grid(row=1, column=0, pady=5, padx=5, sticky=E) btn_view_ttp = CreateToolTip(btn_new, 'View template') btn_edit = Button(frm_aux4, image=self.modify_icon, command=self.click_update) btn_edit.grid(row=2, column=0, pady=5, padx=5, sticky=E) btn_edit_ttp = CreateToolTip(btn_edit, 'Edit template') btn_delete = Button(frm_aux4, image=self.remove_icon, command=self.click_delete) btn_delete.grid(row=3, column=0, pady=5, padx=5, sticky=E) btn_delete_ttp = CreateToolTip(btn_delete, 'Delete template') frm_aux4.grid(row=0, column=3, pady=25, padx=25, sticky=NW) sep_template = Separator(self.frm_child_list, orient=VERTICAL) sep_template.grid(row=0, column=4, sticky=NS, padx=25) frm_aux3 = Frame(self.frm_child_list) lbl_sep3 = Label(frm_aux3) lbl_sep3.grid(row=0, column=0, padx=10, pady=25, rowspan=3) lbl_details = Label(frm_aux3, text='Details') lbl_details.config(fg=TEXT_COLOR, font=SUBTITLE_FONT) lbl_details.grid(row=0, column=1, sticky=W, pady=25, columnspan=2) self.txt_summary = Text(frm_aux3, height=22, width=50) self.txt_summary.config(font=TEXT_FONT, bg=DISABLED_COLOR) self.txt_summary.grid(row=1, column=1) vsb_txt_sum = Scrollbar(frm_aux3, orient="vertical", command=self.txt_summary.yview) vsb_txt_sum.grid(row=1, column=2, sticky=NS) self.txt_summary.configure(yscrollcommand=vsb_txt_sum.set) lbl_sep4 = Label(frm_aux3) lbl_sep4.grid(row=0, column=3, padx=10, pady=25, rowspan=3) lbl_sep5 = Label(frm_aux3) lbl_sep5.grid(row=2, column=1, pady=5, columnspan=2) frm_aux3.grid(row=0, column=5) # Components for CRUD FRM lbl_sep6 = Label(self.frm_child_crud) lbl_sep6.grid(row=0, column=0, padx=10, pady=25, rowspan=10) lbl_name = Label(self.frm_child_crud, text='Name*') lbl_name.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_name.grid(row=0, column=1, pady=25, sticky=NW) lbl_description = Label(self.frm_child_crud, text='Description*') lbl_description.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_description.grid(row=0, column=6, pady=25, sticky=NW) lbl_sep3 = Label(self.frm_child_crud) lbl_sep3.grid(row=0, column=2, padx=10, pady=25) self.txt_name = Entry(self.frm_child_crud, width=30, font=TEXT_FONT) self.txt_name.grid(row=0, column=3, pady=25, sticky=NW) lbl_sep4 = Label(self.frm_child_crud) lbl_sep4.grid(row=0, column=7, padx=10, pady=25) self.txt_description = Text(self.frm_child_crud, height=5, width=49) self.txt_description.config(font=TEXT_FONT) self.txt_description.grid(row=0, column=8, pady=25, sticky=W) vsb_txt_desc = Scrollbar(self.frm_child_crud, orient="vertical", command=self.txt_description.yview) vsb_txt_desc.grid(row=0, column=9, pady=25, sticky=NS) self.txt_description.configure(yscrollcommand=vsb_txt_desc.set) lbl_sep7 = Label(self.frm_child_crud) lbl_sep7.grid(row=0, column=5, padx=10, pady=25, rowspan=3) lbl_sep8 = Label(self.frm_child_crud) lbl_sep8.grid(row=0, column=10, padx=10, pady=25, rowspan=2) lbl_available_d = Label(self.frm_child_crud, text='Available sections') lbl_available_d.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_available_d.grid(row=1, column=1, pady=10, sticky=W, columnspan=4) lbl_selected_d = Label(self.frm_child_crud, text='Selected sections*') lbl_selected_d.config(fg=TEXT_COLOR, font=LABEL_FONT) lbl_selected_d.grid(row=1, column=6, pady=10, sticky=W, columnspan=4) self.trv_available_sections = Treeview(self.frm_child_crud, height=10, columns=('N', 'Name', 'Data Type')) self.trv_available_sections.heading('#0', text='ID', anchor=CENTER) self.trv_available_sections.heading('#1', text='N', anchor=CENTER) self.trv_available_sections.heading('#2', text='Name', anchor=CENTER) self.trv_available_sections.heading('#3', text='Data Type', anchor=CENTER) self.trv_available_sections.column('#0', width=0, minwidth=20, stretch=NO) self.trv_available_sections.column('#1', width=20, minwidth=20, stretch=NO) self.trv_available_sections.column('#2', width=150, minwidth=150, stretch=NO) self.trv_available_sections.column('#3', width=120, minwidth=120, stretch=NO) self.trv_available_sections.bind("<Button-1>", self.click_trv_asections) self.trv_available_sections.grid(row=2, column=1, rowspan=7, columnspan=3, sticky=W, pady=10) vsb_trv_avs = Scrollbar(self.frm_child_crud, orient="vertical", command=self.trv_available_sections.yview) vsb_trv_avs.grid(row=2, column=4, rowspan=7, pady=10, sticky=NS) self.trv_available_sections.configure(yscrollcommand=vsb_trv_avs.set) self.trv_selected_sections = Treeview(self.frm_child_crud, height=10, columns=('N', 'Name', 'Data type', 'Mandatory', 'Main')) self.trv_selected_sections.heading('#0', text='ID', anchor=CENTER) self.trv_selected_sections.heading('#1', text='N', anchor=CENTER) self.trv_selected_sections.heading('#2', text='Name', anchor=CENTER) self.trv_selected_sections.heading('#3', text='Data type', anchor=CENTER) self.trv_selected_sections.heading('#4', text='Mandatory', anchor=CENTER) self.trv_selected_sections.heading('#5', text='Main', anchor=CENTER) self.trv_selected_sections.column('#0', width=0, minwidth=20, stretch=NO) self.trv_selected_sections.column('#1', width=20, minwidth=20, stretch=NO) self.trv_selected_sections.column('#2', width=150, minwidth=150, stretch=NO) self.trv_selected_sections.column('#3', width=120, minwidth=120, stretch=NO) self.trv_selected_sections.column('#4', width=80, minwidth=80, stretch=NO) self.trv_selected_sections.column('#5', width=80, minwidth=80, stretch=NO) self.trv_selected_sections.bind("<Button-1>", self.click_trv_ssections) self.trv_selected_sections.bind("<Double-1>", self.click_switch_mandatory) self.trv_selected_sections.grid(row=2, column=6, rowspan=7, columnspan=3, sticky=W, pady=10) vsb_trv_ses = Scrollbar(self.frm_child_crud, orient="vertical", command=self.trv_selected_sections.yview) vsb_trv_ses.grid(row=2, column=9, rowspan=7, pady=10, sticky=NS) self.trv_selected_sections.configure(yscrollcommand=vsb_trv_ses.set) self.lbl_note_optional = Label( self.frm_child_crud, text= 'NOTES:\tTo switch between optional and mandatory, double click ' 'on selected section.\n\tChoose one or up to three main sections ' 'by first selecting the target sections\n\tand then clicking the ' 'star button.\n') self.lbl_note_optional.config(fg=TEXT_COLOR, font=NOTE_FONT, justify=LEFT) self.btn_add = Button(self.frm_child_crud, image=self.add_icon, command=self.click_add) btn_add_ttp = CreateToolTip(self.btn_add, 'Add section') self.btn_remove = Button(self.frm_child_crud, image=self.delete_icon, command=self.click_remove) btn_remove_ttp = CreateToolTip(self.btn_remove, 'Remove section') self.btn_main_section = Button(self.frm_child_crud, image=self.star_icon, command=self.click_main_section) btn_main_section_ttp = CreateToolTip(self.btn_main_section, 'Main section(s)') self.btn_up = Button(self.frm_child_crud, image=self.up_arrow, command=self.click_up) btn_up_ttp = CreateToolTip(self.btn_up, 'Move up') self.btn_down = Button(self.frm_child_crud, image=self.down_arrow, command=self.click_down) btn_down_ttp = CreateToolTip(self.btn_down, 'Move down') sep_aux1 = Separator(self.frm_child_crud, orient=VERTICAL) sep_aux1.grid(row=0, column=11, sticky=NS, rowspan=10) frm_aux1 = Frame(self.frm_child_crud) self.btn_save = Button(frm_aux1, image=self.save_icon, command=self.click_save) btn_save_ttp = CreateToolTip(self.btn_save, 'Save template') self.btn_back = Button(frm_aux1, image=self.back_icon, command=self.click_back) btn_back_ttp = CreateToolTip(self.btn_back, 'Go back') self.btn_cancel = Button(frm_aux1, image=self.cancel_icon, command=self.click_cancel) btn_cancel_ttp = CreateToolTip(self.btn_cancel, 'Cancel') frm_aux1.grid(row=0, column=12, pady=10, padx=25, sticky=NW, rowspan=10) def retrieve_list(self): # Remove existing elements in the list for item in self.trv_available.get_children(): self.trv_available.delete(item) self.directive = Message(action=37) self.connection = self.directive.send_directive(self.connection) # Adding elements into the list for index, item in enumerate(self.connection.message.information): elements = item.split('¥') self.trv_available.insert('', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 375))) if len(self.trv_available.get_children()) != 0: self.trv_available.selection_set( self.trv_available.get_children()[0]) self.select_template_summary() def select_template_summary(self, event=None): """ Function activated when the event of selecting an item in the available templates TV is generated. It fills the summary text box with information of the selected template :param event: """ if self.trv_available.item( self.trv_available.selection())['text'] != '': # Clear summary txt box self.txt_summary['state'] = NORMAL self.txt_summary.delete('1.0', 'end-1c') self.id_selected = int( self.trv_available.item(self.trv_available.selection()) ['text']) # Retrieve id of selected item from TreeView self.directive = Message(action=40, information=[self.id_selected ]) # ask for the template self.connection = self.directive.send_directive(self.connection) # Insert template's name and description self.txt_summary.insert( 'end-1c', "Name:\n{}\n\n".format(self.connection.message.information[0])) self.txt_summary.insert( 'end-1c', "Description:\n{}\n\nSections:\n".format( self.connection.message.information[1])) self.directive = Message(action=77, information=[ self.id_selected ]) # ask for the sections of the selected template self.connection = self.directive.send_directive(self.connection) # Adding elements in the summary text box index = 0 for item in self.connection.message.information: elements = item.split('¥') self.txt_summary.insert('end-1c', "{}) {}\t\t{}\t\t{}\n".format(index + 1, elements[3], 'optional' \ if elements[7] == '' else 'mandatory', '' if elements[8] == '' else '(MAIN)')) index += 1 self.txt_summary['state'] = DISABLED def show_frm(self): self.retrieve_list() self.frm_child_list.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def hide_frm(self): self.clear_fields() self.frm_child_list.grid_forget() self.frm_child_crud.grid_forget() def click_new(self): self.view_decision = False # Decision when viewing a template self.template = Template() self.retrieve_sections() self.txt_name.focus_set() self.show_cu_buttons() self.frm_child_crud['text'] = 'New template' self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) def click_view(self): if len(self.trv_available.selection()) == 1: self.view_decision = True # Decision when viewing a template self.directive = Message(action=40, information=[self.id_selected]) self.connection = self.directive.send_directive(self.connection) self.template = Template( id=self.id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], sections=self.connection.message.information[2]) self.txt_name.insert(0, self.template.name) self.txt_description.insert('1.0', self.template.description) self.retrieve_sections(self.template.sections) self.txt_name.focus_set() self.disable_visual_components() self.btn_back.grid(row=0, column=0, padx=5, pady=5, sticky=W) self.frm_child_list.grid_forget() self.frm_child_crud['text'] = 'View template' self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_update(self): if len(self.trv_available.selection()) == 1: self.view_decision = False # Decision when viewing a template self.directive = Message( action=40, information=[self.id_selected, 'validate']) self.connection = self.directive.send_directive(self.connection) if self.connection.message.action == 5: # An error ocurred while trying to update the item messagebox.showerror( parent=self.frm_child_list, title='Can not edit the template', message=self.connection.message.information[0]) else: self.template = Template( id=self.id_selected, name=self.connection.message.information[0], description=self.connection.message.information[1], sections=self.connection.message.information[2]) self.txt_name.insert(0, self.template.name) self.txt_description.insert('1.0', self.template.description) self.retrieve_sections(self.template.sections) self.txt_name.focus_set() self.show_cu_buttons() self.frm_child_crud['text'] = 'Update template' self.frm_child_list.grid_forget() self.frm_child_crud.grid(row=1, column=0, columnspan=9, rowspan=8, pady=10, padx=10) else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def click_delete(self): if len(self.trv_available.selection()) == 1: decision = messagebox.askyesno( parent=self.frm_child_list, title='Confirmation', message='Are you sure you want to delete the item?') if decision: self.directive = Message(action=39, information=[self.id_selected]) self.connection = self.directive.send_directive( self.connection) if self.connection.message.action == 5: # An error ocurred while deleting the item messagebox.showerror( parent=self.frm_child_list, title='Can not delete the item', message=self.connection.message.information[0]) else: self.retrieve_list() else: messagebox.showwarning(parent=self.frm_child_list, title='No selection', message='You must select one item') def show_cu_buttons(self): self.btn_add.grid(row=5, column=5, padx=25) self.btn_remove.grid(row=6, column=5, padx=25) self.btn_main_section.grid(row=2, column=10, padx=25) self.btn_down.grid(row=6, column=10, padx=25) self.btn_up.grid(row=5, column=10, padx=25) self.btn_save.grid(row=0, column=0, padx=5, pady=5, sticky=W) self.btn_cancel.grid(row=1, column=0, padx=5, pady=5, sticky=W) self.lbl_note_optional.grid(row=9, column=6, columnspan=4, sticky=W) def disable_visual_components(self): self.txt_name['bg'] = DISABLED_COLOR self.txt_description['bg'] = DISABLED_COLOR self.txt_name['state'] = DISABLED self.txt_description['state'] = DISABLED self.btn_add.grid_forget() self.btn_remove.grid_forget() self.btn_main_section.grid_forget() self.btn_down.grid_forget() self.btn_up.grid_forget() def retrieve_sections(self, s_sections=None): if s_sections is None: s_sections = [] self.directive = Message(action=32) self.connection = self.directive.send_directive(self.connection) a_sections = self.connection.message.information for item in self.trv_available_sections.get_children(): self.trv_available_sections.delete(item) for item in self.trv_selected_sections.get_children(): self.trv_selected_sections.delete(item) for item in s_sections: item_aux1 = item.split('¥')[2:6] item_aux2 = [item.split('¥')[-1]] item = item_aux1 + item_aux2 item = '¥'.join(item) if item in a_sections: a_sections.remove(item) for index, item in enumerate(a_sections): elements = item.split('¥') self.trv_available_sections.insert( '', 'end', text=elements[0], values=(index + 1, summarize_text(elements[1], 150), summarize_text(elements[3], 120))) for index, item in enumerate(s_sections): elements = item.split('¥') self.trv_selected_sections.insert( '', 'end', text=elements[2], values=(index + 1, summarize_text(elements[3], 150), summarize_text(elements[5], 120), elements[7], elements[8])) def click_add(self): """ Function that moves a 'Section' from available tree view to selected tree view (in frm_child_crud) """ if len(self.trv_available_sections.selection()) != 0 and len( self.trv_selected_sections.selection()) == 0: if len(self.trv_selected_sections.get_children()) != 0: index = self.trv_selected_sections.item( self.trv_selected_sections.get_children()[-1])['values'][0] else: index = 0 for row in self.trv_available_sections.selection(): index += 1 values = self.trv_available_sections.item(row)['values'] self.trv_selected_sections.insert( '', 'end', text=self.trv_available_sections.item(row)['text'], values=(index, values[1], values[2], '✓', '')) self.trv_available_sections.delete(row) def click_remove(self): """ Function that moves a 'Section' from selected tree view to available tree view (in frm_child_crud) """ if len(self.trv_selected_sections.selection()) != 0 and len( self.trv_available_sections.selection()) == 0: if len(self.trv_available_sections.get_children()) != 0: index = self.trv_available_sections.item( self.trv_available_sections.get_children() [-1])['values'][0] else: index = 0 for row in self.trv_selected_sections.selection(): index += 1 values = self.trv_selected_sections.item(row)['values'] self.trv_available_sections.insert( '', 'end', text=self.trv_selected_sections.item(row)['text'], values=(index, values[1], values[2])) self.trv_selected_sections.delete(row) def click_up(self): # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: item = self.trv_selected_sections.selection() index = self.trv_selected_sections.index(item) self.trv_selected_sections.move(item, '', index - 1) def click_down(self): # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: item = self.trv_selected_sections.selection() index = self.trv_selected_sections.index(item) self.trv_selected_sections.move(item, '', index + 1) def click_main_section(self): # Make sure a max of three items of 'selected sections' are selected if 4 > len(self.trv_selected_sections.selection()) > 0 and len( self.trv_available_sections.selection()) == 0: # First change all sections as normal (not main) for item in self.trv_selected_sections.get_children(): if self.trv_selected_sections.item(item)['values'][3] != '': values = self.trv_selected_sections.item(item)['values'] self.trv_selected_sections.item( item, values=(values[0], values[1], values[2], values[3], '')) # Set new main sections cont_error = False for row in self.trv_selected_sections.selection(): values = self.trv_selected_sections.item(row)['values'] if values[2] == 'Classification' or values[2] == 'Text': self.trv_selected_sections.item( row, values=(values[0], values[1], values[2], values[3], '✓')) else: # File sections can not be main cont_error = True if cont_error: messagebox.showwarning( parent=self.frm_child_crud, title='Main section(s)', message= "Main section(s) must be of 'Text' or 'Classification' data type" ) else: messagebox.showwarning( parent=self.frm_child_crud, title='Main section(s)', message= 'You must select a minimum of one and a maximum of three sections' ) def click_trv_asections(self, event): """ Function that removes selection from 'available' tree view when 'selected' tree view is selected (in frm_child_crud) """ self.trv_selected_sections.selection_remove( self.trv_selected_sections.selection()) def click_trv_ssections(self, event): """ Function that removes selection from 'selected' tree view when 'available' tree view is selected (in frm_child_crud) """ self.trv_available_sections.selection_remove( self.trv_available_sections.selection()) def click_save(self): if self.validate_fields(): self.template.name = self.txt_name.get() self.template.description = self.txt_description.get( '1.0', 'end-1c') if self.template.id == 0: # Creating a template self.directive = Message(action=36, information=[ self.template.name, self.template.description, [], [], [] ]) for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] self.directive.information[2].append( int(self.trv_selected_sections.item(item)['text'])) self.directive.information[3].append(values[4]) if values[4] != '': self.directive.information[4].append('✓') else: self.directive.information[4].append(values[3]) else: # Updating a template self.directive = Message(action=38, information=[ self.template.id, self.template.name, self.template.description, [], [], [] ]) for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] self.directive.information[3].append( int(self.trv_selected_sections.item(item)['text'])) self.directive.information[4].append(values[4]) if values[4] != '': self.directive.information[5].append('✓') else: self.directive.information[5].append(values[3]) self.connection = self.directive.send_directive(self.connection) self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def click_cancel(self): decision = True if self.txt_name.get() != self.template.name or \ self.txt_description.get('1.0', 'end-1c') != self.template.description or \ len(self.trv_selected_sections.get_children()) != len(self.template.sections): decision = messagebox.askyesno( parent=self.frm_child_crud, title='Cancel', message='Are you sure you want to cancel?') if decision: self.click_back() def click_back(self): self.clear_fields() self.frm_child_crud.grid_forget() self.show_frm() def validate_fields(self): text_section = False if len(self.txt_name.get()) == 0: messagebox.showwarning(parent=self.frm_child_crud, title='Missing information', message='You must provide a name') return False if len(self.txt_description.get('1.0', 'end-1c')) == 0: messagebox.showwarning(parent=self.frm_child_crud, title='Missing information', message='You must provide a description') return False if len(self.trv_selected_sections.get_children()) == 0: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='You must select at least one section') return False for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] if values[2] == 'Text' or values[2] == 'Classification': text_section = True break if not text_section: messagebox.showwarning( parent=self.frm_child_crud, title='Missing information', message='At least one section has to be of text type') return False for item in self.trv_selected_sections.get_children(): values = self.trv_selected_sections.item(item)['values'] if values[4] == '✓': if values[2] == 'Text' or values[2] == 'Classification': return True else: messagebox.showwarning( parent=self.frm_child_crud, title='Main section', message= 'The main section has to be of text or classification type' ) return False messagebox.showwarning( parent=self.frm_child_crud, title='Main section', message='You must set one of the selected section as main') return False def clear_fields(self): self.txt_name['state'] = NORMAL self.txt_description['state'] = NORMAL self.txt_name['bg'] = ENABLED_COLOR self.txt_description['bg'] = ENABLED_COLOR self.txt_name.delete(0, END) self.txt_description.delete('1.0', 'end-1c') self.btn_save.grid_forget() self.btn_cancel.grid_forget() self.btn_back.grid_forget() self.lbl_note_optional.grid_forget() def click_switch_mandatory(self, event): if not self.view_decision: # Only if not viewing a template # Make sure only one item in 'selected sections' is selected if len(self.trv_selected_sections.selection()) == 1 and len( self.trv_available_sections.selection()) == 0: values = self.trv_selected_sections.item( self.trv_selected_sections.focus())['values'] if values[3] == '': self.trv_selected_sections.item( self.trv_selected_sections.focus(), values=(values[0], values[1], values[2], '✓', values[4])) else: self.trv_selected_sections.item( self.trv_selected_sections.focus(), values=(values[0], values[1], values[2], '', values[4]))
class TreeType(Frame): def __init__(self, parent, item, dialog): Frame.__init__(self, parent) self.parent = parent self.item = item self.numTypeAdded = 0 self.dialogParent = dialog self.listType = item.type self.data = Database() self.drawScreen() self.linkAccelerator() for typeItem in self.listType: self.addTypeIntoTree(typeItem) def linkAccelerator(self): self.bind_all("<Shift-Delete>", self.onDeleteItem) self.bind_all("<Control-N>", self.onAddType) self.bind_all("<Delete>", self.onDeleteType) def drawScreen(self): # self.pack(fill = BOTH, anchor = "w", expand = True) ########## Listing all Bills which has been created ########### layoutList = Frame(self) self.drawTreeOfType(layoutList) layoutList.pack(side=LEFT, fill=BOTH, expand=True) self.menu = Menu(self) self.dialogParent.config(menu=self.menu) self.drawMenu(self.menu) def drawTreeOfType(self, parent): self.pack(fill=BOTH, expand=True) listAttribute = ["Loại hàng", "Số lượng tồn", "Đơn giá"] yScrollTree = Scrollbar(parent, orient=VERTICAL) xScrollTree = Scrollbar(parent, orient=HORIZONTAL) # Create Delete, Add and Edit Button layoutButton = Frame(self) btnAddType = Button(layoutButton, text="+", fg="green", command=self.onAddType) btnDelType = Button(layoutButton, text="-", fg="red", command=self.onDeleteType) btnRefresh = Button(layoutButton, text="Refresh", fg="blue", command=self.dialogParent.onRefresh) btnAddType.pack(side=LEFT) btnDelType.pack(side=LEFT) btnRefresh.pack(side=RIGHT) layoutButton.pack(side=TOP, fill=X, anchor="w") # Create Tree View self.treeType = Treeview(parent, column=listAttribute, yscrollcommand=yScrollTree.set, xscrollcommand=xScrollTree.set) self.treeType.bind(sequence="<Double-Button-1>", func=self.onEditType) self.treeType.heading(column="#0", text="ID") self.treeType.column(column="#0", width=100, minwidth=100) for nameAttr in listAttribute: self.treeType.heading(column=nameAttr, text=nameAttr) self.treeType.column(column=nameAttr, width=100, minwidth=100) #Create Scrollbar for tree view yScrollTree.pack(side=RIGHT, fill=Y) xScrollTree.pack(side=BOTTOM, fill=X) self.treeType.pack(side=TOP, anchor="w", fill=BOTH, expand=True) yScrollTree.config(command=self.treeType.yview) xScrollTree.config(command=self.treeType.xview) def drawMenu(self, parent): # Item Menu itemMenu = Menu(parent) itemMenu.add_command(label="Thêm mặt hàng", command=self.dialogParent.onAddItem, accelerator="Ctrl+N") itemMenu.add_command(label="Xóa mặt hàng", command=self.onDeleteItem, accelerator="Shift+Delete") parent.add_cascade(label="Mặt hàng", menu=itemMenu) # Type Menu typeMenu = Menu(parent) typeMenu.add_command(label="Thêm loại hàng", command=self.onAddType, accelerator="Ctrl+Shift+N") typeMenu.add_command(label="Xóa loại hàng", command=self.onDeleteType, accelerator="Delete") parent.add_cascade(label="Loại hàng", menu=typeMenu) def addTypeIntoTree(self, typeInput): temp = typeInput # if not self.treeType.exists(temp.idParent) : # self.treeType.insert("","end", str(temp.idParent), text = temp.idParent) # self.treeType.item(temp.idParent, open = True) self.treeType.insert("", "end", str(temp.idType), text=temp.idType) self.treeType.set(temp.idType, "Loại hàng", temp.name) self.treeType.set(temp.idType, "Số lượng tồn", temp.amount) self.treeType.set(temp.idType, "Đơn giá", int(temp.unitPrice)) def onEditType(self, event=None): listTemp = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] curItem = self.treeType.item(self.treeType.focus()) col = self.treeType.identify_column(event.x) print(curItem) print(col) if curItem["text"] in listTemp and col != "#0": if col != "#0": messagebox.showinfo( "Thêm loại", "Sửa ID loại tạm này để lưu lại vào Database", parent=self) return cellValue = None if col == "#0": temp = simpledialog.askstring("Đổi ID", "Nhập ID mới", parent=self) if temp != None: if len(temp) > 0: cellValue = curItem["text"] try: if cellValue in listTemp: idTab = self.parent.select() frameChosen = self.parent._nametowidget(idTab) typeItem = TypeItem(curItem["values"][0], 0, 0, temp, frameChosen.item.id) self.data.insertTypeItem(typeItem) frameChosen.treeType.update() print("Sau khi sua:", self.treeType.item(self.treeType.focus())) else: self.data.updateIdOfType(cellValue, temp) except (sqlite3.IntegrityError, TclError): messagebox.showwarning( "Opps !!!!", message="ID bạn nhập đã tồn tại !!!!", parent=self) return if cellValue in listTemp: frameChosen.numTypeAdded -= 1 self.treeType.update() else: messagebox.showwarning("Empty !!!", "ID không thể để trống", parent=self) self.treeType.insert( "", str(self.treeType.index(self.treeType.focus())), temp, text=temp, values=curItem["values"]) self.treeType.delete(self.treeType.focus()) return if col == "#1": temp = simpledialog.askstring("Đổi tên loại hàng", "Nhập tên mới", parent=self) if temp != None: if len(temp) > 0: cellValue = curItem["values"][0] self.data.updateNameOfType(curItem["text"], temp) curItem["values"][0] = temp self.treeType.item(curItem["text"], values=curItem["values"]) self.treeType.update() else: messagebox.showwarning("Empty !!!", "Tên loại hàng không thể để trống", parent=self) return if col == "#2": temp = simpledialog.askinteger("Đổi số lượng tồn", "Nhập số lượng tồn mới: ", parent=self) if temp != None: if temp >= 0: cellValue = curItem["values"][1] self.data.updateAmountOfType(curItem["text"], temp) curItem["values"][1] = temp print(curItem["text"]) self.treeType.item(curItem["text"], values=curItem["values"]) self.treeType.update() else: messagebox.showwarning("Empty !!!", "Số lượng không thể là số âm", parent=self) return if col == "#3": temp = simpledialog.askfloat("Đổi đơn giá", "Nhập đơn giá mới: ", parent=self) if temp != None: if temp >= 0: cellValue = curItem["values"][2] self.data.updateUnitOfType(curItem["text"], temp) curItem["values"][2] = temp print(curItem["text"]) self.treeType.item(curItem["text"], values=curItem["values"]) self.treeType.update() else: messagebox.showwarning("Empty !!!", "Đơn giá không thể là số âm", parent=self) return print("Cell Values = ", cellValue) def onAddType(self, event=None): idTab = self.parent.select() frameChosen = self.parent._nametowidget(idTab) if frameChosen.numTypeAdded > 9: messagebox.showwarning( "Warning", "Bạn chỉ có thể thêm tạm 10 loại \n Hãy Sửa ID để lưu lại những loại tạm trên", parent=self) return typeItem = TypeItem(name=frameChosen.numTypeAdded, amount=0, unitPrice=0, idType="#", idParent=frameChosen.item.id) frameChosen.addTypeIntoTree(typeItem) frameChosen.numTypeAdded += 1 def onDeleteType(self, event=None): idTab = self.parent.select() frameChosen = self.parent._nametowidget(idTab) curItem = frameChosen.treeType.selection() accept = messagebox.askokcancel( "Xóa loại hàng này", "Bạn thật sự muốn xóa!!! Bạn không thể hoàn tác hành động này", parent=self) if accept == True: if len(curItem) == 0: messagebox.showwarning( title="Empty !!!", message="Xin hãy chọn loại hàng bạn muốn xóa", parent=self) return for choose in curItem: treeItem = frameChosen.treeType.item(choose) self.data.deleteType(treeItem["values"][0]) frameChosen.treeType.delete(treeItem["text"]) def onDeleteItem(self, event=None): idTab = self.parent.select() frameChosen = self.parent._nametowidget(idTab) accept = messagebox.askokcancel( "Xóa loại hàng này", "Bạn thật sự muốn xóa!!! Bạn không thể hoàn tác hành động này", parent=self) if accept == True: idTab = self.parent.select() frameChosen = self.parent._nametowidget(idTab) self.data.deleteItem(frameChosen.item.id) self.parent.forget(idTab) self.parent.update() def onDestroy(self, event): # ask = """ # Bạn thực sự muốn đóng ứng dụng # """ # messagebox.askokcancel("Closing!!!",) self.dialogParent.destroy()
class EventScheduler(Tk): def __init__(self): Tk.__init__(self, className='Scheduler') logging.info('Start') self.protocol("WM_DELETE_WINDOW", self.hide) self._visible = BooleanVar(self, False) self.withdraw() self.icon_img = PhotoImage(master=self, file=ICON48) self.iconphoto(True, self.icon_img) # --- systray icon self.icon = TrayIcon(ICON, fallback_icon_path=ICON_FALLBACK) # --- menu self.menu_widgets = SubMenu(parent=self.icon.menu) self.menu_eyes = Eyes(self.icon.menu, self) self.icon.menu.add_checkbutton(label=_('Manager'), command=self.display_hide) self.icon.menu.add_cascade(label=_('Widgets'), menu=self.menu_widgets) self.icon.menu.add_cascade(label=_("Eyes' rest"), menu=self.menu_eyes) self.icon.menu.add_command(label=_('Settings'), command=self.settings) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('About'), command=lambda: About(self)) self.icon.menu.add_command(label=_('Quit'), command=self.exit) self.icon.bind_left_click(lambda: self.display_hide(toggle=True)) add_trace(self._visible, 'write', self._visibility_trace) self.menu = Menu(self, tearoff=False) self.menu.add_command(label=_('Edit'), command=self._edit_menu) self.menu.add_command(label=_('Delete'), command=self._delete_menu) self.right_click_iid = None self.menu_task = Menu(self.menu, tearoff=False) self._task_var = StringVar(self) menu_in_progress = Menu(self.menu_task, tearoff=False) for i in range(0, 110, 10): prog = '{}%'.format(i) menu_in_progress.add_radiobutton(label=prog, value=prog, variable=self._task_var, command=self._set_progress) for state in ['Pending', 'Completed', 'Cancelled']: self.menu_task.add_radiobutton(label=_(state), value=state, variable=self._task_var, command=self._set_progress) self._img_dot = tkPhotoImage(master=self) self.menu_task.insert_cascade(1, menu=menu_in_progress, compound='left', label=_('In Progress'), image=self._img_dot) self.title('Scheduler') self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.scheduler = BackgroundScheduler(coalesce=False, misfire_grace_time=86400) self.scheduler.add_jobstore('sqlalchemy', url='sqlite:///%s' % JOBSTORE) self.scheduler.add_jobstore('memory', alias='memo') # --- style self.style = Style(self) self.style.theme_use("clam") self.style.configure('title.TLabel', font='TkdefaultFont 10 bold') self.style.configure('title.TCheckbutton', font='TkdefaultFont 10 bold') self.style.configure('subtitle.TLabel', font='TkdefaultFont 9 bold') self.style.configure('white.TLabel', background='white') self.style.configure('border.TFrame', background='white', border=1, relief='sunken') self.style.configure("Treeview.Heading", font="TkDefaultFont") bgc = self.style.lookup("TButton", "background") fgc = self.style.lookup("TButton", "foreground") bga = self.style.lookup("TButton", "background", ("active", )) self.style.map('TCombobox', fieldbackground=[('readonly', 'white'), ('readonly', 'focus', 'white')], background=[("disabled", "active", "readonly", bgc), ("!disabled", "active", "readonly", bga)], foreground=[('readonly', '!disabled', fgc), ('readonly', '!disabled', 'focus', fgc), ('readonly', 'disabled', 'gray40'), ('readonly', 'disabled', 'focus', 'gray40') ], arrowcolor=[("disabled", "gray40")]) self.style.configure('menu.TCombobox', foreground=fgc, background=bgc, fieldbackground=bgc) self.style.map('menu.TCombobox', fieldbackground=[('readonly', bgc), ('readonly', 'focus', bgc)], background=[("disabled", "active", "readonly", bgc), ("!disabled", "active", "readonly", bga)], foreground=[('readonly', '!disabled', fgc), ('readonly', '!disabled', 'focus', fgc), ('readonly', 'disabled', 'gray40'), ('readonly', 'disabled', 'focus', 'gray40') ], arrowcolor=[("disabled", "gray40")]) self.style.map('DateEntry', arrowcolor=[("disabled", "gray40")]) self.style.configure('cal.TFrame', background='#424242') self.style.configure('month.TLabel', background='#424242', foreground='white') self.style.configure('R.TButton', background='#424242', arrowcolor='white', bordercolor='#424242', lightcolor='#424242', darkcolor='#424242') self.style.configure('L.TButton', background='#424242', arrowcolor='white', bordercolor='#424242', lightcolor='#424242', darkcolor='#424242') active_bg = self.style.lookup('TEntry', 'selectbackground', ('focus', )) self.style.map('R.TButton', background=[('active', active_bg)], bordercolor=[('active', active_bg)], darkcolor=[('active', active_bg)], lightcolor=[('active', active_bg)]) self.style.map('L.TButton', background=[('active', active_bg)], bordercolor=[('active', active_bg)], darkcolor=[('active', active_bg)], lightcolor=[('active', active_bg)]) self.style.configure('txt.TFrame', background='white') self.style.layout('down.TButton', [('down.TButton.downarrow', { 'side': 'right', 'sticky': 'ns' })]) self.style.map('TRadiobutton', indicatorforeground=[('disabled', 'gray40')]) self.style.map('TCheckbutton', indicatorforeground=[('disabled', 'gray40')], indicatorbackground=[ ('pressed', '#dcdad5'), ('!disabled', 'alternate', 'white'), ('disabled', 'alternate', '#a0a0a0'), ('disabled', '#dcdad5') ]) self.style.map('down.TButton', arrowcolor=[("disabled", "gray40")]) self.style.map('TMenubutton', arrowcolor=[('disabled', self.style.lookup('TMenubutton', 'foreground', ['disabled']))]) bg = self.style.lookup('TFrame', 'background', default='#ececec') self.configure(bg=bg) self.option_add('*Toplevel.background', bg) self.option_add('*Menu.background', bg) self.option_add('*Menu.tearOff', False) # toggle text self._open_image = PhotoImage(name='img_opened', file=IM_OPENED, master=self) self._closed_image = PhotoImage(name='img_closed', file=IM_CLOSED, master=self) self._open_image_sel = PhotoImage(name='img_opened_sel', file=IM_OPENED_SEL, master=self) self._closed_image_sel = PhotoImage(name='img_closed_sel', file=IM_CLOSED_SEL, master=self) self.style.element_create( "toggle", "image", "img_closed", ("selected", "!disabled", "img_opened"), ("active", "!selected", "!disabled", "img_closed_sel"), ("active", "selected", "!disabled", "img_opened_sel"), border=2, sticky='') self.style.map('Toggle', background=[]) self.style.layout('Toggle', [('Toggle.border', { 'children': [('Toggle.padding', { 'children': [('Toggle.toggle', { 'sticky': 'nswe' })], 'sticky': 'nswe' })], 'sticky': 'nswe' })]) # toggle sound self._im_sound = PhotoImage(master=self, file=IM_SOUND) self._im_mute = PhotoImage(master=self, file=IM_MUTE) self._im_sound_dis = PhotoImage(master=self, file=IM_SOUND_DIS) self._im_mute_dis = PhotoImage(master=self, file=IM_MUTE_DIS) self.style.element_create( 'mute', 'image', self._im_sound, ('selected', '!disabled', self._im_mute), ('selected', 'disabled', self._im_mute_dis), ('!selected', 'disabled', self._im_sound_dis), border=2, sticky='') self.style.layout('Mute', [('Mute.border', { 'children': [('Mute.padding', { 'children': [('Mute.mute', { 'sticky': 'nswe' })], 'sticky': 'nswe' })], 'sticky': 'nswe' })]) self.style.configure('Mute', relief='raised') # widget scrollbar self._im_trough = {} self._im_slider_vert = {} self._im_slider_vert_prelight = {} self._im_slider_vert_active = {} self._slider_alpha = Image.open(IM_SCROLL_ALPHA) for widget in ['Events', 'Tasks']: bg = CONFIG.get(widget, 'background', fallback='gray10') fg = CONFIG.get(widget, 'foreground') widget_bg = self.winfo_rgb(bg) widget_fg = tuple( round(c * 255 / 65535) for c in self.winfo_rgb(fg)) active_bg = active_color(*widget_bg) active_bg2 = active_color(*active_color(*widget_bg, 'RGB')) slider_vert = Image.new('RGBA', (13, 28), active_bg) slider_vert_active = Image.new('RGBA', (13, 28), widget_fg) slider_vert_prelight = Image.new('RGBA', (13, 28), active_bg2) self._im_trough[widget] = tkPhotoImage(width=15, height=15, master=self) self._im_trough[widget].put(" ".join( ["{" + " ".join([bg] * 15) + "}"] * 15)) self._im_slider_vert_active[widget] = PhotoImage( slider_vert_active, master=self) self._im_slider_vert[widget] = PhotoImage(slider_vert, master=self) self._im_slider_vert_prelight[widget] = PhotoImage( slider_vert_prelight, master=self) self.style.element_create('%s.Vertical.Scrollbar.trough' % widget, 'image', self._im_trough[widget]) self.style.element_create( '%s.Vertical.Scrollbar.thumb' % widget, 'image', self._im_slider_vert[widget], ('pressed', '!disabled', self._im_slider_vert_active[widget]), ('active', '!disabled', self._im_slider_vert_prelight[widget]), border=6, sticky='ns') self.style.layout( '%s.Vertical.TScrollbar' % widget, [('%s.Vertical.Scrollbar.trough' % widget, { 'children': [('%s.Vertical.Scrollbar.thumb' % widget, { 'expand': '1' })], 'sticky': 'ns' })]) # --- tree columns = { _('Summary'): ({ 'stretch': True, 'width': 300 }, lambda: self._sort_by_desc(_('Summary'), False)), _('Place'): ({ 'stretch': True, 'width': 200 }, lambda: self._sort_by_desc(_('Place'), False)), _('Start'): ({ 'stretch': False, 'width': 150 }, lambda: self._sort_by_date(_('Start'), False)), _('End'): ({ 'stretch': False, 'width': 150 }, lambda: self._sort_by_date(_("End"), False)), _('Category'): ({ 'stretch': False, 'width': 100 }, lambda: self._sort_by_desc(_('Category'), False)) } self.tree = Treeview(self, show="headings", columns=list(columns)) for label, (col_prop, cmd) in columns.items(): self.tree.column(label, **col_prop) self.tree.heading(label, text=label, anchor="w", command=cmd) self.tree.tag_configure('0', background='#ececec') self.tree.tag_configure('1', background='white') self.tree.tag_configure('outdated', foreground='red') scroll = AutoScrollbar(self, orient='vertical', command=self.tree.yview) self.tree.configure(yscrollcommand=scroll.set) # --- toolbar toolbar = Frame(self) self.img_plus = PhotoImage(master=self, file=IM_ADD) Button(toolbar, image=self.img_plus, padding=1, command=self.add).pack(side="left", padx=4) Label(toolbar, text=_("Filter by")).pack(side="left", padx=4) # --- TODO: add filter by start date (after date) self.filter_col = Combobox( toolbar, state="readonly", # values=("",) + self.tree.cget('columns')[1:], values=("", _("Category")), exportselection=False) self.filter_col.pack(side="left", padx=4) self.filter_val = Combobox(toolbar, state="readonly", exportselection=False) self.filter_val.pack(side="left", padx=4) Button(toolbar, text=_('Delete All Outdated'), padding=1, command=self.delete_outdated_events).pack(side="right", padx=4) # --- grid toolbar.grid(row=0, columnspan=2, sticky='we', pady=4) self.tree.grid(row=1, column=0, sticky='eswn') scroll.grid(row=1, column=1, sticky='ns') # --- restore data data = {} self.events = {} self.nb = 0 try: with open(DATA_PATH, 'rb') as file: dp = Unpickler(file) data = dp.load() except Exception: l = [ f for f in os.listdir(os.path.dirname(BACKUP_PATH)) if f.startswith('data.backup') ] if l: l.sort(key=lambda x: int(x[11:])) shutil.copy(os.path.join(os.path.dirname(BACKUP_PATH), l[-1]), DATA_PATH) with open(DATA_PATH, 'rb') as file: dp = Unpickler(file) data = dp.load() self.nb = len(data) backup() now = datetime.now() for i, prop in enumerate(data): iid = str(i) self.events[iid] = Event(self.scheduler, iid=iid, **prop) self.tree.insert('', 'end', iid, values=self.events[str(i)].values()) tags = [str(self.tree.index(iid) % 2)] self.tree.item(iid, tags=tags) if not prop['Repeat']: for rid, d in list(prop['Reminders'].items()): if d < now: del self.events[iid]['Reminders'][rid] self.after_id = self.after(15 * 60 * 1000, self.check_outdated) # --- bindings self.bind_class("TCombobox", "<<ComboboxSelected>>", self.clear_selection, add=True) self.bind_class("TCombobox", "<Control-a>", self.select_all) self.bind_class("TEntry", "<Control-a>", self.select_all) self.tree.bind('<3>', self._post_menu) self.tree.bind('<1>', self._select) self.tree.bind('<Double-1>', self._edit_on_click) self.menu.bind('<FocusOut>', lambda e: self.menu.unpost()) self.filter_col.bind("<<ComboboxSelected>>", self.update_filter_val) self.filter_val.bind("<<ComboboxSelected>>", self.apply_filter) # --- widgets self.widgets = {} prop = { op: CONFIG.get('Calendar', op) for op in CONFIG.options('Calendar') } self.widgets['Calendar'] = CalendarWidget(self, locale=CONFIG.get( 'General', 'locale'), **prop) self.widgets['Events'] = EventWidget(self) self.widgets['Tasks'] = TaskWidget(self) self.widgets['Timer'] = Timer(self) self.widgets['Pomodoro'] = Pomodoro(self) self._setup_style() for item, widget in self.widgets.items(): self.menu_widgets.add_checkbutton( label=_(item), command=lambda i=item: self.display_hide_widget(i)) self.menu_widgets.set_item_value(_(item), widget.variable.get()) add_trace(widget.variable, 'write', lambda *args, i=item: self._menu_widgets_trace(i)) self.icon.loop(self) self.tk.eval(""" apply {name { set newmap {} foreach {opt lst} [ttk::style map $name] { if {($opt eq "-foreground") || ($opt eq "-background")} { set newlst {} foreach {st val} $lst { if {($st eq "disabled") || ($st eq "selected")} { lappend newlst $st $val } } if {$newlst ne {}} { lappend newmap $opt $newlst } } else { lappend newmap $opt $lst } } ttk::style map $name {*}$newmap }} Treeview """) # react to scheduler --update-date in command line signal.signal(signal.SIGUSR1, self.update_date) # update selected date in calendar and event list every day self.scheduler.add_job(self.update_date, CronTrigger(hour=0, minute=0, second=1), jobstore='memo') self.scheduler.start() def _setup_style(self): # scrollbars for widget in ['Events', 'Tasks']: bg = CONFIG.get(widget, 'background', fallback='gray10') fg = CONFIG.get(widget, 'foreground', fallback='white') widget_bg = self.winfo_rgb(bg) widget_fg = tuple( round(c * 255 / 65535) for c in self.winfo_rgb(fg)) active_bg = active_color(*widget_bg) active_bg2 = active_color(*active_color(*widget_bg, 'RGB')) slider_vert = Image.new('RGBA', (13, 28), active_bg) slider_vert.putalpha(self._slider_alpha) slider_vert_active = Image.new('RGBA', (13, 28), widget_fg) slider_vert_active.putalpha(self._slider_alpha) slider_vert_prelight = Image.new('RGBA', (13, 28), active_bg2) slider_vert_prelight.putalpha(self._slider_alpha) self._im_trough[widget].put(" ".join( ["{" + " ".join([bg] * 15) + "}"] * 15)) self._im_slider_vert_active[widget].paste(slider_vert_active) self._im_slider_vert[widget].paste(slider_vert) self._im_slider_vert_prelight[widget].paste(slider_vert_prelight) for widget in self.widgets.values(): widget.update_style() def report_callback_exception(self, *args): err = ''.join(traceback.format_exception(*args)) logging.error(err) showerror('Exception', str(args[1]), err, parent=self) def save(self): logging.info('Save event database') data = [ev.to_dict() for ev in self.events.values()] with open(DATA_PATH, 'wb') as file: pick = Pickler(file) pick.dump(data) def update_date(self, *args): """Update Calendar's selected day and Events' list.""" self.widgets['Calendar'].update_date() self.widgets['Events'].display_evts() self.update_idletasks() # --- bindings def _select(self, event): if not self.tree.identify_row(event.y): self.tree.selection_remove(*self.tree.selection()) def _edit_on_click(self, event): sel = self.tree.selection() if sel: sel = sel[0] self.edit(sel) # --- class bindings @staticmethod def clear_selection(event): combo = event.widget combo.selection_clear() @staticmethod def select_all(event): event.widget.selection_range(0, "end") return "break" # --- show / hide def _menu_widgets_trace(self, item): self.menu_widgets.set_item_value(_(item), self.widgets[item].variable.get()) def display_hide_widget(self, item): value = self.menu_widgets.get_item_value(_(item)) if value: self.widgets[item].show() else: self.widgets[item].hide() def hide(self): self._visible.set(False) self.withdraw() self.save() def show(self): self._visible.set(True) self.deiconify() def _visibility_trace(self, *args): self.icon.menu.set_item_value(_('Manager'), self._visible.get()) def display_hide(self, toggle=False): value = self.icon.menu.get_item_value(_('Manager')) if toggle: value = not value self.icon.menu.set_item_value(_('Manager'), value) self._visible.set(value) if not value: self.withdraw() self.save() else: self.deiconify() # --- event management def event_add(self, event): self.nb += 1 iid = str(self.nb) self.events[iid] = event self.tree.insert('', 'end', iid, values=event.values()) self.tree.item(iid, tags=str(self.tree.index(iid) % 2)) self.widgets['Calendar'].add_event(event) self.widgets['Events'].display_evts() self.widgets['Tasks'].display_tasks() self.save() def event_configure(self, iid): self.tree.item(iid, values=self.events[iid].values()) self.widgets['Calendar'].add_event(self.events[iid]) self.widgets['Events'].display_evts() self.widgets['Tasks'].display_tasks() self.save() def add(self, date=None): iid = str(self.nb + 1) if date is not None: event = Event(self.scheduler, iid=iid, Start=date) else: event = Event(self.scheduler, iid=iid) Form(self, event, new=True) def delete(self, iid): index = self.tree.index(iid) self.tree.delete(iid) for k, item in enumerate(self.tree.get_children('')[index:]): tags = [ t for t in self.tree.item(item, 'tags') if t not in ['1', '0'] ] tags.append(str((index + k) % 2)) self.tree.item(item, tags=tags) self.events[iid].reminder_remove_all() self.widgets['Calendar'].remove_event(self.events[iid]) del (self.events[iid]) self.widgets['Events'].display_evts() self.widgets['Tasks'].display_tasks() self.save() def edit(self, iid): self.widgets['Calendar'].remove_event(self.events[iid]) Form(self, self.events[iid]) def check_outdated(self): """Check for outdated events every 15 min.""" now = datetime.now() for iid, event in self.events.items(): if not event['Repeat'] and event['Start'] < now: tags = list(self.tree.item(iid, 'tags')) if 'outdated' not in tags: tags.append('outdated') self.tree.item(iid, tags=tags) self.after_id = self.after(15 * 60 * 1000, self.check_outdated) def delete_outdated_events(self): now = datetime.now() outdated = [] for iid, prop in self.events.items(): if prop['End'] < now: if not prop['Repeat']: outdated.append(iid) elif prop['Repeat']['Limit'] != 'always': end = prop['End'] enddate = datetime.fromordinal( prop['Repeat']['EndDate'].toordinal()) enddate.replace(hour=end.hour, minute=end.minute) if enddate < now: outdated.append(iid) for item in outdated: self.delete(item) logging.info('Deleted outdated events') def refresh_reminders(self): """ Reschedule all reminders. Required when APScheduler is updated. """ for event in self.events.values(): reminders = [date for date in event['Reminders'].values()] event.reminder_remove_all() for date in reminders: event.reminder_add(date) logging.info('Refreshed reminders') # --- sorting def _move_item(self, item, index): self.tree.move(item, "", index) tags = [t for t in self.tree.item(item, 'tags') if t not in ['1', '0']] tags.append(str(index % 2)) self.tree.item(item, tags=tags) @staticmethod def to_datetime(date): date_format = get_date_format("short", CONFIG.get("General", "locale")).pattern dayfirst = date_format.startswith("d") yearfirst = date_format.startswith("y") return parse(date, dayfirst=dayfirst, yearfirst=yearfirst) def _sort_by_date(self, col, reverse): l = [(self.to_datetime(self.tree.set(k, col)), k) for k in self.tree.get_children('')] l.sort(reverse=reverse) # rearrange items in sorted positions for index, (val, k) in enumerate(l): self._move_item(k, index) # reverse sort next time self.tree.heading(col, command=lambda: self._sort_by_date(col, not reverse)) def _sort_by_desc(self, col, reverse): l = [(self.tree.set(k, col), k) for k in self.tree.get_children('')] l.sort(reverse=reverse, key=lambda x: x[0].lower()) # rearrange items in sorted positions for index, (val, k) in enumerate(l): self._move_item(k, index) # reverse sort next time self.tree.heading(col, command=lambda: self._sort_by_desc(col, not reverse)) # --- filter def update_filter_val(self, event): col = self.filter_col.get() self.filter_val.set("") if col: l = set() for k in self.events: l.add(self.tree.set(k, col)) self.filter_val.configure(values=tuple(l)) else: self.filter_val.configure(values=[]) self.apply_filter(event) def apply_filter(self, event): col = self.filter_col.get() val = self.filter_val.get() items = list(self.events.keys()) if not col: for item in items: self._move_item(item, int(item)) else: i = 0 for item in items: if self.tree.set(item, col) == val: self._move_item(item, i) i += 1 else: self.tree.detach(item) # --- manager's menu def _post_menu(self, event): self.right_click_iid = self.tree.identify_row(event.y) self.tree.selection_remove(*self.tree.selection()) self.tree.selection_add(self.right_click_iid) if self.right_click_iid: try: self.menu.delete(_('Progress')) except TclError: pass state = self.events[self.right_click_iid]['Task'] if state: self._task_var.set(state) if '%' in state: self._img_dot = PhotoImage(master=self, file=IM_DOT) else: self._img_dot = tkPhotoImage(master=self) self.menu_task.entryconfigure(1, image=self._img_dot) self.menu.insert_cascade(0, menu=self.menu_task, label=_('Progress')) self.menu.tk_popup(event.x_root, event.y_root) def _delete_menu(self): if self.right_click_iid: self.delete(self.right_click_iid) def _edit_menu(self): if self.right_click_iid: self.edit(self.right_click_iid) def _set_progress(self): if self.right_click_iid: self.events[self.right_click_iid]['Task'] = self._task_var.get() self.widgets['Tasks'].display_tasks() if '%' in self._task_var.get(): self._img_dot = PhotoImage(master=self, file=IM_DOT) else: self._img_dot = tkPhotoImage(master=self) self.menu_task.entryconfigure(1, image=self._img_dot) # --- icon menu def exit(self): self.save() rep = self.widgets['Pomodoro'].stop(self.widgets['Pomodoro'].on) if not rep: return self.menu_eyes.quit() self.after_cancel(self.after_id) try: self.scheduler.shutdown() except SchedulerNotRunningError: pass self.destroy() def settings(self): splash_supp = CONFIG.get('General', 'splash_supported', fallback=True) dialog = Settings(self) self.wait_window(dialog) self._setup_style() if splash_supp != CONFIG.get('General', 'splash_supported'): for widget in self.widgets.values(): widget.update_position() # --- week schedule def get_next_week_events(self): """Return events scheduled for the next 7 days """ locale = CONFIG.get("General", "locale") next_ev = {} today = datetime.now().date() for d in range(7): day = today + timedelta(days=d) evts = self.widgets['Calendar'].get_events(day) if evts: evts = [self.events[iid] for iid in evts] evts.sort(key=lambda ev: ev.get_start_time()) desc = [] for ev in evts: if ev["WholeDay"]: date = "" else: date = "%s - %s " % ( format_time(ev['Start'], locale=locale), format_time(ev['End'], locale=locale)) place = "(%s)" % ev['Place'] if place == "()": place = "" desc.append(("%s%s %s\n" % (date, ev['Summary'], place), ev['Description'])) next_ev[day.strftime('%A')] = desc return next_ev # --- tasks def get_tasks(self): # TODO: find events with repetition in the week # TODO: better handling of events on several days tasks = [] for event in self.events.values(): if event['Task']: tasks.append(event) return tasks